I recently ran into a problem where a control was rendering slow enough that it caused flickering (as the screen region is cleared and then redrawn).
Looking around the ‘net I settled on rendering the control to a bitmap and then drawing the bitmap to the screen (fast operation), which is called double buffering.
For example.
//In the OnPaint function or every time you redraw the control Bitmap offscreenBitMap = new Bitmap(this.Width, this.Height); Graphics offscreenGraphics = Graphics.FromImage(offscreenBitMap); g.Clear(this.BackColor); //Drawing code with offscreenGraphics offscreenGraphics.Dispose(); //Draw bitmap to screen Graphics clientDC = this.CreateGraphics(); clientDC.DrawImage(offscreenBitMap , 0, 0);
The above should be sufficient for most people, but read on if you redraw the control many times a second.
While this solved the flickering problem another problem promptly appeared (don’t they always).
Because the control was being redrawn about 10-15 times a second, it was causing the application to “stutter”, as in not respond for the milliseconds when the control was being drawn (i.e. dragging the window around wouldn’t be smooth).
So putting the render code in its own thread seemed like a good idea. Problem now is how to synchronise/communicate the need to re-render the control when needed.
My solution was to use an object lock and Monitor.
Method of operation
- Application thread locks object
- Render thread attempts to lock object (waits for application thread to release object)
- When redraw is needed, release object
- Render thread acquires object lock
- Control is drawn to bitmap
- Render thread releases object
- Application thread is invoked to post the redraw
- Goto 1
One thing to note is that this threaded model shouldn’t be used in design mode, as it may cause Visual Studio to hang.
Another is that calls to redraw the control should be ignored if its already rendering from another call.
Download [Project] [Code File]
[..using..] using System.Threading; public partial class ThreadedRendererControl : Control { Thread renderThread; object renderControl = new object(); private bool currentlyDrawing = false; private object dataLock = new object(); public ThreadedRendererControl() { InitializeComponent(); Monitor.Enter(renderControl); if (!DesignMode) //No point threading in design mode (in IDE) { renderThread = new Thread(new ThreadStart(Renderer)); Disposed += new EventHandler(ThreadedRendererControl_Disposed); } Resize += new EventHandler(this.ThreadedRendererControl_Resize); } void ThreadedRendererControl_Disposed(object sender, EventArgs e) { renderThread.Abort(); } public void UpdateData() { lock (dataLock) { //Update any data here that is used by the renderer } } public void Redraw() { if (DesignMode)//Just draw component in design mode { this.Invalidate(); //this will call OnPaint return; } //Only start the render thread on the first redraw if (renderThread.ThreadState == ThreadState.Unstarted) renderThread.Start(); //Prevent multiple attempts to draw before current request has finished lock (dataLock) { if (currentlyDrawing == false) currentlyDrawing = true; else return; } //Allow the render thread to aquire the lock Monitor.Exit(renderControl); } private void Renderer() { while (true) { Bitmap bmp; Monitor.Enter(renderControl); lock (dataLock) //prevent data from being changed during rendering { bmp = Draw(); } //Release lock Monitor.Exit(renderControl); //If the form is currently loading (not yet displayed) //posting the redraw will crash the app, as there's nothing to draw to if (IsHandleCreated) //Synchronously invoke the PostRedraw, this will also lock the renderControl object this.Invoke(new Action(PostRedraw), new object[] { bmp }); else Thread.Sleep(10); //Wait, and loop again } } private void PostRedraw(Bitmap bmp) { Monitor.Enter(renderControl); lock (dataLock) currentlyDrawing = false; //Draw the bitmap Graphics clientDC = this.CreateGraphics(); clientDC.DrawImage(bmp, 0, 0); } private Bitmap Draw() { Bitmap offscreenBitMap = new Bitmap(this.Width, this.Height); Graphics offscreenGraphics = Graphics.FromImage(offscreenBitMap); offscreenGraphics.Clear(this.BackColor); Font labelFont = this.Font; offscreenGraphics.DrawString("I was drawn in another thread!", labelFont, Brushes.Black, 0, 0); offscreenGraphics.Dispose(); return offscreenBitMap; } protected override void OnPaint(PaintEventArgs pe) { if (DesignMode) { Graphics clientDC = this.CreateGraphics(); clientDC.DrawImage(Draw(), 0, 0); } else Redraw(); } private void ThreadedRendererControl_Resize(object sender, EventArgs e) { Redraw(); } }
In hindsight maybe I should of just found a way to draw it faster rather than use threading. Regardless, I hope you found this useful!
One Response to Threaded flicker-free control rendering in c#