2014年7月26日土曜日

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



結果、継承コントロールでオブジェクトを指定してやり、作成したコントロールを参照渡しする方法も探しましたができなかったので、再びプロパティをちまちま作成することに。

まずは、現在状況の調整から。
現状のままでは、実行時に→キーを押すとうまくいかないなどの問題があり、このまま使用することは厳しい状況です。
プロパティを作成する前に、この辺の調整から始めます。

十時キーの問題はどうやら、「CreateWindowEx」関数を宣言したタイミングでのパラメーターの一つ、
<param name="hWndParent">親ウインドウまたはオーナーウインドウのハンドル</param>
こいつが、宣言時はユーザーコントロールのハンドルを渡しているために、発生した問題のようです。
これを「Me.Handle」からMe.ParentForm.Handle」に変更した場合、Newのタイミングでは「Me.ParentForm」は「Nothing」のため、エラーを返します。
なので「CreateWindowEx」関数の宣言のタイミングを「New」から「Load」に変更すれば問題は解決します。
しかし、「Load」のタイミングでやると、デザイン段階でコントロールのサイズや移動を行うとうまくいかないという不具合が発生しました。
なので、デザイン時のオーナーはユーザーコントロールが、実行時のオーナーは配置先のウインドウという切り替えをしてやる必要があります。

まず、オーナーの変更には「SetParent」関数を使用します。

