Reputation: 1571
I have an inherited listview and there is significant flickering when I click column headers. The list is in details view.
public ListViewEx()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
this.DoubleBuffered = true;
}
int sortColumn = -1;
protected override void OnColumnClick(ColumnClickEventArgs e)
{
if(e.Column != sortColumn)
{
sortColumn = e.Column;
this.Sorting = SortOrder.Ascending;
}
else
{
if(this.Sorting == SortOrder.Ascending)
this.Sorting = SortOrder.Descending;
else
this.Sorting = SortOrder.Ascending;
}
this.Sort();
}
There is no flickering when I populate the list.
for(int i = 0; i < 10; i++)
{
ListViewItem lvi = new ListViewItem("this is column 1 " +i);
lvi.SubItems.Add("...
lvi.SubItems.Add("...
lvi.SubItems.Add("...
lvi.SubItems.Add("...
lvi.SubItems.Add("...
listViewEx1.Items .Add (lvi);
}
Edit
WM_ERASEBKGND didn't solve my problem.
I added this code at the form hosting the listview and the flicker is gone
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}
Upvotes: 0
Views: 778
Reputation: 941407
You can see what's going wrong here by adding this code to your class:
protected override void OnHandleCreated(EventArgs e) {
Console.WriteLine("Listview window created");
base.OnHandleCreated(e);
}
Run your program and click column headers, pay attention to the Output window. You'll see that every time you click, you'll see the "Listview window created" message appear. Or in other words, the ListView window gets recreated every time you sort. That will always flicker, no matter what double-buffering you use.
This is caused by your code assigning the ListView.Sorting property. The underlying native implementation for it is a style flag, it can only be specified when the window is created. So when you change it, Winforms is forced to recreate the window. Flicker is the inevitable side-effect.
There's a better way to do this, you can also implement a custom sorting method for the control by using its ListViewItemSorter property. All you have to do is provide a class that implements the IComparable interface. Might as well have the ListView itself implement it. Make your code look like this and sorting will be silky smooth:
using System;
using System.Windows.Forms;
class ListViewEx : ListView, System.Collections.IComparer {
public ListViewEx() {
this.DoubleBuffered = true;
this.ListViewItemSorter = this;
}
public int Compare(object x, object y) {
var item1 = (ListViewItem)x;
var item2 = (ListViewItem)y;
int compare = String.Compare(item1.SubItems[this.sortColumn].Text, item2.SubItems[this.sortColumn].Text);
if (sortOrder == SortOrder.Descending) compare = -compare;
return compare;
}
protected override void OnColumnClick(ColumnClickEventArgs e) {
if (e.Column != sortColumn) {
sortColumn = e.Column;
this.sortOrder = SortOrder.Ascending;
}
else {
if (this.sortOrder == SortOrder.Ascending)
this.sortOrder = SortOrder.Descending;
else
this.sortOrder = SortOrder.Ascending;
}
this.Sort();
}
private int sortColumn = 0;
private SortOrder sortOrder = SortOrder.Ascending;
}
Upvotes: 5
Reputation: 5719
class ListViewNF : System.Windows.Forms.ListView
{
public ListViewNF()
{
//Activate double buffering
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
//Enable the OnNotifyMessage event so we get a chance to filter out
// Windows messages before they get to the form's WndProc
this.SetStyle(ControlStyles.EnableNotifyMessage, true);
}
protected override void OnNotifyMessage(Message m)
{
//Filter out the WM_ERASEBKGND message
if(m.Msg != 0x14)
{
base.OnNotifyMessage(m);
}
}
}
This is the source
Upvotes: 1