ListBox Flicker
Here's some not so simple code to solve C#'s ListBox flicker. The problem arises when the ListBox is automatically updated via some means. If its being updated via Timer or BackgroundWorker, or a Child Thread calling Invoke(), the result is the same. With anything more than a trivial number of elements, the ListBox periodically flickers when updated. Update it often and its down-right annoying.
You can try the standby solutions., like wrapping your call to update the item with
... If that doesn't work, you can try changing your Form.DoubleBuffer property to true.
But that still may not work. You can even go and find the topics on ListView flicker. The ListView discusson on the web gets to the heart of the issue. The window is erasing the background when it doesn't need to. The solution for ListView is to derive a new class from ListView, set a few ControlStyles and then override the OnNotifyMessage() member.
But that doesn't solve it for ListBox. The solution below, draws upon the ListView solution, and then adds to it something posted by Niel B on EggHeadCafe. Thank you.
Like with ListView, the solution involves Deriving a new ListBox of your own and replacing your existing ListBox with it. Copy the code below into your project Form1.cs (or where ever) and then go to your form and replace references to ListBox with FlickerFreeListBox. You should see the FlickerFreeListBox in your controls tool box, so you can also just drop it on your forms. Since it overrides OnPaint() and must have OnDrawItem() called, the DrawMode is automatically set to OwnerDrawnFixed. If you need something else you will have to experiment.
But enough of all the chatter. Where's the code, right? Here you go.
Update: September 24, 2008
Many of those who are having flicker problems with the ListBox are having them because they are programmatically updating the control. By that, I mean that the flicker issue is most pronounced when the program rather than the user input (mouse click, keyboard) results in an update to the ListBox. This programmatic control might be in the form of a timer handler, or a background thread, or the like. For that reason, I've decided to update this page to provide an easy reference to my articles covering multi-threading, synchronization, and timers. I hope you find these of value...
You can try the standby solutions., like wrapping your call to update the item with
BeginUpdate();
//my update... and
EndUpdate();
... If that doesn't work, you can try changing your Form.DoubleBuffer property to true.
this.DoubleBuffer = true;
But that still may not work. You can even go and find the topics on ListView flicker. The ListView discusson on the web gets to the heart of the issue. The window is erasing the background when it doesn't need to. The solution for ListView is to derive a new class from ListView, set a few ControlStyles and then override the OnNotifyMessage() member.
But that doesn't solve it for ListBox. The solution below, draws upon the ListView solution, and then adds to it something posted by Niel B on EggHeadCafe. Thank you.
Like with ListView, the solution involves Deriving a new ListBox of your own and replacing your existing ListBox with it. Copy the code below into your project Form1.cs (or where ever) and then go to your form and replace references to ListBox with FlickerFreeListBox. You should see the FlickerFreeListBox in your controls tool box, so you can also just drop it on your forms. Since it overrides OnPaint() and must have OnDrawItem() called, the DrawMode is automatically set to OwnerDrawnFixed. If you need something else you will have to experiment.
But enough of all the chatter. Where's the code, right? Here you go.
internal class FlickerFreeListBox : System.Windows.Forms.ListBox
{
public FlickerFreeListBox()
{
this.SetStyle(
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint,
true);
this.DrawMode = DrawMode.OwnerDrawFixed;
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
if (this.Items.Count > 0)
{
e.DrawBackground();
e.Graphics.DrawString(this.Items[e.Index].ToString(), e.Font, new SolidBrush(this.ForeColor), new PointF(e.Bounds.X, e.Bounds.Y));
}
base.OnDrawItem(e);
}
protected override void OnPaint(PaintEventArgs e)
{
Region iRegion = new Region(e.ClipRectangle);
e.Graphics.FillRegion(new SolidBrush(this.BackColor), iRegion);
if (this.Items.Count > 0)
{
for (int i = 0; i < this.Items.Count; ++i)
{
System.Drawing.Rectangle irect = this.GetItemRectangle(i);
if (e.ClipRectangle.IntersectsWith(irect))
{
if ((this.SelectionMode == SelectionMode.One && this.SelectedIndex == i)
|| (this.SelectionMode == SelectionMode.MultiSimple && this.SelectedIndices.Contains(i))
|| (this.SelectionMode == SelectionMode.MultiExtended && this.SelectedIndices.Contains(i)))
{
OnDrawItem(new DrawItemEventArgs(e.Graphics, this.Font,
irect, i,
DrawItemState.Selected, this.ForeColor,
this.BackColor));
}
else
{
OnDrawItem(new DrawItemEventArgs(e.Graphics, this.Font,
irect, i,
DrawItemState.Default, this.ForeColor,
this.BackColor));
}
iRegion.Complement(irect);
}
}
}
base.OnPaint(e);
}
}
Update: September 24, 2008
Many of those who are having flicker problems with the ListBox are having them because they are programmatically updating the control. By that, I mean that the flicker issue is most pronounced when the program rather than the user input (mouse click, keyboard) results in an update to the ListBox. This programmatic control might be in the form of a timer handler, or a background thread, or the like. For that reason, I've decided to update this page to provide an easy reference to my articles covering multi-threading, synchronization, and timers. I hope you find these of value...
Timers Are A Changin' | The first in my series of articles on "timers". |
Locked-Up | An article on understanding and avoiding dead-lock. |
Thread Syncrohinzed Queue | An article explaining how to make a synchronized queue. |
Threading with .NET ThreadPool | The first in a series of articles on multi-threading using the ThreadPool. |
A Simple TaskQueue | An article and source code on an easy to use task sequencer that works in a different thread. |
Comments
As a test, I commented out all of the code in the overridden OnPaint() handler and it drew an empty ListBox as expected (I think... or should it just be a blank white rectangle? - I had scrollbars always enabled). However, as I resized the ListBox it would display that first item in the list still, flickering.
Any ideas how to fix this? Thanks
if ((this.SelectionMode == SelectionMode.One && this.SelectedIndex == i)
|| (this.SelectionMode == SelectionMode.MultiSimple && this.SelectedIndices.Contains(i))
|| (this.SelectionMode == SelectionMode.MultiExtended && this.SelectedIndices.Contains(i)))
I'll do a follow-up to the article at some point when I have some more of the selection issues solved.
I have one suggestion tho
e.Graphics.DrawString(this.Items[e.Index].ToString(), e.Font, new SolidBrush(this.ForeColor), new PointF(e.Bounds.X, e.Bounds.Y));
should be
e.Graphics.DrawString(this.Items[e.Index].ToString(), e.Font, new SolidBrush(e.ForeColor), new PointF(e.Bounds.X, e.Bounds.Y));
so it uses the correct forecolor on select.
protected override void OnDrawItem(DrawItemEventArgs e)
{
if (this.Items.Count > 0)
{
string text;
e.DrawBackground();
if (this.DataSource != null && this.DataManager != null)
{
System.Collections.IList list = this.DataManager.List;
PropertyDescriptorCollection propList = this.DataManager.GetItemProperties();
PropertyDescriptor prop = propList.Find(this.DisplayMember, false);
text = prop.GetValue(this.DataManager.List[e.Index]).ToString();
}
else
text = this.Items[e.Index].ToString();
e.Graphics.DrawString(text, e.Font, new SolidBrush(e.ForeColor), new PointF(e.Bounds.X, e.Bounds.Y));
}
base.OnDrawItem(e);
}
this.DoubleBuffered = true;
BTW, it really irritates me that we have to resort to subclassing to get a simple control to work properly.
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true);
My listbox flickers whenever it gets focus.
(the entire listbox flickers)
If I change focus to some other control on the form, the flickering stops.
When I click on the listbox again, it will start flickering again...
Do you have any solution???
Thx,
Alon
Goodbye annoying flicker and thank you very much
Here is how to get rid of this design flaw in 3 steps:
1. Override the control's wndproc like this:
private bool resizing;
protected override void WndProc( ref Message m )
{
if( m.Msg == 5 ) // WM_SIZE = 0x05
{
this.resizing = true;
base.WndProc( ref m );
this.resizing = false;
return;
}
base.WndProc( ref m );
}
2. When you call OnDrawItem from OnPaint, add a custom DrawItemState flag so OnDrawItem can tell it comes from OnPaint (I use 0x1000000 because it will probably never be used by Microsoft). The OnPaint line in question looks like this in my code:
this.OnDrawItem( new DrawItemEventArgs( e.Graphics, this.Font, rect, i,
(this.SelectedIndices.Contains(i) ? DrawItemState.Selected : DrawItemState.Default) |
(DrawItemState) 0x1000000 ) );
3. And finally, put the following line at the begining of OnDrawItem:
if( e.Index == -1 || ((int)e.State & 0x1000000) == 0 && this.resizing ) return;
If you're also having the issue with many item populations, make the bool public and set it to true before the ListBox.Add loop and false after (and find a better name for the bool :p).