Реализация по умолчанию для ListView OwnerDraw - PullRequest
9 голосов
/ 30 января 2012

У меня есть ListView , где я хочу настроить рисование элементов (например, выделение определенных строк в элементах представления списка), однако я не хочу радикально изменять способ отображения элементов.

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

Есть ли где-нибудь, где я могу увидеть, что такое обработчики по умолчанию для DrawItem и DrawSubItem события делают так, чтобы я мог лучше понять и легче настроить свой код?

Для справки здесь приведен фрагмент, показывающий, что я в данный моментделать:

public MyListView()
{
    this.OwnerDraw = true;
    this.DoubleBuffered = true;
    this.DrawColumnHeader += new DrawListViewColumnHeaderEventHandler(MyListView_DrawColumnHeader);
    this.DrawItem += new DrawListViewItemEventHandler(MyListView_DrawItem);
    this.DrawSubItem += new DrawListViewSubItemEventHandler(MyListView_DrawSubItem);
}

private void MyListView_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
    // Not interested in changing the way columns are drawn - this works fine
    e.DrawDefault = true;
}

private void MyListView_DrawItem(object sender, DrawListViewItemEventArgs e)
{
    e.DrawBackground();
    e.DrawFocusRectangle();
}

private void MyListView_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
    string searchTerm = "Term";
    int index = e.SubItem.Text.IndexOf(searchTerm);
    if (index >= 0)
    {
        string sBefore = e.SubItem.Text.Substring(0, index);

        Size bounds = new Size(e.Bounds.Width, e.Bounds.Height);
        Size s1 = TextRenderer.MeasureText(e.Graphics, sBefore, this.Font, bounds);
        Size s2 = TextRenderer.MeasureText(e.Graphics, searchTerm, this.Font, bounds);

        Rectangle rect = new Rectangle(e.Bounds.X + s1.Width, e.Bounds.Y, s2.Width, e.Bounds.Height);
        e.Graphics.FillRectangle(new SolidBrush(Color.Yellow), rect);
    }

    e.DrawText();
}

Ответы [ 5 ]

12 голосов
/ 01 февраля 2012

У меня нет времени, чтобы написать полный ответ, поэтому вместо этого я запишу несколько быстрых заметок и вернусь к ним позже.

Как сказал LarsTech, рисование владельцем элемента управления ListView является проблемой - класс .Net ListView является оберткой вокруг базового Win32 List View Control , а возможность "рисовать владельца" предоставляется NM_CUSTOMDRAW кодом уведомления . Таким образом, «стандартная реализация .Net» отсутствует - по умолчанию используется базовый элемент управления Win32.

Чтобы сделать жизнь еще сложнее, есть ряд дополнительных соображений:

  • Как указывал LarsTech, первый подэлемент фактически представляет сам родительский элемент, и поэтому, если вы обрабатываете рендеринг как в DrawItem, так и DrawSubItem, вы вполне можете рисовать содержимое первой ячейки дважды.
  • В базовом элементе управления представлением списка есть ошибка (задокументированная в примечании на на этой странице ), которая означает, что событие DrawItem произойдет без соответствующих событий DrawSubItem, что означает, что если вы рисуете фон в событии DrawItem, а затем нарисуйте текст в событии DrawSubItem, текст вашего элемента исчезнет, ​​когда вы наведете курсор мыши.
  • По умолчанию часть рендеринга также не имеет двойной буферизации
  • Я также заметил, что свойство ItemState не всегда корректно, например, сразу после изменения размера столбца. Следовательно, я нашел все возможное, чтобы не полагаться на него.
  • Вам также необходимо убедиться, что ваш текст не разделен на несколько строк, иначе вы увидите несколько верхних пикселей нижней строки, отображаемых в нижней части ячейки.
  • Также необходимо уделить особое внимание при рендеринге первой ячейки, чтобы учесть дополнительные отступы, которые использует собственный вид списка.
  • Поскольку событие DrawItem происходит первым, все, что вы рисуете в обработчике DrawItem (например, эффект выбора), может быть наложено на то, что вы делаете в обработчике DrawSubItem (например, наличие определенных ячеек с другим фоном). цвет).

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

