albert
albert

Reputation: 1571

ListView flicker when sort

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

Answers (2)

Hans Passant
Hans Passant

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

matzone
matzone

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

Related Questions