将键盘快捷键添加到系统菜单

时间:2018-07-05 10:46:37

标签: vb.net winforms

我正在尝试向表单的系统菜单中添加其他选项列表,并使这些选项可用作键盘快捷键。 我设法将选项添加到系统菜单,并设法抓住菜单单击这些选项。

System Menu with Shortcut List

 Dim sysmenu As IntPtr = GetSystemMenu(Me.Handle, False)
 Dim shortcutMenu As IntPtr = CreateMenu()

 InsertMenu(sysmenu, 6, MenuFlags.MF_BYPOSITION Or MenuFlags.MF_POPUP, shortcutMenu, "&Shortcut List")
 InsertMenu(shortcutMenu, 0, MenuFlags.MF_STRING, 4200, String.Format("Shortcut &2 {0}Alt + B", ControlChars.Tab))
 InsertMenu(sysmenu, 7, MenuFlags.MF_BYPOSITION Or MenuFlags.MF_SEPARATOR, 0, Nothing)

但是我找不到如何添加键盘快捷键的示例。 我正在尝试关注另一个stackoverflow问题(Delphi - Adding a shortcut to a programmatically added system menu option)的答案。  他们正在其中创建带有所需快捷方式的加速器表。但是我找不到被调用方法的签名或快捷方式的结构。

        'Creating accelerator table
        Dim listaAccel As List(Of ACCEL) = New List(Of ACCEL)()
        Dim accel As New ACCEL()
        accel.fVirt = &H10 '"ALT"
        accel.key = &H42 '"B"
        accel.cmd = 4200
        listaAccel.Add(accel)

        Dim accelPointer As Integer = Marshal.SizeOf(GetType(ACCEL))
        Dim arrayPointer As IntPtr = Marshal.AllocHGlobal(accelPointer * 1) ' * 2)
        Marshal.StructureToPtr(listaAccel(0), arrayPointer, True)
        CreateAcceleratorTable(arrayPointer, 1)

我设法在网站上找到方法签名,但是该网站没有任何其他信息,所以我不确定这是否是正确的方法。

Declare Function CreateAcceleratorTable Lib "user32" Alias "CreateAcceleratorTableA" (ByRef lpaccl As IntPtr, ByVal cEntries As Integer) As Integer

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
Public Class ACCEL
    Public fVirt As Byte
    Public key As UInt32
    Public cmd As UInt32
End Class

我觉得我已经接近解决方案,但是无法找到正确的信息。

其他信息: 我使用以下代码成功捕获了新菜单上的click事件

Protected Overloads Overrides Sub WndProc(ByRef message As Message)
    If message.Msg = WndValues.WM_SYSCOMMAND AndAlso message.WParam.ToInt32 = 4200 Then
        Dim i = 0    ' Shortcut 2 or Alt+B activated
    ElseIf message.Msg = WndValues.WM_SYSCOMMAND Then
        Dim j = 0
    End If
    MyBase.WndProc(message)
End Sub

1 个答案:

答案 0 :(得分:0)

很抱歉这么晚回复。花了一天的时间之后,我终于设法找到了解决方案。

首先,有几件事需要用代码来纠正:

  1. ACCEL应该是Structure,而不是Class

  2. ACCEL.keyACCEL.cmd都应声明为UShort,而不是UInt32

  3. 事实证明,CreateAcceleratorTable()的第一个参数应声明为ByVal lpaccel As ACCEL()ACCEL结构的数组)。

  4. 您需要使用当前1值按位或FVIRTKEY(相当于accel.fVirt)。 FVIRTKEY指定accel.key虚拟键码而不是ASCII码。后者区分大小写,前者不区分大小写。

现在,为了使加速器正常工作,您需要具有一些可处理并将其转换为SYSCOMMAND的东西。

可以使用TranslateAccelerator() function将加速器从窗口消息转换为SYSCOMMAND。每次收到窗口消息时都需要调用此方法。但是,仅在WndProc内调用它是不够的,因为仅将消息传递给焦点控件,这意味着如果表单的某个控件具有焦点,则表单本身将不会收到关键消息。