Я также обнаружил, что просмотр исходного кода для ObjectListView очень удобен.

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

Когда я получу шанс, я попытаюсь опубликовать мой рабочий пример кода.

3 голосов
/ 30 января 2012

Не знаю, поможет ли это вам полностью, но я добавлю несколько заметок:

Следует иметь в виду, что DrawSubItem также нарисует первый элемент, и, вероятно, именно отсюда вы получаете double-rendered взгляд.

Некоторые вещи, которые нужно попробовать (без учета скорости):

private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e) {
  e.DrawBackground();
  if ((e.State & ListViewItemStates.Selected) == ListViewItemStates.Selected) {
    Rectangle r = new Rectangle(e.Bounds.Left + 4, e.Bounds.Top, TextRenderer.MeasureText(e.Item.Text, e.Item.Font).Width, e.Bounds.Height);
    e.Graphics.FillRectangle(SystemBrushes.Highlight, r);
    e.Item.ForeColor = SystemColors.HighlightText;
  } else {
    e.Item.ForeColor = SystemColors.WindowText;
  }
  e.DrawText();
  e.DrawFocusRectangle();
}

Для вашей подпрограммы DrawSubItem убедитесь, что вы не рисуете в первом столбце, и я добавил подпрограмму DrawBackground(). Я добавил немного отсечения в прямоугольник выделения, чтобы он не рисовал вне параметров столбца.

private void listView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) {
  if (e.ColumnIndex > 0) {
    e.DrawBackground();

    string searchTerm = "Term";
    int index = e.SubItem.Text.IndexOf(searchTerm);

    if (index >= 0) {
      string sBefore = e.SubItem.Text.Substring(0, index);

      Size bounds = new Size(e.Bounds.Width, e.Bounds.Height);
      Size s1 = TextRenderer.MeasureText(e.Graphics, sBefore, this.Font, bounds);
      Size s2 = TextRenderer.MeasureText(e.Graphics, searchTerm, this.Font, bounds);

      Rectangle rect = new Rectangle(e.Bounds.X + s1.Width, e.Bounds.Y, s2.Width, e.Bounds.Height);

      e.Graphics.SetClip(e.Bounds);
      e.Graphics.FillRectangle(new SolidBrush(Color.Yellow), rect);
      e.Graphics.ResetClip();
    }

    e.DrawText();
  }
}

В общем, владелец, рисующий элемент управления ListView, радушен в мире боли. Вы больше не рисуете в визуальных стилях, вам придется делать это и самим. Тьфу.

2 голосов
/ 05 ноября 2013

Изменен выбранный цвет спины.По умолчанию синий в окнах.Этот код поможет вам в любых цветах:

    private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
    {
        e.DrawBackground(); 
         if (e.Item.Selected) 
        {
            e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(250, 194, 87)), e.Bounds); 
        } 
        e.Graphics.DrawString(e.Item.Text, new Font("Arial", 10), new SolidBrush(Color.Black), e.Bounds); 

    }

    private void Form1_Load(object sender, EventArgs e)
    {
        for (int ix = 0; ix < listView1.Items.Count; ++ix)
        {
            var item = listView1.Items[ix];
            item.BackColor = (ix % 2 == 0) ? Color.Gray : Color.LightGray;

        }

    }

    private void listView1_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
    {
        e.DrawDefault = true;
    }

    }
}
0 голосов
/ 05 ноября 2013
  private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
  {
      e.DrawBackground(); 
      if (e.Item.Selected) 
      {
          e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(250, 194, 87)), e.Bounds); 
      } 
      e.Graphics.DrawString(e.Item.Text, new Font("Arial", 10), new SolidBrush(Color.Black), e.Bounds); 

  }
0 голосов
/ 01 февраля 2012

ComponentOwl недавно выпустил бесплатный компонент под названием Better ListView Express .

Он выглядит и ведет себя точно так же, как ListView, но имеет гораздо более мощные возможности для рисования владельца - вы можете точно рисовать над всеми элементами и даже отключать некоторые рисунки (например, выделение, чтобы сделать вас включенным).

...