2014年8月17日日曜日

縦書きテキストボックスコントロールの作成(その8)



すべてのプロパティを作成するのはめんどくさいので、必要そうなプロパティを列挙してみます。
今回は元々テキストボックスでやりたかったので、リッチテキストではなくテキストボックスでできる範囲で使うであろうプロパティで実装が必要そうなものを挙げてみます。

プロパティ名
説明
BackColor
コンポーネントの背景色です。
BorderStyle
エディットコントロールに境界線を付けるかどうかを示します。Fixed3D,FixedSingle,None
CharacterCasing
すべての文字をそのまま(Normal)にするか、大文字(Upper)または小文字(Lower)にするかを示します。
ContextMenuStrip
ユーザーがコントロールを右クリックしたときに表示されるショートカットメニューです。
Dock
コンテナーに固定されるコントロールの境界線を定義します。
Enabled
コントロールが有効かどうかを示します。
Font
コントロールでテキストを表示するフォントです。
ForeColor
テキストを表示するのに使用される、このコンポーネントの前景色です。
HideSelection
エディットコントロールがフォーカスを失ったときに選択を非表示にすることを示します。
ImeMode
選択された時のオブジェクトのIMEの状態を決定します。
Lines
文字列配列として複数行エディット内に表示されるテキストの行です。
Location
コンテナーの上部左端に相対する、コントロール上部左端の座標です。
Multiline
エディットコントロールのテキストが複数行にわたることができるかどうかを設定します。
ScrollBars
複数行エディットコントロールで、このコントロールに対してどのスクロールバーを表示するかを示します。
Text
コントロールに関連付けられたテキストです。
TextAlign
エディットコントロールに対してどのようにテキストを配置するかを示します。
Visible
コントロールの表示、非表示を示します。
WordWrap
複数行エディットコントロールで、行が自動的に折り返されるかどうかを示します。

上の項目のうち、ユーザーコントロールの方で勝手にやってくれそうなのが

プロパティ名
説明
Dock
コンテナーに固定されるコントロールの境界線を定義します。
Enabled
コントロールが有効かどうかを示します。
Location
コンテナーの上部左端に相対する、コントロール上部左端の座標です。
Visible
コントロールの表示、非表示を示します。

AcceptsTabは前回に実装したので今回からは除外します。

プロパティ名
説明
Text
コントロールに関連付けられたテキストです。

Textはすでに作成している通りなので、こちらも合わせて除外してもよいと考えます。

プロパティ名
説明
ContextMenuStrip
ユーザーがコントロールを右クリックしたときに表示されるショートカットメニューです。

まずはこれから。

 
旧来のリッチテキストボックスには、AutoVerbMenuという項目があったのですが、.netになってから標準のコンテキストメニューがなくなったようです。
MSDSを見ても、自作するコードが乗っているだけで、プロパティの変更でどうにかなるようなものではないようです。

メニュー項目としては、元に戻す、切り取り、コピー、貼り付け、削除、すべて選択の6点。
さて、メニューに含めるかどうか悩むのが、元に戻すは「UNDO」に似た操作の「REDO」、つまり「操作をやり直す」というものです。
UNDOは変更履歴をさかのぼるだけなのに対して、REDOは変更履歴ではなく操作履歴を遡る形となり、微妙に違います。
(つまり、状況によっては変更履歴を進める、ブラウザでいうところの右矢印の進むみたいなものです)
リッチテキストボックスのバージョン2以降は実装されている機能のようなので、作りたいなぁと思うこのごろです。
なので、まずはこれら標準に関して一般的なコード(調整前)を記載します。

まずはコンテキストメニューのウインドウ及びその動作において作成します。