为解决此问题,我们可以在应用程序的消息泵中安装message filter。在到达任何形式或控件之前,这将拦截发送给应用程序的每个窗口消息。然后,我们只需将表单的窗口句柄传递给TranslateAccelerator(),以使其将所有生成的SYSCOMMAND消息发送给它。

最后,我们应该在表单关闭以释放加速器表之前调用DestroyAcceleratorTable()

表单代码:

'The ID of our menu item.
Const MENU_SHORTCUT2 As Integer = 4200

'A variable holding a reference to our accelerator filter.
Dim AccelMessageFilter As KeyboardAcceleratorFilter

Private Sub MainForm_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
    'Remove our message filter and destroy the accelerator table.
    Application.RemoveMessageFilter(AccelMessageFilter)
    NativeMethods.DestroyAcceleratorTable(AccelMessageFilter.AcceleratorTable)
End Sub

Private Sub MainForm_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    'Get the system menu.
    Dim SysMenu As IntPtr = NativeMethods.GetSystemMenu(Me.Handle, False)
    Dim ShortcutMenu As IntPtr = NativeMethods.CreateMenu()

    'Insert our custom menu items.
    NativeMethods.InsertMenu(SysMenu, 6, MenuFlags.MF_BYPOSITION Or MenuFlags.MF_POPUP, ShortcutMenu, "&Shortcut List")
    NativeMethods.InsertMenu(ShortcutMenu, 0, MenuFlags.MF_BYPOSITION Or MenuFlags.MF_STRING, MENU_SHORTCUT2, String.Format("Shortcut &2{0}Alt + B", ControlChars.Tab))
    NativeMethods.InsertMenu(SysMenu, 7, MenuFlags.MF_BYPOSITION Or MenuFlags.MF_SEPARATOR, 0, Nothing)

    'Create a keyboard accelerator for ALT + B.
    Dim Accel As New NativeMethods.ACCEL()
    Accel.fVirt = AcceleratorModifiers.FVIRTKEY Or AcceleratorModifiers.FALT
    Accel.key = Keys.B
    Accel.cmd = MENU_SHORTCUT2

    'Create an accelerator table.
    Dim hAccel As IntPtr = NativeMethods.CreateAcceleratorTable(New NativeMethods.ACCEL() {Accel}, 1)

    'Create our message filter.
    AccelMessageFilter = New KeyboardAcceleratorFilter(Me, hAccel, True)

    'Add the filter to the application's message pump.
    Application.AddMessageFilter(AccelMessageFilter)
End Sub

Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
    If m.Msg = WindowMessages.WM_SYSCOMMAND Then

        Select Case m.WParam.ToInt32()
            Case MENU_SHORTCUT2
                MessageBox.Show("'Shortcut 2' was pressed!")
        End Select

    End If
    MyBase.WndProc(m)
End Sub

消息过滤器:

Public Class KeyboardAcceleratorFilter
    Implements IMessageFilter

    Private _acceleratorTable As IntPtr
    Private _form As Form

    ''' <summary>
    ''' Gets the pointer to the filter's accelerator table.
    ''' </summary>
    ''' <remarks></remarks>
    Public ReadOnly Property AcceleratorTable As IntPtr
        Get
            Return _acceleratorTable
        End Get
    End Property

    ''' <summary>
    ''' Gets or sets whether accelerator messages should be intercepted by the filter, stopping them from being dispatched to the form/control.
    ''' </summary>
    ''' <remarks></remarks>
    Public Property InterceptMessages As Boolean

    ''' <summary>
    ''' Gets the form that the filter applies to.
    ''' </summary>
    ''' <remarks></remarks>
    Public ReadOnly Property Form As Form
        Get
            Return _form
        End Get
    End Property

    Public Function PreFilterMessage(ByRef m As System.Windows.Forms.Message) As Boolean Implements System.Windows.Forms.IMessageFilter.PreFilterMessage
        'Native MSG structure (required by TranslateAccelerator()).
        Dim Msg As New NativeMethods.MSG() With {.hwnd = Me.Form.Handle, .message = m.Msg, .wParam = m.WParam, .lParam = m.LParam}

        'Process accelerators (if any) and send the SYSCOMMAND messages to this filter's form (Msg.hwnd is set to Me.Form.Handle above).
        Dim Result As Integer = NativeMethods.TranslateAccelerator(Msg.hwnd, Me.AcceleratorTable, Msg)

        'Intercept the message if an accelerator was processed and Me.InterceptMessages = True.
        Return If(Result <> 0, Me.InterceptMessages, False)
    End Function

    ''' <summary>
    ''' Initializes a new instance of the KeyboardAcceleratorFilter class.
    ''' </summary>
    ''' <param name="Form">The form that the filter applies to.</param>
    ''' <param name="AcceleratorTable">The pointer to the filter's accelerator table.</param>
    ''' <param name="InterceptMessages">Whether accelerator messages should be intercepted by the filter, stopping them from being dispatched to the form/control.</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal Form As Form, ByVal AcceleratorTable As IntPtr, ByVal InterceptMessages As Boolean)
        _form = Form
        _acceleratorTable = AcceleratorTable
        Me.InterceptMessages = InterceptMessages
    End Sub
