【问题标题】:ContextMenuStrip Requires Two Right Clicks to DisplayContextMenuStrip 需要两次右键才能显示
【发布时间】:2026-02-02 19:55:02
【问题描述】:

我喜欢以编程方式创建我的上下文菜单。我通常不会在 contextmenustrip 打开之前将项目添加到它,因为显示的项目取决于设计的其他可变方面。

我发现 contextmenustrips 似乎需要两次右键才能显示。我尝试在不同的事件(打开、打开等)中添加菜单项,并手动将 contextmenustrip 的可见性设置为 true 无济于事。

我终其一生都无法弄清楚为什么需要单击两次右键。如果您创建一个空白的 winforms 项目,然后用它替换所有代码,它将证明问题。


Public Class Form1
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        Dim currContextMenuStrip As New ContextMenuStrip
        Me.ContextMenuStrip = currContextMenuStrip
        AddHandler currContextMenuStrip.Opening, AddressOf ContextMenuStrip1_Opening
    End Sub

    Private Sub ContextMenuStrip1_Opening(sender As Object, e As CancelEventArgs)
        Dim currContextMenuStrip As ContextMenuStrip = sender
        Dim menuTxt As String = "&Find"
        'only add the menu if it doesn't already exist
        If (From f In currContextMenuStrip.Items Where f.text = menuTxt).Count = 0 Then
            Dim newMenuItem As New ToolStripMenuItem
            newMenuItem.Text = menuTxt
            currContextMenuStrip.Items.Add(newMenuItem)
        End If
    End Sub
End Class

编辑:刚刚发现它似乎与第一次右键单击时 contextmenustrip 没有任何项目的事实有关。如果我添加一个虚拟项目,然后在添加其他项目后将其隐藏,它在第一次右键单击时起作用。好迷茫!

这行得通:

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        Dim currContextMenuStrip As New ContextMenuStrip
        Me.ContextMenuStrip = currContextMenuStrip
        AddHandler currContextMenuStrip.Opening, AddressOf ContextMenuStrip1_Opening

        'add a dummy item
        Dim newMenuItem As New ToolStripMenuItem
        newMenuItem.Text = "dummy"
        currContextMenuStrip.Items.Add(newMenuItem)
    End Sub

    Private Sub ContextMenuStrip1_Opening(sender As Object, e As CancelEventArgs)
        Dim currContextMenuStrip As ContextMenuStrip = sender
        Dim menuTxt As String = "&Find"
        'only add the menu if it doesn't already exist
        If (From f In currContextMenuStrip.Items Where f.text = menuTxt).Count = 0 Then
            Dim newMenuItem As New ToolStripMenuItem
            newMenuItem.Text = menuTxt
            currContextMenuStrip.Items.Add(newMenuItem)
        End If

        'hide the dummy item
        Dim items As List(Of ToolStripMenuItem) = (From f As ToolStripMenuItem In currContextMenuStrip.Items Where f.Text = "dummy").ToList
        items.First.visible = False
    End Sub
End Class

【问题讨论】:

  • 你应该设置Option Strict On,解决出现的问题,然后描述这段代码的作用:你有一个新声明的(empty)ContextMenuStrip,然后即时附加一个新的 ToolStripMenuItem(在显示 empty 菜单时,顺便说一句,没有任何用于选择的事件处理程序)。当然,下次显示 ContextMenuStrip 时会显示 MenuItem。
  • Option Strict 显然是Off,因为您到处都有后期绑定。当然你不能这样做:Dim currContextMenuStrip As ContextMenuStrip = sender(你不应该)使用选项On。这是处理 ContextMenuStrip 的一种非常奇怪的方式。你真的需要在每次打开菜单时检查你是否添加了那些 MenuItems 吗?为什么不设置一个根据您设置的条件添加和删除 MenuItems 的过程(如果设置了条件,从这段代码中不清楚为什么此时要添加 MenuItem)
  • “好困惑”。我似乎很清楚发生了什么。这是一个优化。假设您有一个盒子,并且您知道盒子是空的。如果有人让你把盒子里的所有东西都倒在桌子上,你会经历打开盒子然后把它倒过来的游戏,结果什么都没有掉出来吗?当然不是。你只是告诉他们盒子是空的,什么也不做。这就是这里发生的事情。系统知道菜单是空的,没有什么可显示的,所以它不会尝试。
  • “有点过分热心”。可能是这样。也许这是该优化的意外结果,但这不会改变意图或实施。你几乎已经找到了你的解决方法,所以去吧。如果这不方便,因为这意味着您不能简单地测试计数,那么还有很多其他选择。您可以只使用 Boolean 字段来指示菜单是否已初始化,或者您可以派生自己的自定义类,例如自动添加一个虚拟项目并设置一个只读标志,当从外部添加项目时该标志会重置。
  • 建议: 在设计时或Load 事件中添加您需要的所有项目,然后在Opening 事件中根据当前条件切换可见性。或者,在设计时添加默认项,然后返回代码中应替换 ContextMenuStrip 的项并从那里添加/删除。应该在某些地方执行此操作,例如 SelectedIndexChanged 事件,或者选定的选项卡已更改...等。没有明确的上下文告诉你在哪里。除了Opening 事件之外的其他地方。