''' <summary>
''' 指定された子ウインドウの親ウインドウを変更します
''' </summary>
''' <param name="hWndChild">ウインドウハンドル</param>
''' <param name="hWndNewParent">新しい親ウインドウ</param>
''' <returns></returns>
''' <remarks></remarks>
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Public Shared Function SetParent(ByVal hWndChild As IntPtr, ByVal hWndNewParent As IntPtr) As IntPtr
End Function

次に、ユーザーコントロール側に以下の宣言を行います。

''' <summary>
''' オーナーウインドウの変更の実施の有無 True:変更済み False:変更前
''' </summary>
''' <remarks></remarks>
Private _SetParant As Boolean = False

''' <summary>
''' オーナーウインドウを変更する
''' </summary>
''' <remarks></remarks>
Public Sub SetPararenthWnd()
     If Me.ParentForm IsNot Nothing Then
          Me.Enabled = False
        Class_API.SetParent(hwndEdit, Me.ParentForm.Handle)
        _SetParant = True
     End If
End Sub
最後に、作成したユーザーコントロール(縦書きテキストボックス)を配置したウインドウに、以下のコードを記載します。
Private Sub test4_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    Call TateTextBox_Test41.SetPararenthWnd()
End Sub
これでとりあえずは、デザイン時のレイアウト調整及び実行時のカーソルキーの問題は解決します。


次にサイズ変更について、以下の通りおまじないをかけます。
''' <summary>
''' 縦書きコントロールの位置及びサイズ調整
''' </summary>
''' <remarks></remarks>
Private Sub ReSetWindow(sender As Object, e As System.EventArgs)
     Class_API.SetWindowPos(hwndEdit, 0, Me.Left, Me.Top, Me.Width, Me.Height, Class_API.SWP_NOMOVE Or Class_API.SWP_NOZORDER)
End Sub

次に「CreateWindow」関数を宣言した後に、以下のコードを追加し、サイズ変更およびコントロール移動時に上記プロシージャを呼び出すようにします。
AddHandler Me.SizeChanged, AddressOf ReSetWindow
AddHandler Me.Move, AddressOf ReSetWindow


これで終了なのですが、再び問題が発生。
縦書き用テキストボックスがこれ一つであれば問題はないのですが、これ以外の入力フォーカスのあるコントロールが配置されている場合、そちらにフォーカスが移動してしまうという事態が発生してしまいました。

こうなると、イベントを拾ってやるしかないなぁって思って、下記のようにどういうイベントか見てみましたが、どうにも拾えない。
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
    MyBase.WndProc(m)
    End Select
    Debug.Print(m.ToString)
End Sub

CreateWindow関数はAPIなので、もしかしてAPI関数で作成したコントロールのイベントは拾えない??
こうなると、イベント自体を取得するためのコードが必要そうです。なんてめんどくさい……

この問題を放置すると、正直コントロールとして使えないので、何とかイベントを拾ってやろうと「SetWindowLong」関数を使うことに。

''' <summary>
''' 指定されたウインドウの属性を変更します。拡張ウインドウメモリ内に指定されたオフセット位置にあるlongデータも設定できます
''' </summary>
''' <param name="hWnd">ウインドウのハンドル</param>
''' <param name="nIndex">設定する値のオフセット</param>
''' <param name="dwNewLong">新しい値</param>
''' <returns>成功すると、変更前の値が帰ります。失敗すると0が返ります。</returns>
''' <remarks></remarks>
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Public Shared Function SetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As IntPtr, ByVal dwNewLong As IntPtr) As IntPtr
End Function

ユーザーコントロール内

Dim lpord As Integer = 0
''' <summary>
''' 縦書きコントロール用コールバック関数
''' </summary>
''' <param name="m"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function TateControlProc(ByRef m As Message) As IntPtr
    Static calling As Boolean
    If calling = False Then
       calling = True
       Debug.Print(Hex(m.Msg), Hex(m.LParam), Hex(lpord))
       calling = False
    End If
    TateControlProc = Class_API.CallWindowsProc(lpord, m.HWnd, m.Msg, m.WParam, m.LParam)
End Function

Private Sub TateTextBox_Test4_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    lpord = Class_API.SetWindowLong(hwndEdit, Class_API.GWL_WNDPROC, AddressOf WindowProc)
End Sub

とすると
 
このようなエラーが(涙)
仕方がないのでDelegateを宣言してやることに。

''' <summary>
''' 指定されたウインドウの属性を変更します。拡張ウインドウメモリ内に指定されたオフセット位置にあるlongデータも設定できます
''' </summary>
''' <param name="hWnd">ウインドウのハンドル</param>
''' <param name="nIndex">設定する値のオフセット</param>
''' <param name="dwNewLong">新しい値</param>
''' <returns>成功すると、変更前の値が帰ります。失敗すると0が返ります。</returns>
''' <remarks></remarks>
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Public Shared Function SetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As IntPtr, ByVal dwNewLong As WndProcDelegate) As IntPtr
End Function

''' <summary>
''' SetWindowLong関数のAddressof用デリゲート型
''' </summary>
''' <param name="hWnd">ウインドウハンドル</param>
''' <param name="uMsg">メッセージ</param>
''' <param name="wParam">最初のパラメータ</param>
''' <param name="lParam">二番目のパラメータ</param>
''' <returns></returns>
''' <remarks></remarks>
Public Delegate Function WndProcDelegate(ByVal hWnd As IntPtr, ByVal uMsg As IntPtr, wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
''' <summary>
''' 指定されたウインドウプロシージャに、メッセージ情報を渡します
''' </summary>
''' <param name="lpPrevWndFunc">元のウインドウプロシージャ</param>
''' <param name="hWnd">ウインドウのハンドル</param>
''' <param name="msg">メッセージ</param>
''' <param name="wParam">メッセージの最初のパラメータ</param>
''' <param name="lParam">メッセージの2番目のパラメータ</param>
''' <returns></returns>
''' <remarks></remarks>
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Public Shared Function CallWindowProc(ByVal lpPrevWndFunc As IntPtr, ByVal hWnd As IntPtr, ByVal msg As IntPtr, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
End Function
ユーザーコントロール内も変更
''' <summary>
''' 縦書きコントロール用コールバック関数
''' </summary>
''' <param name="hWnd">ウインドウハンドル</param>
''' <param name="uMsg">メッセージ</param>
''' <param name="wParam">最初のパラメータ</param>
''' <param name="lParam">二番目のパラメータ</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function TateControlProc(ByVal hWnd As IntPtr, ByVal uMsg As IntPtr, wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
    Static calling As Boolean
    If calling = False Then
        calling = True
(ここに処理を書く)
        calling = False
    End If
    TateControlProc = Class_API.CallWindowProc(lpord, hWnd, uMsg, wParam, lParam)
End Function

で、上のコードをユーザーコントロール内に記載するとエラーが発生しました。(ぉぃぉぃ)
縦書きコントロールを配置したフォームに変更すると問題なく動きましたので、「SetPararenthWnd」関数の宣言に合わせる形の方がよいかもしれません。
わたしは、縦型テキストコントロール内で処理したかったので、そのようにしました。
 
次にキーイベントごとにふるい分けして処理してやればいいのですが、いろいろ関数とか宣言してそれに合わせて定数を作ったりしてきたので、整理が必要かも(汗
とりあえず、キーイベントについては別のクラス内で宣言しようと思います。(定数なら列挙体で宣言してもいけそうではあるんですけどね)

今回必要なイベント定数は、キーが押された、離されたの二つですから
''' <summary>キーが押された</summary>
Friend Const WM_KEYDOWN As Integer = &H100
''' <summary>キーが離された</summary>
Friend Const WM_KEYUP As Integer = &H101
このように宣言し、「(ここに処理を書く)」にて識別します。
今回のように、縦書ボックスにした場合の方向キーの動作が変になる件については
Select Case uMsg
    Case API.Msg.WM.WM_KEYUP, API.Msg.WM.WM_KEYDOWN
        Select Case wParam
                Case Windows.Forms.Keys.Up
                    wParam = Windows.Forms.Keys.Left
        Case Windows.Forms.Keys.Down
                    wParam = Windows.Forms.Keys.Right
        Case Windows.Forms.Keys.Left
                wParam = Windows.Forms.Keys.Down
        Case Windows.Forms.Keys.Right
                wParam = Windows.Forms.Keys.Up
        End Select
    Case Else
End Select
このように記載してやればOKです。前後左右のボタンの問題はようやく解決を見せました。

次に方向キーを押すとコントロールのフォーカスが変更する件。(てか、最初にこっちだろうとw)
ログを調べたところ、キーダウンもアップのイベントも拾えませんでした。その代り「8」と「281」の番号を拾えたので、おそらくコレをキャンセルしてやればいけそうな気もします。
まずは意味から。
8はフォーカスを失ったときに発生する「WM_KILLFOCUS」定数であることがわかりました。
281はウインドウがアクティブになった時にアプリケーションに送信される「WM_IME_SETCONTEXT」定数のようです。
どちらにしても、フォーカスを失った後に発生しては意味がないわけで、、、さて、どうしたものか?

0 件のコメント:

コメントを投稿