''' <summary>リッチテキストコントロールのハンドルを格納した変数</summary>
Dim _RT As IntPtr
''' <summary>
''' コンテキストメニューコントロール
''' </summary>
''' <remarks></remarks>
Dim WithEvents AVM As ContextMenuStrip
''' <summary>
''' 作成時初期処理(メニューの作成)
''' </summary>
''' <param name="TextHandle">縦書ボックスのハンドル</param>
''' <remarks></remarks>
Sub New(TextHandle As IntPtr)
    If AVM Is Nothing Then AVM = New ContextMenuStrip
    With AVM
        .ShowImageMargin = False

        .Items.Add(Add_Menu("元に戻す(&U)", Keys.U, True))   '0
        .Items.Add(Add_Menu("やり直す(&R)", Keys.R, True))   '1
        .Items.Add(New ToolStripSeparator)                   '2
        .Items.Add(Add_Menu("切り取り(&T)", Keys.T, True))   '3
        .Items.Add(Add_Menu("コピー(&C)", Keys.C, True))     '4
        .Items.Add(Add_Menu("貼り付け(&P)", Keys.P, True))   '5
        .Items.Add(Add_Menu("削除(&D)", Keys.Delete, False)) '6
        .Items.Add(New ToolStripSeparator)                   '7
        .Items.Add(Add_Menu("すべて選択(&A)", Keys.A, True)) '8
        .Items.Add(New ToolStripSeparator)                   '9

        AddHandler CType(.Items(0), ToolStripMenuItem).Click, AddressOf _undo
        AddHandler CType(.Items(1), ToolStripMenuItem).Click, AddressOf _Redo
        AddHandler CType(.Items(3), ToolStripMenuItem).Click, AddressOf _Cut
        AddHandler CType(.Items(4), ToolStripMenuItem).Click, AddressOf _Copy
        AddHandler CType(.Items(5), ToolStripMenuItem).Click, AddressOf _Paste
        AddHandler CType(.Items(6), ToolStripMenuItem).Click, AddressOf _Delete
        AddHandler CType(.Items(8), ToolStripMenuItem).Click, AddressOf _SelectALL

    End With
    _RT = TextHandle
End Sub
''' <summary>
''' メニューアイコン作成用関数
''' </summary>
''' <param name="ViewText">表示するテキスト</param>
''' <param name="ShoutCutKey">ショートカットキー</param>
''' <param name="ControlKey">ショートカットキーにCtrlキーを含める場合はTrue、含めない場合はFalse</param>
''' <returns></returns>
''' <remarks></remarks>
Private Function Add_Menu(ByVal ViewText As String, ByVal ShoutCutKey As Keys, ByVal ControlKey As Boolean) As ToolStripMenuItem
    Dim C As New ToolStripMenuItem(ViewText)
    C.ShowShortcutKeys = False
    If ControlKey Then
        C.ShortcutKeys = CType((System.Windows.Forms.Keys.Control Or ShoutCutKey), System.Windows.Forms.Keys)
    Else
        C.ShortcutKeys = ShoutCutKey
    End If
    Return C
End Function
''' <summary>
''' ドロップダウンが開いたときに発生
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub AVM_Opened(sender As Object, e As System.EventArgs) Handles AVM.Opened
    For Each i As ToolStripItem In AVM.Items
        Select Case True
            Case TypeOf i Is ToolStripMenuItem
                Select Case i.Text
                    Case "元に戻す(&U)"
                        If Class_API.SendMessage(_RT, EM_CANUNDO, 0, 0) <> 0 Then
                            i.Enabled = True
                        Else
                            i.Enabled = False
                        End If
                    Case "やり直す(&R)"
                        If Class_API.SendMessage(_RT, EM_CANREDO, 0, 0) <> 0 Then
                            i.Enabled = True
                        Else
                            i.Enabled = False
                        End If

                    Case "切り取り(&T)", "コピー(&C)", "コピー(&C)"
                        '文字列が選択されているかどうか、選択状況を調べます。
                        Dim c As CHARRANGE
                        Class_API.SendMessage(_RT, EM_GETSEL, c.cpMin, c.cpMax)
                        If c.cpMan = c.cpMin Then
                            i.Enabled = False
                        Else
                            i.Enabled = True
                        End If
                    Case "貼り付け(&P)"
                        If Class_API.SendMessage(_RT, EM_CANPASTE, 0, 0) <> 0 Then
                            i.Enabled = True
                        Else
                            i.Enabled = False
                        End If
                    Case "すべて選択(&A)"
                        Dim _LENGTH As Integer = Class_API.SendMessage(_RT, WM_GETTEXTLENGTH, 0, 0)
                        If _LENGTH > 0 Then
                            i.Enabled = True
                        Else
                            i.Enabled = False
                        End If

                End Select
        End Select
    Next
