タブ式テキストエディタ(4) キーステート感知クラスをつくる

 ここでいうキーステートとは、[Insert]キーで切り替わる挿入/上書、[Shift]+[Caps]で切り替わるCaps Lock(大文字/小文字)、[Scroll]キーで切り替わる画面スクロールのオン/オフ、[Num Lock]キーで切り替わるテンキーのオン/オフのことです。
 これらの状態を監視して、ステータスバーパネルに表示させるようにします。この表示用のステータスバーパネルは、メインフォームのNEWコンストラクタ内で動的に作成することにします。またWin32APIを使わなければなりません。ただ、不可欠な機能というわけではありません。[Insert]キーに対応する表示(挿入/上書)がないのでつくりたかったのです。

VKStatePane クラス

 StatusBarPanel派生クラスです。本クラスはキーステートの変化を感知するだけです。表示はフォーム側でオーナードローを使って行います。

@ "Application_Idol_Ex"関数でキーが押されたことを感知。
A "GetKeyState"関数(Win32API)でキーの状態を取得。
B 取得したキーの状態を"VKState"プロパティに格納。
C "VKStateChange"イベントで変更があったことをフォームに通知。
D フォームは"VKState"プロパティの値に基づいてパネルに描画。

 列挙体"VKType"はキーの状態を格納するためのものです。複数の項目を同時に選択できるよう、Flags属性をつけて、ビット演算ができるようにしています。これはクラシックなスタイル。これ以外にも、"BitArray"クラスや"BitVector32"構造体を使う手もあります。
 キーの状態は、Win32APIの"GetKeyState"関数でキーごとに取得します。その引数として、"VK_CAPITAL"以下の定数が必要になります。キーが無効なら"0"、有効なら"1"を返してきます。

Public Class VKStatePane
    Inherits StatusBarPanel

    ' 変数
    Private m_state As VKFlags    ' キーの状態(プロパティ用)
    Private m_current As VKFlags  ' キーの状態(現状取得用)

    ' イベント
    Public Event VKStateChanged( _
            ByVal pane As VKStatePane, _
            ByVal e As EventArgs)

    ' Win32API : 定数
    Private Const VK_CAPITAL As Integer = &H14
    Private Const VK_KANA As Integer = &H15
    Private Const VK_INSERT As Integer = &H2D
    Private Const VK_NUMLOCK As Integer = &H90
    Private Const VK_SCROLL As Integer = &H91

    ' 列挙体 : VKType
    <Flags()> Public Enum VKFlags
        None = 0
        VKCapital = 1
        VKKana = 2
        VKInsert = 4
        VKNumlock = 8
        VKScroll = 16
        VKAll = _
            VKCapital Or VKKana Or VKInsert Or VKNumlock Or VKScroll
    End Enum

    ' Win32API : GetKeyState
    Private Declare Function GetKeyState Lib "user32"  _
            (ByVal vk As Integer) As Short

    ' プロパティ : VKState
    Public Property VKState() As VKFlags
        Get
            Return m_state
        End Get
        Set(ByVal Value As VKFlags)
            m_state = Value
        End Set
    End Property

    ' New : コンストラクタ
    Public Sub New(ByVal width As Integer)
        MyBase.New()
        Me.Width = width
        Me.Alignment = HorizontalAlignment.Left

        ' Idleイベントハンドラの追加
        AddHandler Application.Idle, AddressOf Application_Idle_Ex
    End Sub

    ' Application_Idle : アプリケーションがアイドル状態の時
    Private Sub Application_Idle_Ex( _
            ByVal sender As Object, _
            ByVal e As EventArgs)
        ' キーの状態を監視
        m_current = VKFlags.None
        If GetKeyState(VK_CAPITAL) <> 0 Then
            m_current = m_current Or VKFlags.VKCapital
        End If
        If GetKeyState(VK_KANA) <> 0 Then
            m_current = m_current Or VKFlags.VKKana
        End If
        If GetKeyState(VK_INSERT) <> 0 Then
            m_current = m_current Or VKFlags.VKInsert
        End If
        If GetKeyState(VK_NUMLOCK) <> 0 Then
            m_current = m_current Or VKFlags.VKNumlock
        End If
        If GetKeyState(VK_SCROLL) <> 0 Then
            m_current = m_current Or VKFlags.VKScroll
        End If

        If (m_state Xor m_current) <> VKFlags.None Then
            m_state = m_current
            RaiseEvent VKStateChanged(Me, Nothing)
        End If
    End Sub

