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

WinForms ListView控件原生显示不同上下文菜单的实现方案

WinForms ListView 按选中项切换上下文菜单解决方案

方法一:利用ContextMenuStrip的Opening事件(你的思路完全可行)

这个方案无需单独处理鼠标/键盘事件,仅通过绑定一个临时菜单,在其打开前动态切换目标菜单:

  1. 给ListView绑定一个临时ContextMenuStrip(比如命名为mainContextMenu)
  2. 在mainContextMenu的Opening事件中执行以下逻辑:
    • 取消当前菜单的默认打开行为
    • 根据ListView.SelectedItems.Count判断,选择对应菜单
    • 区分鼠标/键盘触发场景,在正确位置显示目标菜单

代码示例:

private void MainContextMenu_Opening(object sender, CancelEventArgs e)
{
    // 取消当前菜单的默认打开流程
    e.Cancel = true;

    ContextMenuStrip targetMenu = SelectedItems.Count > 0 
        ? menuWithSelectedItems  // 有选中项时显示的菜单
        : menuWithoutSelectedItems; // 无选中项时显示的菜单

    if (Control.MouseButtons == MouseButtons.Right)
    {
        // 鼠标右键触发:用当前鼠标位置转换为控件内坐标
        targetMenu.Show(this, PointToClient(Cursor.Position));
    }
    else
    {
        // 键盘(Shift+F10/上下文键)触发:优先显示在选中项位置,无选中项则显示在控件中心
        if (SelectedItems.Count > 0)
        {
            var itemRect = GetItemRect(SelectedIndices[0]);
            targetMenu.Show(this, itemRect.Location);
        }
        else
        {
            targetMenu.Show(this, new Point(Width / 2, Height / 2));
        }
    }
}

方法二:重写ListView的WndProc(控件级别原生处理)

通过拦截Windows原生消息WM_CONTEXTMENU(消息码0x007B),统一处理所有上下文菜单触发场景,属于最底层的控件级解决方案:

  1. 继承ListView类,重写WndProc方法
  2. 捕获WM_CONTEXTMENU消息时,判断选中项状态
  3. 选择对应菜单并在正确位置显示,跳过默认菜单处理

代码示例:

public class CustomListView : ListView
{
    public ContextMenuStrip MenuWithSelectedItems { get; set; }
    public ContextMenuStrip MenuWithoutSelectedItems { get; set; }

    protected override void WndProc(ref Message m)
    {
        const int WM_CONTEXTMENU = 0x007B;
        if (m.Msg == WM_CONTEXTMENU)
        {
            ContextMenuStrip targetMenu = SelectedItems.Count > 0 
                ? MenuWithSelectedItems 
                : MenuWithoutSelectedItems;

            if (targetMenu != null)
            {
                // 解析消息中的鼠标位置(鼠标触发时有效)
                var x = (int)(m.LParam.ToInt64() & 0xFFFF);
                var y = (int)((m.LParam.ToInt64() >> 16) & 0xFFFF);
                
                if (x == -1 && y == -1)
                {
                    // 键盘触发场景
                    if (SelectedItems.Count > 0)
                    {
                        var itemRect = GetItemRect(SelectedIndices[0]);
                        targetMenu.Show(this, itemRect.Location);
                    }
                    else
                    {
                        targetMenu.Show(this, new Point(Width / 2, Height / 2));
                    }
                }
                else
                {
                    // 鼠标触发场景:转换为控件内坐标
                    targetMenu.Show(this, PointToClient(new Point(x, y)));
                }
                // 跳过基类的默认处理,避免显示绑定的默认菜单
                return;
            }
        }
        // 其他消息交给基类处理
        base.WndProc(ref m);
    }
}

方案对比

  • 方法一:实现简单,无需自定义控件,适合已有ListView的快速改造,覆盖所有触发方式
  • 方法二:更原生,逻辑集中在控件内部,适合需要封装成通用自定义控件的场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 04:57:21