End Sub

''' <summary>
''' 元に戻す
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub _undo(sender As System.Object, e As System.EventArgs)
    If Class_API.SendMessage(_RT, EM_CANUNDO, 0, 0) <> 0 Then
        Class_API.SendMessage(_RT, EM_UNDO, 0, 0)
    End If
End Sub

''' <summary>
''' やり直す
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub _Redo(sender As System.Object, e As System.EventArgs)
    If Class_API.SendMessage(_RT, EM_CANREDO, 0, 0) <> 0 Then
        Class_API.SendMessage(_RT, EM_REDO, 0, 0)
    End If
End Sub

''' <summary>
''' 切り取り
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub _Cut(sender As System.Object, e As System.EventArgs)
    Class_API.SendMessage(_RT, WM_CUT, 0, 0)
End Sub
''' <summary>
''' コピー
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub _Copy(sender As System.Object, e As System.EventArgs)
    Class_API.SendMessage(_RT, WM_COPY, 0, 0)
End Sub
''' <summary>
''' 貼り付け
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub _Paste(sender As System.Object, e As System.EventArgs)
    If Class_API.SendMessage(_RT, EM_CANPASTE, 0, 0) <> 0 Then
        Class_API.SendMessage(_RT, WM_PASTE, 0, 0)
    End If
End Sub

''' <summary>
''' 削除
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub _Delete(sender As System.Object, e As System.EventArgs)
    Class_API.SendMessage(_RT, WM_CLEAR, 0, 0)
End Sub
''' <summary>
''' すべて選択
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub _SelectALL(sender As System.Object, e As System.EventArgs)
    Dim _LENGTH As Integer = Class_API.SendMessage(_RT, WM_GETTEXTLENGTH, 0, 0)
    Dim c As CHARRANGE
    c.cpMin = 0
    c.cpMax = _LENGTH
    Class_API.SendMessage(_RT, EM_SETSEL, c.cpMin, c.cpMax)
End Sub

次に、

