You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现自绘ListViewItem悬停差异化绘制并解决相关异常问题?

解决自绘ListView鼠标悬停差异化绘制的问题

咱们来一步步拆解你遇到的这几个问题,然后给出修复后的完整代码:

问题根源分析

  • MouseMove触发不及时:快速移动鼠标时,部分项的区域可能没被MouseMove事件覆盖,导致旧的悬停项状态没被重置,出现多个项同时显示悬停效果的情况。
  • 重绘次数过多:你在DrawItem方法里调用了RedrawItems,这会触发新一轮的DrawItem事件,形成循环调用,直接导致重绘过载。
  • State不包含Hot状态:当ListView设置为OwnerDraw = true时,控件默认不会自动维护Hot(悬停)状态,需要我们手动跟踪当前鼠标悬停的项。

修复方案

我们需要做这几个关键调整:

  • 仅在悬停项发生变化时才触发重绘,避免无意义的重复绘制
  • 移除DrawItem里的重绘调用,彻底杜绝循环重绘
  • 添加MouseLeave事件处理,确保鼠标完全离开ListView时清空悬停标记
  • 优化悬停状态判断逻辑,基于我们主动跟踪的变量,而非每次实时获取鼠标位置

修复后的完整代码

private ListViewItem _lastHoveredItem;

private void Form1_Load(object sender, EventArgs e)
{
    listView1.OwnerDraw = true;
    listView1.View = View.LargeIcon;
    listView1.DrawItem += ListView1_DrawItem;
    listView1.MouseMove += ListView1_MouseMove;
    // 新增MouseLeave事件,处理鼠标离开控件的情况
    listView1.MouseLeave += ListView1_MouseLeave;

    // 填充测试数据
    for (int i = 1; i <= 6; ++i)
    {
        listView1.Items.Add($"item {i}", 0);
    }
}

private void ListView1_MouseMove(object sender, MouseEventArgs e)
{
    var currentHoverItem = listView1.GetItemAt(e.X, e.Y);
    
    // 只有当前悬停项和上次记录的项不同时,才执行重绘操作
    if (currentHoverItem != _lastHoveredItem)
    {
        // 重绘旧的悬停项,恢复默认样式
        if (_lastHoveredItem != null)
        {
            listView1.RedrawItems(_lastHoveredItem.Index, _lastHoveredItem.Index, false);
        }
        
        // 重绘新的悬停项,显示悬停样式
        if (currentHoverItem != null)
        {
            listView1.RedrawItems(currentHoverItem.Index, currentHoverItem.Index, false);
        }
        
        // 更新上次悬停项的记录
        _lastHoveredItem = currentHoverItem;
    }
}

private void ListView1_MouseLeave(object sender, EventArgs e)
{
    // 鼠标离开ListView时,清空悬停标记并重绘对应项
    if (_lastHoveredItem != null)
    {
        listView1.RedrawItems(_lastHoveredItem.Index, _lastHoveredItem.Index, false);
        _lastHoveredItem = null;
    }
}

private void ListView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
    // 直接通过跟踪的变量判断当前项是否处于悬停状态
    bool isHot = _lastHoveredItem != null && _lastHoveredItem.Index == e.Item.Index;

    // 根据悬停状态绘制背景
    if (isHot)
    {
        e.Graphics.FillRectangle(Brushes.Green, e.Bounds);
    }
    else
    {
        // 绘制默认背景,可替换为ListView的背景色
        e.Graphics.FillRectangle(Brushes.White, e.Bounds);
    }

    // 绘制文本,如需绘制图标可调用e.DrawImage()
    e.DrawText();
}

关键改进点说明

  • 杜绝循环重绘:把重绘逻辑完全移到MouseMove和MouseLeave事件中,DrawItem只负责根据当前状态完成绘制,不再触发新的重绘请求。
  • 精准控制重绘:只有当悬停项发生变化时,才重绘旧项和新项,大幅减少不必要的绘制操作。
  • 完善边界处理:添加MouseLeave事件,确保鼠标离开ListView后,悬停状态能被正确清空,不会残留悬停样式。
  • 稳定的状态判断:依赖主动跟踪的_lastHoveredItem判断悬停状态,比每次实时获取鼠标位置更可靠,也避免了重复计算。

这样调整后,你遇到的多个项同时悬停、重绘过多的问题都能得到解决,悬停效果的显示也会更稳定。

内容的提问来源于stack exchange,提问作者silviubogan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 06:28:16