Можно ли устранить мерцание перерисовки в плохо написанном дочернем контроле? - PullRequest
0 голосов
/ 07 мая 2018

Короткая версия : Я использую элемент управления WinForms, который мигает при частой перерисовке. Я обнаружил, что ни одно из существующих решений типа «Включение двойного буфера» не является эффективным, поскольку этот элемент управления буквально выполняет две различные операции рисования непосредственно на своей поверхности рисования при перерисовке. Этот элемент управления должен быть интерактивным (следовательно, самым верхним в форме). Учитывая, что я могу выполнять инструкции непосредственно перед тем, как этот элемент управления начинает перерисовывать, и сразу после того, как он заканчивает перерисовывать, есть ли что-нибудь, что может сделать родительский контейнер или содержащая Форма, чтобы устранить мерцание этого элемента управления?

Полное объяснение : Я делаю пользовательский контроль. ListView как один из детей на нем. Я использую ListView, чтобы в моем элементе управления были столбцы и все функции, которые предоставляют столбцы ListView (следовательно, на моем элементе управления фактически видна только часть заголовка столбца ListView). Мне нужно нарисовать некоторую дополнительную информацию (назовем это «оверлей»), где находится заголовок столбца ListView, но в противном случае я не хочу изменять «внешний вид» заголовка столбца. Очевидно, что я никак не могу просто нарисовать оверлей из моего пользовательского элемента управления (ListView - это дочерний элемент моего элемента управления, и он скрывает поверхность моего элемента управления). У меня не может быть элемента управления в верхней части ListView, который бы функционировал в качестве поверхности рисования оверлея - конечно, я могу рисовать на нем ListView и даже сохранять интерактивность, путаясь с сообщениями WM_NCHITTEST, но теперь ListView не перерисовывается и я застрял либо с перерисовкой всего ListView на любых событиях мыши, либо с ручным отслеживанием того, какие области ListView необходимо перерисовать на основе событий, которые получает мой элемент управления оверлеем (слишком хлопотно).

Таким образом, очевидное решение состоит в том, чтобы переопределить заголовок столбца ListView WndProc (как это делает ObjectListView) и добавить туда мой код для наложения чертежа. Итак, у меня есть:

protected override void WndProc(ref Message m) {
  switch (m.Msg) {
    case WM_PAINT:
      RECT r = new RECT();
      WinAPI.GetUpdateRect(Handle, ref r, false);       //user32.dll - bool GetUpdateRect(IntPtr hWnd, ref RECT rect, bool erase)
      Rectangle rc = new Rectangle(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
         /* marker 1 */
      base.WndProc(ref m);                      //draw the column headers
         /* marker 2 */
      MyPaint(rc, Graphics.FromHwnd(Handle));   //draw overlay  (I'm aware of graphics needing to be disposed)
         /* marker 3 */
      return;
    }
  base.WndProc(ref m);
}

Отлично выглядит! Но при перерисовке часто, иногда я вижу на экране результат рисования только до marker 2. Разумеется, это имеет смысл, поскольку теперь у меня есть две различные операции рисования прямо на поверхности рисования заголовка столбца ListView. Тем не менее, я могу установить любое желаемое состояние в marker 1 и marker 3, так что должен быть какой-то способ, чтобы я мог каким-то образом приостановить рисование где-то, верно? Ну, я не могу придумать, как это сделать. Так вот мой вопрос - возможно ли это?

В принципе, любые решения, которые я пытался каким-то образом приостановить перерисовку, очевидно, вызывают перерисовку на 'un-suspend', что приводит к бесконечным WM_PAINT с. Я действительно пытался избежать просто избавления от base.WndProc(ref m) и отрисовки заголовков столбцов самостоятельно, а также от любого стиля рисования, который должен идти вместе с ним, чтобы сохранить «родной» вид заголовков столбцов. Но после нескольких дней работы с WndProc s я начинаю думать, что рисование заголовков столбцов с нуля на самом деле является наименее болезненным решением ...

1 Ответ

0 голосов
/ 08 мая 2018

Ну, я только что понял, что все это время я пропускал одно довольно простое решение - просто нарисовал базовый ListView на другой поверхности! Итак, теперь у меня есть:

case WM_Paint:
  PAINTSTRUCT ps;
  WinAPI.BeginPaint(Handle, out ps);            //user32 native calls
  Rectangle rc = new Rectangle(ps.rcPaint.Left, ps.rcPaint.Top, ps.rcPaint.Right - ps.rcPaint.Left, ps.rcPaint.Bottom - ps.rcPaint.Top);
  listview.DrawToBitmap(bmp, listview.ClientRectangle); //send yourself a WM_PRINT, pretty much
  buf.Graphics.SetClip(rc);                     //now just draw everything to a BufferedGraphicsContext buffer
  buf.Graphics.DrawImageUnscaled(bmp, 0, 0);    //draw base ListView column header
  MyPaint(rc, buf.Graphics);                    //draw overlay
  buf.Render(Graphics.FromHwnd(Handle));        //flush buffer
  WinAPI.EndPaint(Handle, ref ps);              //validate drawing rectangle
  return;

Нет больше мерцания! Самое приятное то, что это отвечает на исходный вопрос в общем смысле, поскольку эту технику можно использовать для «исправления» любого плохо прорисовывающего элемента управления (или добавления дополнительного кода рисования к одному), при условии, что элемент управления правильно обрабатывает сообщения WM_PRINT. .

...