0

I am setting the ForeColor of all items in my ListView to a different color, but this get's overrided when the item is selected (changes to Black again; changes back to custom color on deselection).

I want my items to retain my custom color, even in selection.

I'm basically asking the same question that was asked here 7 years ago, and doesn't seem to have any satisfactory answer.

I tried searching in SO and elsewhere, and no luck. The only solution provided so far is to draw the whole thing (the DrawItem method), which I gave a try but is ridiculously complicated for such a petty requirement...

Is this the only way? Say it ain't so.

Jimi
  • 29,621
  • 8
  • 43
  • 61
Maverick Meerkat
  • 5,737
  • 3
  • 47
  • 66
  • 1
    Because of your requirement `even in selection` you will need to do a custom DrawItem method. Reason being is to override the Highlight ForeColor property when the highlighted item is drawn. Sorry, No way around it that I know – JayV Mar 22 '18 at 16:01
  • 1
    Actually it is not that complicated. There are default drawing routines you cam use. What Viewmode is the LV in? Also: The user expects all selections to be highlighted in systemcolors. – TaW Mar 22 '18 at 16:03
  • 2
    `Say it ain't so.` It is so. – LarsTech Mar 22 '18 at 16:06
  • 1
    `using (SolidBrush br = new SolidBrush(e.Item.ForeColor)) if (e.Item.Selected) e.Graphics.DrawString(e.Item.Text, e.Item.Font, br, e.Bounds.X + 4, e.Bounds.Y); else e.DrawText();` - To make the selection stick out you could use a bold font.. – TaW Mar 22 '18 at 16:18
  • 1
    It's two lines of code. Four, if you change ForeColor/BackColor on Selected. However, there can be some overhead to accomodate the logic behind *all items in my ListView have a different color*. But, if this logic is pre-defined, the overhead is very low. – Jimi Mar 22 '18 at 16:19
  • @TaW I need to support all views (tiles, thumbnails, list, details, etc), and each item consists of image and text (Icons for each folder/file + name of the file) so I have to compensate for that with positioning... If I use your code and pinpoint it in the details view, will it mess other views ? – Maverick Meerkat Mar 22 '18 at 20:04
  • The code will probably not work too well for other views. One would have to test it, though and adapt it. – TaW Mar 22 '18 at 20:08

1 Answers1

3

Enable your ListView OwnerDraw mode, then subscribe its DrawItem and DrawColumnHeader events.
If your design requires it, also subcribe the DrawSubitem event.

At this point, you can draw anything in the related areas of your ListView.

In the example, I've painted a little symbol in the Header area.
The Header text needs to be painted too.

If the Background color doesn't change (same as in design mode), you just need to use the DrawListViewItemEventArgs e parameter function e.DrawBackground();

If not, use e.Graphics.FillRectangle() to color the Item area, defined by e.Bounds.

The Item Text is drawn using e.Graphics.DrawString().
The item Text is e.Item.Text, the text area is defined by e.Bounds again.
If you don't need any specific details/settings for the item's text, you can simply use e.DrawText();, which uses the default properties (defined at design-time).

Here, the item color complex logic is that the color is specified inside the item text. Could be anything else. The item tag, its Index position, a List<Parameters>, you name it.

This is how it might look like:
(I added e.Graphics.TextRenderingHint = [] to show how you can control the quality of the rendered text. e.Graphics.TextContrast can be also used to enhance the contrast).

ListView Colored Items

Note: this code sample only draws a generic image, if the ListView has an ImageList. You should also verify whether the SmallIcon/LargeIcon ImageLists are defined and draw the related Image in the specified size. It's the same procedure, though.

protected void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
    e.Item.UseItemStyleForSubItems = true;
    int imageOffset = 0;
    Rectangle rect = e.Item.Bounds;
    bool drawImage = !(e.Item.ImageList is null);
    Color itemColor = Color.FromName(e.Item.Text.Substring(e.Item.Text.LastIndexOf(" ") + 1));
    using (var format = new StringFormat(StringFormatFlags.FitBlackBox)) {
        format.LineAlignment = StringAlignment.Center;

        if (drawImage) {
            imageOffset = e.Item.ImageList.ImageSize.Width + 1;
            rect.Location = new Point(e.Bounds.X + imageOffset, e.Item.Bounds.Y);
            rect.Size = new Size(e.Bounds.Width - imageOffset, e.Item.Bounds.Height);
            e.Graphics.DrawImage(e.Item.ImageList.Images[e.Item.ImageIndex], e.Bounds.Location);
        }

        if (e.Item.Selected) {
            using (var bkgrBrush = new SolidBrush(itemColor))
            using (var foreBrush = new SolidBrush(e.Item.BackColor)) {
                e.Graphics.FillRectangle(bkgrBrush, rect);
                e.Graphics.DrawString(e.Item.Text, e.Item.Font, foreBrush, rect, format);
            }
            e.DrawFocusRectangle();
        }
        else {
            //e.DrawDefault = true;
            using (var foreBrush = new SolidBrush(itemColor)) {
                e.Graphics.DrawString(e.Item.Text, e.Item.Font, foreBrush, rect, format);
            }
        }
    }
}

// Draws small symbol in the Header beside the normal Text
protected void listView1_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
    e.DrawBackground();
    string extra = (e.ColumnIndex == 1) ? (char)32 + "\u2660" + (char)32 : (char)32 + "\u2663" + (char)32;
    using (var brush = new SolidBrush(e.ForeColor)) { 
        e.Graphics.DrawString(extra + e.Header.Text, e.Font, brush, e.Bounds, StringFormat.GenericTypographic);
    }
}
Jimi
  • 29,621
  • 8
  • 43
  • 61