2008年5月25日

前回: WPFでホットキーコントロールへの道4

 ようやく一応できました。TextBoxのXAMLは前回をみてください。今回はコード部分についてです。

 ホットキーに表示する各キーの名前は、System.Windows.Input.Key列挙体の値との対応を地道にコードで書きました。日本語キーボードはこんな感じで良いでしょう。英語キーボードには未対応。

Public Sub New()
    InitializeComponent()

    Hotkeys = New Dictionary(Of Key, String)

    ' A-Z, F1-F12
    For Each k In New Key() {Key.A, Key.B, Key.C, Key.D, Key.E, Key.F, Key.G, Key.H, Key.I, Key.J, Key.K, Key.L, Key.M, Key.N, Key.O, Key.P, Key.Q, Key.R, Key.S, Key.T, Key.U, Key.V, Key.W, Key.X, Key.Y, Key.Z, _
                             Key.F1, Key.F2, Key.F3, Key.F4, Key.F5, Key.F6, Key.F7, Key.F8, Key.F9, Key.F10, Key.F11, Key.F12}
        Hotkeys.Add(k, k.ToString)
    Next

    ' D0-D9
    For Each k In New Key() {Key.D0, Key.D1, Key.D2, Key.D3, Key.D4, Key.D5, Key.D6, Key.D7, Key.D8, Key.D9}
        Hotkeys.Add(k, k.ToString.Chars(1))
    Next

    Hotkeys.Add(Key.Escape, "Esc")
    Hotkeys.Add(Key.Back, "BackSpace")
    Hotkeys.Add(Key.Tab, "Tab")
    Hotkeys.Add(Key.Enter, "Enter")
    Hotkeys.Add(Key.Space, "Space")

    Hotkeys.Add(Key.Insert, "Insert")
    Hotkeys.Add(Key.Delete, "Delete")
    Hotkeys.Add(Key.Home, "Home")
    Hotkeys.Add(Key.End, "End")
    Hotkeys.Add(Key.PageUp, "PageUp")
    Hotkeys.Add(Key.PageDown, "PageDown")

    Hotkeys.Add(Key.Up, "Up")
    Hotkeys.Add(Key.Left, "Left")
    Hotkeys.Add(Key.Down, "Down")
    Hotkeys.Add(Key.Right, "Right")

    Hotkeys.Add(Key.NumLock, "NumLock")
    Hotkeys.Add(Key.Divide, "Num /")
    Hotkeys.Add(Key.Multiply, "Num *")
    Hotkeys.Add(Key.Subtract, "Num -")
    Hotkeys.Add(Key.Add, "Num +")
    Hotkeys.Add(Key.Decimal, "Num .")
    Hotkeys.Add(Key.NumPad0, "Num 0")
    Hotkeys.Add(Key.NumPad1, "Num 1")
    Hotkeys.Add(Key.NumPad2, "Num 2")
    Hotkeys.Add(Key.NumPad3, "Num 3")
    Hotkeys.Add(Key.NumPad4, "Num 4")
    Hotkeys.Add(Key.NumPad5, "Num 5")
    Hotkeys.Add(Key.NumPad6, "Num 6")
    Hotkeys.Add(Key.NumPad7, "Num 7")
    Hotkeys.Add(Key.NumPad8, "Num 8")
    Hotkeys.Add(Key.NumPad9, "Num 9")

    Select Case GetKeyboardType(0)
        Case 7
            ' 日本語キーボード
            Hotkeys.Add(Key.OemMinus, "-")
            Hotkeys.Add(Key.OemQuotes, "^")
            Hotkeys.Add(Key.OemPipe, "\(|)")
            Hotkeys.Add(Key.OemTilde, "@")
            Hotkeys.Add(Key.OemOpenBrackets, "[")
            Hotkeys.Add(Key.OemPlus, "+")
            Hotkeys.Add(Key.OemSemicolon, "*")
            Hotkeys.Add(Key.OemCloseBrackets, "]")
            Hotkeys.Add(Key.OemComma, ",")
            Hotkeys.Add(Key.OemPeriod, ".")
            Hotkeys.Add(Key.OemQuestion, "/")
            Hotkeys.Add(Key.OemBackslash, "\(_)")
            Hotkeys.Add(Key.ImeConvert, "変換")

        Case Else
            '
    End Select

    SetHotkey(Input.Key.None, Input.ModifierKeys.None)
End Sub

 ホットキーコントロールのプロパティを作りました。ホットキーがセットされているか、修飾キー、キーの値が取れるようにしてます。あと読み取り専用です。設定する方法はメソッドで用意することにします。これは、修飾キーとキーを同時に設定する必要があるためです。

#Region "Properties"

    Private Shared ReadOnly IsKeySetPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("IsKeySet", GetType(Boolean), GetType(HotkeyControl), New FrameworkPropertyMetadata(Nothing))
    Public Shared ReadOnly IsKeySetProperty As DependencyProperty = IsKeySetPropertyKey.DependencyProperty

    Public Property IsKeySet() As Boolean
        Get
            Return GetValue(IsKeySetProperty)
        End Get
        Protected Set(ByVal value As Boolean)
            SetValue(IsKeySetPropertyKey, value)
        End Set
    End Property


    Private Shared ReadOnly ModifierPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("Modifier", GetType(ModifierKeys), GetType(HotkeyControl), New FrameworkPropertyMetadata(Nothing))
    Public Shared ReadOnly ModifierProperty As DependencyProperty = ModifierPropertyKey.DependencyProperty

    Public Property Modifier() As ModifierKeys
        Get
            Return GetValue(ModifierProperty)
        End Get
        Protected Set(ByVal value As ModifierKeys)
            SetValue(ModifierPropertyKey, value)
        End Set
    End Property


    Private Shared ReadOnly KeyPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("Key", GetType(Key), GetType(HotkeyControl), New FrameworkPropertyMetadata(Nothing))
    Public Shared ReadOnly KeyProperty As DependencyProperty = KeyPropertyKey.DependencyProperty

    Public Property Key() As Key
        Get
            Return GetValue(KeyProperty)
        End Get
        Protected Set(ByVal value As Key)
            SetValue(KeyPropertyKey, value)
        End Set
    End Property