''' <summary>
''' 指定されたウインドウに、新しいメニューを割り当てます。
''' </summary>
''' <param name="hWnd">ウインドウのハンドル</param>
''' <param name="hMenu">メニューのハンドル</param>
''' <returns></returns>
''' <remarks></remarks>
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Public Shared Function SetMenu(ByVal hWnd As IntPtr, ByVal hMenu As IntPtr) As IntPtr
End Function

このようにメニューをぶっこむSetMenu関数を宣言して、宣言してやります。

次に縦書きコントロール作成時に下記ノードを追加してやります。

_AVM = New AutoVerbMenu(_Handle)
Dim err_ As IntPtr = Class_API.SetMenu(_Handle, _AVM.Handle)
If err_ = 0 Then
    MsgBox("メニュー作成に失敗しました", , "エラーダイアログ")
End If

これで十分かな? と思ってテストすると

 
Oh….

この方法では、コンテキストメニュープロパティに格納してやることはできないようです。

次に試した方法は、調べた結果、コンテキストメニューは「TrackPopupMenu関数」を使用して表示するようです。
宣言から
''' <summary>
''' 指定された位置にショートカットメニューを表示し、そのメニュー内のメニュー項目の選択状況を追跡します。ショートカットメニューは、画面内のどこにでも表示できます。
''' </summary>
''' <param name="hMenu">ショートカットメニューのハンドル</param>
''' <param name="uFlags">オプション</param>
''' <param name="x">水平位置</param>
''' <param name="y">垂直位置</param>
''' <param name="nReserved">予約済み、0を指定する</param>
''' <param name="hWnd">所有側ウインドウのハンドル</param>
''' <param name="preRect">無視される</param>
''' <returns></returns>
''' <remarks></remarks>
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Public Shared Function TrackPopupMenu(ByVal hMenu As IntPtr, ByVal uFlags As UInteger, ByVal x As Integer, ByVal y As Integer, ByVal nReserved As Integer, ByVal hWnd As IntPtr, ByVal preRect As IntPtr) As IntPtr
End Function
次に、マウスの右クリックのイベントを取得してやる必要があります。
TateControlProc」ないで、マウスイベントを取得します。
lParamから、X座標とY座標の値を取得する必要もあるので、取得するためのLOWORD関数及びHIWORD関数も合わせて作成します。
lParam
Select Case uMsg
    Case API.Msg.WM.WM_RBUTTONUP
        Dim x As Integer = Class_API.LOWORD(lParam)
        Dim Y As Integer = Class_API.HIWORD(lParam)
        Call _AVM.PopUp(x, Y)
End Select

    Sub PopUp(ByVal x As Integer, ByVal y As Integer)
        Dim err_ As IntPtr = Class_API.TrackPopupMenu(AVM.Handle, 0, x, y, 0, _RT, Nothing)
        If err_ = 0 Then
            MsgBox("コンテキストメニュー表示に失敗しました。", , "エラーダイアログ")
        End If
    End Sub
''' <summary>
''' 指定した値の下位の値を取得します。
''' </summary>
''' <param name="value">取得元の値</param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function LOWORD(ByVal value As IntPtr) As Integer
    Return CInt((value.ToInt32 And &HFFFF&))
End Function

''' <summary>
''' 指定した値の上位の値を取得します。
''' </summary>
''' <param name="value">取得元の値</param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function HIWORD(ByVal value As IntPtr) As Integer
    Return CInt((value.ToInt32 And &HFFFF000%)) \ &H10000%
End Function

さて、これでテストしてみると、
 
Oh…> orz

どうやら、.Netが提供しているContextMenuSplitは使えないようです。こうなると、API関数の「CreatePopupMenu関数」で作成してやらないといけないかもしれません。
ですが、どうにもそれをやりたくない、、、


そこで、はっと気が付きました。なら、UserControlの方のContextMenuSplitに割り当てればいいんじゃね? 重ねて表示しているわけだし。

さっそく「AVM」の宣言を「DIM」から「FRIEND」に変更し
_AVM = New AutoVerbMenu(_Handle)
Dim err_ As IntPtr = Class_API.SetMenu(_Handle, _AVM.Handle)
If err_ = 0 Then
    MsgBox("メニュー作成に失敗しました", , "エラーダイアログ")
End If
上を下に書き換えてみましたところ
_AVM = New AutoVerbMenu(_Handle)
sender.ContextMenuStrip = _AVM.AVM
 
このように、カーソル位置のそばに表示されました。

ただ、範囲選択の件だけうまく動作していないので修正してやる必要があります。
原因は簡単で、情報を格納する戻り値である「wParam」と「lParam」の宣言が「ByVal」になっているためです。これを「ByRef」に直してやる必要があります。
SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr)
しかし、単純に直せばいいというわけでもないので、あたらなメッセージ送信関数を準備することにしました。
''' <summary>
''' メッセージの送信
''' </summary>
''' <param name="hWnd">送信先ウインドウハンドル</param>
''' <param name="Msg">送信するメッセージ</param>
''' <param name="wParam">最初のパラメータ</param>
''' <param name="lParam">二番目のパラメータ</param>
''' <returns></returns>
''' <remarks></remarks>
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True, CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
Public Shared Function SendMessageA(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByRef wParam As IntPtr, ByRef lParam As IntPtr) As IntPtr
End Function

つぎに、先ほどのメッセージ送信時の宣言を
Class_API.SendMessageA(_RT, EM_GETSEL, c.cpMin, c.cpMax)」に修正。
すべて選択も、
 
 
この通り、問題なく選択することができました。

なお、上記のコードではポップアップ時における「削除」の制御を忘れていますので、付け加えてやる必要があります。あしからず。
一般的なコード(調整前)はここまでで、これでは「コピー」と「貼り付け」において問題が発生しています。
それは次回で。

0 件のコメント:

コメントを投稿