Joan Venge
Joan Venge

Reputation: 330952

Winforms ListView Selection Drawing?

Is it possible to override the listview detault selection paint? The one that looks semi-transparent blue overlayed over the items, like in the explorer windows.

I want to draw an outline around the selection to indicate selection.

Any way to do this? Examples are appreciated.

Upvotes: 0

Views: 3987

Answers (3)

Grammarian
Grammarian

Reputation: 6882

.NET ListView supports owner drawing much more directly than the other answers suggest. You don't even need to subclass. Set OwnerDraw to true, listen for DrawSubItem event, and then in that event you can draw what you like.

As always, ObjectListView makes this process easier. There is this page documenting exacting how to do this. You can garish things like this if you are feeling mean to your users: alt text

HOWEVER, none of these techniques will work if you want to draw something outside the bounds of the cell itself. So, if you were hoping to draw a selection outline around the whole row that overlapped the previous and subsequent rows, you cannot do this through owner drawing. Each cell is drawn individually and "owns" its part of the screen, wiping out anything that was already there.

To do something like you are asking, you would have to intercept the postpaint stage of the custom draw (not owner draw. Michael Dunn wrote a great introduction to custom drawing for CodeProject). You can read what is required here.

I hate to say it, but the simplest answer to use an ObjectListView, create a Decoration and install it:

public void InitializeSelectionOverlay()
{
    this.olv1.HighlightForegroundColor = Color.Black;
    this.olv1.HighlightBackgroundColor = Color.White;
    this.olv1.AddDecoration(new SelectedRowDecoration());
}

public class SelectedRowDecoration : IOverlay
{
    public void Draw(ObjectListView olv, Graphics g, Rectangle r) {
        if (olv.SelectedIndices.Count != 1)
            return;

        Rectangle rowBounds = olv.GetItem(olv.SelectedIndices[0]).Bounds;
        rowBounds.Inflate(0, 2);
        GraphicsPath path = this.GetRoundedRect(rowBounds, 15);
        g.DrawPath(new Pen(Color.Red, 2.0f), path);
    }

    private GraphicsPath GetRoundedRect(RectangleF rect, float diameter) {
        GraphicsPath path = new GraphicsPath();

        RectangleF arc = new RectangleF(rect.X, rect.Y, diameter, diameter);
        path.AddArc(arc, 180, 90);
        arc.X = rect.Right - diameter;
        path.AddArc(arc, 270, 90);
        arc.Y = rect.Bottom - diameter;
        path.AddArc(arc, 0, 90);
        arc.X = rect.Left;
        path.AddArc(arc, 90, 90);
        path.CloseFigure();

        return path;
    }
}

This gives something that looks like this:alt text

Upvotes: 3

Stan R.
Stan R.

Reputation: 16065

Here is a quick working example, i was messing around with.

First helper structs and enums.

  [StructLayout(LayoutKind.Sequential)]
    public struct DRAWITEMSTRUCT
    {
        public int CtlType;
        public int CtlID;
        public int itemID;
        public int itemAction;
        public int itemState;
        public IntPtr hwndItem;
        public IntPtr hDC;
        public RECT rcItem;
        public IntPtr itemData;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
        public int Width
        {
            get { return right - left; }
        }
        public int Height
        {
            get { return bottom - top; }
        }
    }

    public enum ListViewDefaults
    {
        LVS_OWNERDRAWFIXED = 0x0400
    }

    public enum WMDefaults
    {
        WM_DRAWITEM = 0x002B,
        WM_REFLECT = 0x2000
    }

Now create a custom ListView and Override CreateParams and WndProc

public class CustomListView : ListView
    {
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                //add OwnerDraw style...i took this idea from Reflecting against ListView
                // bit OR is very important, otherwise you'll get an exception
                cp.Style |= (int)ListViewDefaults.LVS_OWNERDRAWFIXED; 

                return cp;
            }
        }

        protected override void WndProc(ref Message m)
        {

            base.WndProc(ref m);

            //if we are drawing an item then call our custom Draw.
            if (m.Msg == (int)(WMDefaults.WM_REFLECT | WMDefaults.WM_DRAWITEM))
                   ProcessDrawItem(ref m);
        }

Now for the most important part..the drawing. I am pretty amateur at drawing but this should get you an idea of what to do.

 private void ProcessDrawItem(ref Message m)
        {
            DRAWITEMSTRUCT dis = (DRAWITEMSTRUCT)Marshal.PtrToStructure(m.LParam, typeof(DRAWITEMSTRUCT));
            Graphics g = Graphics.FromHdc(dis.hDC);
            ListViewItem i = this.Items[dis.itemID];

            Rectangle rcItem = new Rectangle(dis.rcItem.left, dis.rcItem.top, this.ClientSize.Width, dis.rcItem.Height);
            //we have our rectangle.
            //draw whatever you want
            if (dis.itemState == 17)
            {
                //item is selected
                g.FillRectangle(new SolidBrush(Color.Red), rcItem);
                g.DrawString(i.Text, new Font("Arial", 8), new SolidBrush(Color.Black), new PointF(rcItem.X, rcItem.Y+1));
            }
            else
            {
                //regular item
                g.FillRectangle(new SolidBrush(Color.White), rcItem);
                g.DrawString(i.Text, new Font("Arial", 8), new SolidBrush(Color.Black), new PointF(rcItem.X, rcItem.Y+1));
            }

            //we have handled the message
            m.Result = (IntPtr)1;
        }

This is the result.

alt text

Upvotes: 1

Dustin Hoffman
Dustin Hoffman

Reputation: 98

My first thought would be to subclass the ListView control, set OwnerDraw to true and perform all of the drawing yourself, but that seems like overkill for such a small change.

However, in my wanderings of the web I found this article, which may be helpful, as it's very similar to your situation and allows you to avoid drawing everything yourself.

Upvotes: 1

Related Questions