End Class

本机方法

Imports System.Runtime.InteropServices

Public NotInheritable Class NativeMethods
    Private Sub New() 'Private constructor as we're not supposed to create instances of this class.
    End Sub

#Region "WinAPI Functions"
    <DllImport("user32.dll", SetLastError:=True)> _
    Public Shared Function GetSystemMenu(ByVal hWnd As IntPtr, <MarshalAs(UnmanagedType.Bool)> ByVal bRevert As Boolean) As IntPtr
    End Function

    <DllImport("user32.dll", SetLastError:=True)> _
    Public Shared Function CreateMenu() As IntPtr
    End Function

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Ansi)> _
    Public Shared Function InsertMenu(ByVal hMenu As IntPtr, ByVal uPosition As UInteger, ByVal uFlags As MenuFlags, ByVal uIDNewItem As IntPtr, ByVal lpNewMenu As String) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("user32.dll", SetLastError:=True)> _
    Public Shared Function CreateAcceleratorTable(ByVal lpaccel As ACCEL(), ByVal cAccel As Integer) As IntPtr
    End Function

    <DllImport("user32.dll", SetLastError:=True)> _
    Public Shared Function TranslateAccelerator(ByVal hWnd As IntPtr, ByVal hAccel As IntPtr, ByRef lpMsg As MSG) As Integer
    End Function

    <DllImport("user32.dll", SetLastError:=True)> _
    Public Shared Function DestroyAcceleratorTable(ByVal hAccel As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function
#End Region

#Region "Structures"
    <StructLayout(LayoutKind.Sequential)> _
    Public Structure ACCEL
        Public fVirt As AcceleratorModifiers
        Public key As UShort
        Public cmd As UShort
    End Structure

    <StructLayout(LayoutKind.Sequential)> _
    Public Structure MSG
        Public hwnd As IntPtr
        Public message As UInteger
        Public wParam As IntPtr
        Public lParam As IntPtr
        Public time As UInteger
        Public pt As POINT
    End Structure

    <StructLayout(LayoutKind.Sequential)> _
    Public Structure POINT
        Public x As Integer
        Public y As Integer
    End Structure
#End Region

End Class

#Region "Enumerations"
<Flags()> _
Public Enum AcceleratorModifiers As Byte
    FVIRTKEY = 1
    FSHIFT = &H4
    FCONTROL = &H8
    FALT = &H10
End Enum

Public Enum MenuFlags As Integer
    MF_BYCOMMAND = &H0
    MF_BYPOSITION = &H400
    MF_BITMAP = &H4
    MF_CHECKED = &H8
    MF_DISABLED = &H2
    MF_ENABLED = &H0
    MF_GRAYED = &H1
    MF_MENUBARBREAK = &H20
    MF_MENUBREAK = &H40
    MF_OWNERDRAW = &H100
    MF_POPUP = &H10
    MF_SEPARATOR = &H800
    MF_STRING = &H0
    MF_UNCHECKED = &H0
End Enum

Public Enum WindowMessages As Integer
    WM_COMMAND = &H111
    WM_SYSCOMMAND = &H112
End Enum
#End Region