Извините за ответ так поздно. Проведя целый день с этим, мне наконец удалось найти решение.
Прежде всего, есть пара вещей, которые необходимо исправить с помощью кода:
ACCEL
должно быть Structure
, а не Class
.
ACCEL.key
и ACCEL.cmd
должны быть объявлены как UShort
, а не UInt32
.
Оказывается, первый параметр CreateAcceleratorTable()
должен быть объявлен как ByVal lpaccel As ACCEL()
(массив структур ACCEL
).
Вам нужно поразрядно или 1
(эквивалентно FVIRTKEY
) с вашим текущим значением accel.fVirt
. FVIRTKEY
указывает, что accel.key
- это код виртуального ключа , а не код ASCII. Последний регистрозависим, первый - нет.
Теперь, чтобы ускорители работали, вам нужно что-то, что обрабатывает и переводит их в SYSCOMMAND.
Ускорители можно переводить из оконных сообщений в SYSCOMMAND с помощью функции TranslateAccelerator()
. Это должно быть вызвано каждый раз, когда получено сообщение окна. Однако вызова его внутри 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
NativeMethods:
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