#End Region

 TextBoxのPreviewKeyDownとPreviewKeyUpイベント処理。ここでは入力されたキーを表示します。PreviewKeyDownイベントでリアルタイムに押されているきーを表示。ただし、コンストラクタで設定したHotkeysコレクションにあるキーのみ。修飾キーを除いて複数のキーが押されていてもホットキーとしては登録できないので、1個のキーだけ表示します。さらに、CtrlだけやAltだけでもホットキーとして登録できないので、その場合PreviewKeyUpイベントでホットキーなしにします。

Private Sub TextBox_PreviewKeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.KeyEventArgs)
    Dim text = New Text.StringBuilder

    Me.Modifier = Keyboard.Modifiers

    If CBool(Keyboard.Modifiers And ModifierKeys.Control) Then
        text.Append("Ctrl + ")
    End If
    If CBool(Keyboard.Modifiers And ModifierKeys.Shift) Then
        text.Append("Shift + ")
    End If
    If CBool(Keyboard.Modifiers And ModifierKeys.Alt) Then
        text.Append("Alt + ")
    End If

    Me.IsKeySet = False
    Me.Key = Input.Key.None

    For Each k In Hotkeys
        If Keyboard.IsKeyDown(k.Key) Then
            text.Append(k.Value)
            IsKeySet = True
            Me.Key = k.Key
            Exit For
        End If
    Next

    If text.ToString <> "" Then
        DirectCast(sender, TextBox).Text = text.ToString
    End If
    e.Handled = True
End Sub

Private Sub TextBox_PreviewKeyUp(ByVal sender As System.Object, ByVal e As System.Windows.Input.KeyEventArgs)
    If Not IsKeySet Then
        DirectCast(sender, TextBox).Text = "(Nothing)"
        Me.Modifier = Input.ModifierKeys.None
        Me.Key = Input.Key.None
    End If

    e.Handled = True
End Sub

 ホットキーコントロールにキーを設定するメソッドは次のようにしました。すこしPreviewKeyDownイベントとかぶってます。

Public Sub SetHotkey(ByVal key As Key, ByVal modifier As ModifierKeys)
    Dim text = New Text.StringBuilder

    If CBool(modifier And Input.ModifierKeys.Control) Then
        text.Append("Ctrl + ")
    End If

    If CBool(modifier And Input.ModifierKeys.Shift) Then
        text.Append("Shift + ")
    End If

    If CBool(modifier And Input.ModifierKeys.Alt) Then
        text.Append("Alt + ")
    End If

    If Hotkeys.Keys.Contains(key) Then
        IsKeySet = True
        text.Append(Hotkeys(key))
        TextBox.Text = text.ToString

        Me.Modifier = modifier
        Me.Key = key
    Else
        IsKeySet = False
        TextBox.Text = "(Nothing)"

        Me.Modifier = Input.ModifierKeys.None
        Me.Key = Input.Key.None
    End If
End Sub

 あとコンテキストメニューの処理。さっそくSetHotkeyメソッド使います。

Private Sub MenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    SetHotkey(Input.Key.None, Input.ModifierKeys.None)
End Sub

 以上です。まだきちんと自分で使ってないのでバグってるかも。テスト用のウィンドウ。半角/全角キーとか押すとコードでは(Nothing)と設定しているはずなのに、テキストに反映されませんね。

Window1

posted @ 17:58 | Feedback (125)

前回: WPFでホットキーコントロールへの道3

 TextBoxをホットキーコントロールとして使おうとしてました。その場合、右クリックが邪魔ですねー。コンテキストメニューの無効またはカスタムメニューの表示方法について、そのまんまのサンプルがMSDNにありました。

カスタム コンテキスト メニューを持つ TextBox のサンプル

 これで万事解決(右クリックに関しては)。無効にする場合は、TextBoxのContextMenuプロパティをNothingにするだけで良いようです。

 Win32のホットキーコントロールではDeleteやBackSpaceキーを押すと「ホットキーなし」とされていたんですが、Win32のAPI RegisterHotKey関数ではDeleteやBackSpaceもホットキーとして登録できます。なんで、作るホットキーコントロールはDeleteやBackSpaceもホットキーとして表示して、コンテキストメニューに「ホットキーなし」の項目を追加しました。

 XAMLはこんな感じ。

<UserControl x:Class="HotkeyControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <TextBox x:Name="TextBox" PreviewKeyDown="TextBox_PreviewKeyDown" PreviewKeyUp="TextBox_PreviewKeyUp" IsUndoEnabled="False" AllowDrop="False">
            <TextBox.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Clear hotkey" Click="MenuItem_Click" />
                </ContextMenu>
            </TextBox.ContextMenu>
        </TextBox>
    </Grid>
</UserControl>

 あと最後に、デフォルトのままだとTextBoxのテキストはドラッグ&ドロップで変更できてしまうので、AllowDropプロパティをFalseにしています。

posted @ 16:01 | Feedback (372)