Мерцание в режиме просмотра списка при использовании ownerdraw и виртуального режима

Я использую элемент управления списком со следующими параметрами:

        this.listView1.BackColor = System.Drawing.Color.Gainsboro;
        this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
        this.columnHeader1,
        this.columnHeader2});
        this.listView1.FullRowSelect = true;
        this.listView1.HideSelection = false;
        this.listView1.Location = new System.Drawing.Point(67, 192);
        this.listView1.Name = "listView1";
        this.listView1.Size = new System.Drawing.Size(438, 236);
        this.listView1.TabIndex = 0;
        this.listView1.UseCompatibleStateImageBehavior = false;
        this.listView1.View = System.Windows.Forms.View.Details;
        this.listView1.DrawColumnHeader += new System.Windows.Forms.DrawListViewColumnHeaderEventHandler(this.listView1_DrawColumnHeader);
        this.listView1.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(this.listView1_RetrieveVirtualItem);
        this.listView1.DrawSubItem += new System.Windows.Forms.DrawListViewSubItemEventHandler(this.listView1_DrawSubItem);

Две строки снабжены произвольным текстом. Собственник рисовать просто:

    private void listView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
    {
        if (e.ColumnIndex == 0)
        {
            e.DrawBackground();
            e.DrawText();                
        }
        else
            e.DrawDefault = true;
        //Console.WriteLine("{0}\t\tBounds:{1}\tItem:{2}\tSubitem:{3}", (i++).ToString(), e.Bounds.ToString(), e.Item, e.SubItem);
    }

проблема в том, что когда я наводил указатель мыши на содержимое списка, у меня мерцал первый столбец. Отладка показывает, что DrawSubItem вызывается постоянно, пока на него наведен указатель мыши.

Это ошибка? Как избежать такого поведения?


person silly_dg    schedule 02.06.2009    source источник
comment
Это старый вопрос, но принятый ответ неверен, по крайней мере, не для .NET 4.0. Проверьте защищенный атрибут DoubleBuffered класса ListView и, возможно, мой ответ на this вопрос.   -  person zmilojko    schedule 08.05.2012
comment
Полученный ответ полностью правильный. В XP, если у вас есть виртуальный список и вы наведете курсор на столбец 0, элемент управления будет мерцать. DoubleBuffered = true не имеет значения. Верно, что в Windows 7 такой проблемы не возникает, но это не означает, что этот ответ неверен.   -  person Grammarian    schedule 11.05.2012


Ответы (3)


Это ошибка в ListView .NET, и вы не можете обойти ее с помощью двойной буферизации.

В виртуальных списках базовый элемент управления генерирует множество настраиваемых событий рисования при наведении указателя мыши на столбец 0. Эти настраиваемые события рисования вызывают мерцание, даже если вы включаете DoubleBuffering, поскольку они отправляются вне обычного сообщения WmPaint.

Я также, кажется, помню, что это происходит только на XP. Vista исправила это (но представила другие).

Вы можете посмотреть код в ObjectListView, чтобы увидеть, как он решил эту проблему.

Если вы хотите решить эту проблему самостоятельно, вам нужно вникнуть во внутреннюю сеть элемента управления ListView:

  1. переопределить WndProc
  2. перехватить сообщение WmPaint и установить флаг, который является истинным во время сообщения
  3. перехватить сообщение WmCustomDraw и игнорировать все сообщения, происходящие вне события WmPaint.

Что-то вроде этого::

protected override void WndProc(ref Message m) {
    switch (m.Msg) {
        case 0x0F: // WM_PAINT
            this.isInWmPaintMsg = true;
            base.WndProc(ref m);
            this.isInWmPaintMsg = false;
            break;
        case 0x204E: // WM_REFLECT_NOTIFY
            NativeMethods.NMHDR nmhdr = (NativeMethods.NMHDR)m.GetLParam(typeof(NativeMethods.NMHDR));
            if (nmhdr.code == -12) { // NM_CUSTOMDRAW
                if (this.isInWmPaintMsg)
                    base.WndProc(ref m);
            } else
                base.WndProc(ref m);
            break;
        default:
            base.WndProc(ref m);
            break;
    }
}
person Grammarian    schedule 02.06.2009
comment
В моем случае я переопределил каждую функцию OnDrawXXX и поставил перед каждой функцией проверку на (this.isInWmPaintMsg == true). Как и этот метод, он решил проблему для меня (по крайней мере, на Win7). Я удалил корпус 0x204E, что является хорошим решением, но оно зависит от внутренней константы WM_REFLECT .NET, определенной как 0x2000. Хотя это почти наверняка никогда не изменится, WM_PAINT НИКОГДА не изменится. Нашел это в гугле, большое спасибо за указатели! - person Michael; 31.08.2010
comment
WM_REFLECT_NOTIFY не зависит от .NET. Он используется со времен программирования Windows в стиле Петцольда. MFC широко его использует. - person Grammarian; 02.09.2010
comment
@Grammarian Я создал продолжение этого вопроса (я думаю, есть некоторые моменты, которые не совсем ясны), не могли бы вы взглянуть - stackoverflow.com/questions/10484265/ - person Yippie-Ki-Yay; 07.05.2012

Я получаю кучу

'System.Drawing.NativeMethods' is inaccessible due to its protection level

и

The type name 'NMHDR' does not exist in the type 'System.Drawing.NativeMethods' 

ошибки. Я где-то читал, что мне нужно включить user32.dll, но не могу понять, как это сделать в этом случае.

Редактировать: Хорошо, я отправил еще до того, как начал думать. Я создал свой собственный элемент управления ListView и скопировал структуру из кода objectListView. Вроде работает сейчас. Вот мой код:

public class Listview : ListView
{
    private bool isInWmPaintMsg=false;

    [StructLayout(LayoutKind.Sequential)]
    public struct NMHDR
    {
        public IntPtr hwndFrom;
        public IntPtr idFrom;
        public int code;
    }

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case 0x0F: // WM_PAINT
                this.isInWmPaintMsg = true;
                base.WndProc(ref m);
                this.isInWmPaintMsg = false;
                break;
            case 0x204E: // WM_REFLECT_NOTIFY
                NMHDR nmhdr = (NMHDR)m.GetLParam(typeof(NMHDR));
                if (nmhdr.code == -12)
                { // NM_CUSTOMDRAW
                    if (this.isInWmPaintMsg)
                        base.WndProc(ref m);
                }
                else
                    base.WndProc(ref m);
                break;
            default:
                base.WndProc(ref m);
                break;
        }
    }

}
person r4i    schedule 05.11.2009

Я не могу предложить решение для ListView, слишком часто вызывающего пользовательские события рисования, но, возможно, вы можете просто замаскировать проблему с помощью двойной буферизации:

Stackoverflow: как удвоить буферизацию элементов управления .NET на форму?

person Ian Boyd    schedule 02.06.2009