Default implementation for ListView OwnerDraw

ε祈祈猫儿з 提交于 2019-11-28 00:01:52

问题


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

回答1:


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.




回答2:


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.




回答3:


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

    }
}



回答4:


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).




回答5:


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

  }


来源:https://stackoverflow.com/questions/9066408/default-implementation-for-listview-ownerdraw

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!