Добавление сочетаний клавиш в системное меню - PullRequest
0 голосов
/ 05 июля 2018

Я пытаюсь добавить список дополнительных параметров в системное меню формы и сделать их доступными в виде сочетаний клавиш. Мне удалось добавить параметры в системное меню, и мне удалось поймать щелчок меню для этих параметров.

Системное меню со списком ярлыков

 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 - добавление ярлыка к программно добавленному параметру системного меню ) и в нем они создают таблицу ускорителей с необходимыми ярлыками. Однако я не смог найти сигнатуру для вызываемого метода или структуру для ярлыков.

        '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 Ответ

0 голосов
/ 06 июля 2018

Извините за ответ так поздно. Проведя целый день с этим, мне наконец удалось найти решение.

Прежде всего, есть пара вещей, которые необходимо исправить с помощью кода:

  1. ACCEL должно быть Structure, а не Class.

  2. ACCEL.key и ACCEL.cmd должны быть объявлены как UShort, а не UInt32.

  3. Оказывается, первый параметр CreateAcceleratorTable() должен быть объявлен как ByVal lpaccel As ACCEL() (массив структур ACCEL).

  4. Вам нужно поразрядно или 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...