标签: vb.net winforms contextmenustrip


【解决方案1】:

如果您真的需要这样做,一种选择是创建您自己的自定义ContextMenuStrip,该ContextMenuStrip 说明没有项目时的行为以及对虚拟项目的要求。我使用了这段代码:

Imports System.ComponentModel

Public Class Form1

    Private WithEvents menu As New ContextMenuStrip

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ContextMenuStrip = menu
    End Sub

    Private Sub menu_Opening(sender As Object, e As CancelEventArgs) Handles menu.Opening
        If menu.Items.Count = 0 Then
            menu.Items.AddRange({New ToolStripMenuItem("First"),
                                 New ToolStripMenuItem("Second"),
                                 New ToolStripMenuItem("Third")})
        End If
    End Sub

    Private Sub menu_ItemClicked(sender As Object, e As ToolStripItemClickedEventArgs) Handles menu.ItemClicked
        MessageBox.Show(e.ClickedItem.Text)
    End Sub

End Class

并看到您描述的相同行为。然后我定义了这个类:

Public Class ContextMenuStripEx
    Inherits ContextMenuStrip

    Private dummyItem As ToolStripItem

    Public ReadOnly Property IsInitialised As Boolean
        Get
            Return dummyItem Is Nothing
        End Get
    End Property

    Public Sub New()
        dummyItem = Items.Add(CStr(Nothing))
    End Sub

    ''' <inheritdoc />
    Protected Overrides Sub OnItemAdded(e As ToolStripItemEventArgs)
        If Not IsInitialised Then
            Items.Remove(dummyItem)
            dummyItem = Nothing
        End If

        MyBase.OnItemAdded(e)
    End Sub

End Class

并将我的表单代码更改为:

Imports System.ComponentModel

Public Class Form1

    Private WithEvents menu As New ContextMenuStripEx

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ContextMenuStrip = menu
    End Sub

    Private Sub menu_Opening(sender As Object, e As CancelEventArgs) Handles menu.Opening
        If Not menu.IsInitialised Then
            menu.Items.AddRange({New ToolStripMenuItem("First"),
                                 New ToolStripMenuItem("Second"),
                                 New ToolStripMenuItem("Third")})
        End If
    End Sub

    Private Sub menu_ItemClicked(sender As Object, e As ToolStripItemClickedEventArgs) Handles menu.ItemClicked
        MessageBox.Show(e.ClickedItem.Text)
    End Sub

End Class

它按预期工作。请注意,最后一个代码 sn-p 使用自定义类型及其自定义属性。

【讨论】:

    【解决方案2】:

    感谢所有帮助和建议!我最终决定在 Designer 中构建菜单超集,然后在运行时显示/隐藏。每次右键单击然后每次重建菜单可能更快。

    【讨论】:

      【解决方案3】:

      Microsoft 有旧式和新式上下文菜单。 Popup 事件用于旧式上下文菜单,它接收一个普通的 EventArgs 对象。新的上下文菜单使用接收 CancelEventArgs 对象的 Opening 事件。如果 currContextMenuStrip.Items 不包含任何项目,则 e.Cancel 将在调用事件处理程序时设置为 True(这会导致您遇到的问题)。解决方法是添加菜单项,然后将 e.Cancel 设置为 False。之后应该可以正常显示。为了确保确实添加了项目,可以使用 if 语句保护 e.Cancel 的分配,如下所示:

      If currContextMenuStrip.Items.Count <> 0 Then
         e.Cancel = False
      End If
      

      【讨论】:

        最近更新 更多