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



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

带快捷方式列表的系统菜单

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)

但是,我找不到有关如何添加键盘快捷键的示例。 我试图遵循另一个堆栈溢出问题的答案(德尔福 - 向以编程方式添加的系统菜单选项添加快捷方式( 在其中,他们正在创建一个具有所需快捷方式的加速器表。但是,我找不到所调用方法的签名或快捷方式的结构。

'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

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

额外信息: 我使用以下代码成功捕获新菜单上的单击事件

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. 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()函数从窗口消息转换为 SYSCOMMAND。每次收到窗口消息时都需要调用它。但是,在WndProc中调用它还不够,因为消息仅传递到焦点控件,这意味着如果窗体的控件之一具有焦点,则窗体本身将不会接收关键消息。

为了克服这个问题,我们可以在应用程序的消息泵中安装一个消息过滤器。这将在发送到应用程序的每个窗口消息到达任何形式或控件之前截获它。然后,我们只需要将窗体的窗口句柄传递给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

最新更新