End Class	

 コンストラクタでステータスバーパネルの幅を引数にしていますが、これは余計かもしれません。フォーム側で直接指定した方がすっきりするような気がします。

フォーム側のイベント処理

 "KeyStateCanged"イベントを感知したら、"Refresh"メソッドを使って、ステータスバー"SB1"に描画をするよう命令をします。これによってステータスバーの"DrawItem"イベントが発生します。

' ステータスバーパネル : キーステートの変更イベント
Private Sub KeyStateChanged(ByVal pane As VKStatePane, ByVal e As EventArgs)
    SB1.Refresh() '再描画
End Sub
	 

フォーム側のオーナードロー

 "sbdEvent.Panel"でどのパネルに描画するかを指定する必要があります。
 また、文字列の描画位置を直接数字で指定しています。手抜きです。面倒くさがらずに定数として宣言するべきでしょう。

' ステータスパネル : 描画イベント
Private Sub SB1_DrawItem( _
            ByVal sender As Object, _
            ByVal sbdevent As StatusBarDrawItemEventArgs) _
            Handles SB1.DrawItem

    Dim f As Font = New Font("MS UI Gothic", 9)  ' 描画フォントの指定

    ' ファイル名の描画
    If sbdevent.Panel Is Me.sbPath Then
        sbdevent.Graphics.DrawString( _
                sbdevent.Panel.Text, _
                f, _
                Brushes.Black, _
                2, _
                2)
        sbdevent.Graphics.Dispose()
    End If

    ' キーステートの描画
    If sbdevent.Panel Is m_vkpane Then
        Dim status As VKStatePane.VKFlags = _
                DirectCast(sbdevent.Panel, VKStatePane).VKState
        Dim r As Rectangle = sbdevent.Bounds ' 描画対象四角形
        Dim x As Integer = r.Left + 2  ' 描画位置X
        Dim y As Integer = r.Top + 3   ' 描画位置Y
        Dim brDark As Brush = SystemBrushes.WindowText '有効時の文字色
        Dim brLight As Brush = SystemBrushes.ControlDark ' 無効時の文字色
        With sbdevent.Graphics
            If (status And VKStatePane.VKFlags.VKInsert) = _
                        VKStatePane.VKFlags.VKInsert Then
                .DrawString("上書", f, brDark, x, y)
            Else
                .DrawString("挿入", f, brDark, x, y)
            End If
            If (status And VKStatePane.VKFlags.VKNumlock) = _
                        VKStatePane.VKFlags.VKNumlock Then
                .DrawString("Num", f, brDark, x + 32, y)
            Else
                .DrawString("Num", f, brLight, x + 32, y)
            End If
            If (status And VKStatePane.VKFlags.VKCapital) = _
                        VKStatePane.VKFlags.VKCapital Then
                .DrawString("Caps", f, brDark, x + 60, y)
            Else
                .DrawString("Caps", f, brLight, x + 60, y)
            End If
            If (status And VKStatePane.VKFlags.VKScroll) = _
                        VKStatePane.VKFlags.VKScroll Then
                .DrawString("Scrl", f, brDark, x + 90, y)
            Else
                .DrawString("Scrl", f, brLight, x + 90, y)
            End If
            .Dispose()
        End With
    End If

    f.Dispose()
End Sub	

 ビット処理のところは、なれないうちはややこしいですよね。
  states and VKStatePanel.VKFlags.VKInsert
で、VKInsertの箇所だけの値を取り出すことができます。したがって、この値が VKInsert と同じ 4 なら有効、それ以外なら無効であると判断できるわけです。

フォームの New コンストラクタでインスタンスの生成

 "Windowsフォームデザイナで生成されたコード"内のNewコンストラクタに、ステータスバーパネルを追加するコードを付け足します。これでキーステート関係のコーディングは終了です。

' New : コンストラクタ
Public Sub New()
    MyBase.New()
    ' この呼び出しは Windows フォーム デザイナで必要です。
    InitializeComponent() 

    ' InitializeComponent() 呼び出しの後に初期化を追加します。
    m_vkpane = New VKStatePane(128)
    m_vkpane.Style = StatusBarPanelStyle.OwnerDraw
    AddHandler m_vkpane.VKStateChanged, AddressOf KeyStateChanged
    SB1.Panels.Add(m_vkpane)
End Sub	
| ■HOME | ◆プログラムTop | ▲ページの先頭 | << 前の章 | 次の章 >> |