Search code examples
c#winformslistview

How to get the correct ListViewItem under the mouse cursor?


The problem with the ListView.GetItemAt method is shown in the screenshot below.

Designer code

this.listView1 = new System.Windows.Forms.ListView();
this.SuspendLayout();
// 
// listView1
// 
this.listView1.Dock = System.Windows.Forms.DockStyle.Fill;
this.listView1.Location = new System.Drawing.Point(0, 0);
this.listView1.Name = "listView1";
this.listView1.Size = new System.Drawing.Size(717, 511);
this.listView1.TabIndex = 0;
this.listView1.UseCompatibleStateImageBehavior = false;
// 
// Form1
// 
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(717, 511);
this.Controls.Add(this.listView1);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);

Code-behind

public Form1()
{
    InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
    listView1.OwnerDraw = true;
    listView1.View = View.LargeIcon;
    listView1.DrawItem += ListView1_DrawItem;
    listView1.MouseMove += ListView1_MouseMove;

    for (int i = 1; i <= 10; ++i)
    {
        listView1.Items.Add($"item {i}", 0);
    }
}

private void ListView1_MouseMove(object sender, MouseEventArgs e)
{
    ListViewItem item = listView1.GetItemAt(e.X, e.Y);

    if (LastHoveredItem != null)
    {
        ListViewItem item2 = LastHoveredItem;
        LastHoveredItem = null;
        listView1.Invalidate(item2.Bounds);
    }

    if (item != null)
    {
        LastHoveredItem = item;
        listView1.Invalidate(item.Bounds);
    }
    else
    {
        LastHoveredItem = null;
    }
}

private void ListView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
    if (LastHoveredItem == e.Item)
    {
        e.Graphics.FillRectangle(Brushes.Yellow, e.Item.Bounds);
    }
    else
    {
        e.Graphics.FillRectangle(Brushes.Green, e.Item.Bounds);
    }
}

internal ListViewItem LastHoveredItem = null;

Screenshot

The rectangle under the mouse cursor should be yellow, but it is green like the others that are not hovered.

screenshot


Solution

  • ListView.GetItemAt gets the item if the label or icon rectangle of the icon contains the point. It's not what you are looking for. You need to check the entire item.

    You can use either of the following methods:

    Example - Detect hot item in DrawItem

    private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
    {
        //...
        var p = listView1.PointToClient(Cursor.Position);
        if (e.Item.GetBounds(ItemBoundsPortion.Entire).Contains(p))
        {
            //e.Item is Hot
        }
        else
        {
            //e.Item is Normal
        }
        //...
    }