ListView OwnerDraw 的默认实现

发布于 2025-01-01 06:53:50 字数 2183 浏览 0 评论 0原文

我有一个 ListView 我想调整项目的绘制(例如突出显示列表视图项目中的某些字符串),但是我不想从根本上改变项目的显示方式。

我已将 OwnerDraw 设置为 true并且可以让我了解如何绘制突出显示效果,但是每当我尝试遵循默认实现来绘制列表视图项的其余部分时,就会出现问题,并且我会留下一大堆图形问题,表明实际上我已经完全出了问题。

是否有地方可以看到 DrawItemDrawSubItem 事件这样做以便我可以更好地理解并更轻松地调整我的代码?

作为参考,这里是一个显示我当前正在做的事情的片段:

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();
}

I have a ListView where I wish to tweak the drawing of items (for example highlighting certain strings in list view itmes), however I don't want to radically alter the way that items are displayed.

I have set the OwnerDraw to true and can get my head around how to draw my highlighting effect, however whenever I try to defer to the default implementation to draw the rest of the list view item things go wrong and I'm left with a whole load of graphical problems indicating that actually I've completely gone wrong.

Is there somewhere that I can see what the "Default" handlers for the DrawItem and DrawSubItem events do so that I can better my understanding and more easily tweak my code?

For reference here is a snippet showing what I'm currently doing:

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();
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

黑色毁心梦 2025-01-08 06:53:50

我现在没有时间写出完整的答案,所以我会记下一些快速笔记并稍后再回来。

正如 LarsTech 所说,所有者绘制 ListView 控件是一件痛苦的事情 - .Net ListView 类是底层 Win32 列表视图控件 以及“所有者抽奖”由 NM_CUSTOMDRAW 通知代码。因此,不存在“默认 .Net 实现”——默认是使用底层 Win32 控件。

为了让生活变得更加困难,需要考虑一些额外的因素:

  • 正如 LarsTech 指出的那样,第一个子项实际上代表父项本身,因此如果您在 DrawItem 和 < code>DrawSubItem 您很可能将第一个单元格的内容绘制两次。
  • 基础列表视图控件中存在错误(记录在 此页面),这意味着将发生 DrawItem 事件,而没有相应的 DrawSubItem 事件,这意味着如果您在DrawItem 事件,然后在 DrawSubItem 事件中绘制文本,当您将鼠标悬停在项目上时,项目文本将消失。
  • 默认情况下,某些渲染似乎也不是双缓冲的
  • 我还注意到 ItemState 属性并不总是正确的,例如在调整列大小后。因此我发现最好不要依赖它。
  • 您还需要确保文本不会拆分为多行,否则您将看到下一行的顶部几个像素呈现在单元格的底部。
  • 在渲染第一个单元格时还需要特别考虑,以考虑本机列表视图使用的额外填充。
  • 由于 DrawItem 事件首先发生,因此您在 DrawItem 处理程序中绘制的任何内容(例如选择效果)很可能会被您在 DrawSubItem 中执行的操作覆盖code> 处理程序(例如,某些单元格具有不同的背景颜色)。

总而言之,处理所有者绘图是一件相当复杂的事情 - 我发现最好在 DrawSubItem 事件中处理所有绘图,最好通过以下方式执行您自己的双缓冲使用 BufferedGraphics 类。

我还发现查看 ObjectListView 的源代码非常方便。

最后,所有这些只是为了处理列表视图的详细信息模式(我正在使用的唯一模式),如果您希望其他模式也能工作,那么我相信还有一些额外的事情需要考虑。

当我有机会时,我会尝试发布我的工作示例代码。

I haven't got the time now to write up a complete answer so instead I'll put down some quick notes and come back to it later.

As LarsTech said, owner drawing a ListView control is a pain - the .Net ListView class is a wrapper around the underlying Win32 List View Control and the ability to "Owner draw" is provided by the NM_CUSTOMDRAW notification code. As such there is no "default .Net implementation" - the default is to use the underlying Win32 control.

To make life even more difficult there are a number of extra considerations to make:

  • As LarsTech pointed out, the first subitem in fact represents the parent item itself, and so if you handle rendering in both DrawItem and DrawSubItem you may well be drawing the contents of the first cell twice.
  • There is a bug in the underlying list view control (documented on the note on this page) that means that a DrawItem event will occur without corresponding DrawSubItem events, meaning that if you draw a background in the DrawItem event and then draw the text in the DrawSubItem event your item text will disappear when you mouse over.
  • Some of the rendering also appears to not be double-buffered by default
  • I also noticed that the ItemState property is not always correct, for example just after resizing a column. Consequently I've found its best not to rely on it.
  • You also need to make sure that your text doesn't split over multiple lines, else you will see the top few pixels of the lower line being rendered at the bottom of the cell.
  • Also special consideration needs to be given when rendering the first cell to take account of extra padding that the native list view uses.
  • Because the DrawItem event occurs first, anything you draw in the DrawItem handler (e.g. the selection effect) may well be overlayed by things you do in the DrawSubItem handler (e.g. having certain cells with a different background color).

All in all handling owner drawing is a fairly involved affair - I found it best to handle all drawing inside the DrawSubItem event, its also best to perform your own double-buffering by using the BufferedGraphics class.

I also found looking at the source code for ObjectListView very handy.

Finally, all of this is just to handle the details mode of the list view (the only mode I am using), if you want the other modes to work too then I believe that there are extra things to take account of.

When I get a chance I'll try and post my working example code.

南冥有猫 2025-01-08 06:53:50

我不知道这是否完全对您有帮助,但我会添加一些注释:

需要记住的一件事是 DrawSubItem 也会绘制第一个项目,这可能就是您的位置正在获得双重渲染外观。

一些需要尝试的事情(不考虑速度):

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 控件的所有者在一个受伤的世界中是受欢迎的。您不再使用视觉样式进行绘图,您也必须自己这样做。啊。

I don't know if this will completely help you, but I'll add a few notes:

One thing to keep in mind is that DrawSubItem will draw the first item, too, and that's probably where you are getting the double-rendered look from.

Some things to try (not factored for speed):

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();
}

For your DrawSubItem routine, make sure you aren't drawing in the first column and I added the DrawBackground() routine. I added some clipping to the highlight rectangle so it wouldn't paint outside the column parameters.

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();
  }
}

In general, owner drawing a ListView control is welcoming in a world of hurt. You aren't drawing in Visual Styles anymore, you would have to do that yourself, too. Ugh.

未央 2025-01-08 06:53:50

所选项目的背景颜色已更改。 Windows 中默认为蓝色。此代码将对您在任何颜色下​​都有帮助:

    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;
    }

    }
}

Item selected back color changed. In by default blue in windows. This code will help for u in any colors:

    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;
    }

    }
}
墨洒年华 2025-01-08 06:53:50

ComponentOwl recently released a freeware component called Better ListView Express.

It looks and behaves exactly like the ListView, but has much more powerful owner drawing capabilities - you can draw accurately over all elements and even turn off some drawing (e.g. selection to make you on).

就是爱搞怪 2025-01-08 06:53:50
  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 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); 

  }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文