如何实现自绘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
相关产品推荐
相关产品推荐

