2014年7月29日火曜日

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



前回で方向キーを縦型テキストボックスに対応させることはできましたが、フォーカスを失うという問題が残っています。

他にも問題があり、メッセージを拾うのはよいのですが、アプリ終了時に御呪いは必要なので、下記の通り記載しました。

Private Sub _Disposed(sender As Object, e As System.EventArgs) Handles Me.Disposed
    lpord = Class_API.SetWindowLong(hwndEdit, Class_API.GWL_WNDPROC, lpord)
End Sub

方向キーについても、専用の関数を作成し、それを呼び出す形に修正をかけることで整理。
''' <summary>
''' 方向キーを縦型に対応させるための関数
''' </summary>
''' <param name="uMsg">メッセージ</param>
''' <param name="wParam">パラメータ</param>
''' <returns>True:方向キーの変更を実施 False:方向キーの変更を未実施</returns>
''' <remarks></remarks>
Private Function KeyCheck(ByVal uMsg As IntPtr, ByRef wParam As IntPtr) As Boolean
    If uMsg = API.Msg.WM.WM_KEYUP Or uMsg = API.Msg.WM.WM_KEYDOWN Then
        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
            Case Else
                Return False
        End Select
        Return True
    Else
        Return False
    End If
End Function

さて、ここからが長い戦いがはじまりです。

実のところ、いろいろ調査をしてみましたが、どうにも方向キーのイベントメッセージだけ受け取れない状況でした。ほかのコントロールが受け取っているのかと思い、ハンドルを変えていろいろチェックをかけたのですが、見つからない……

コントロール独自の問題で、内部で変なことをしているのかと思って、他のバージョンやテキストボックスに変更してテストしてみましたが、同様の現象が発生しました。
さらに、ユーザーコントロール上で実施しているのが悪く、直接フォーム上で宣言しても見ましたが、ダメでした。
なお、CreateWindowEx関数で二つ以上の縦書きコントロールを配置してみましたが、こちらは問題なく動作する……

よーわからん………

試しに、通常のテキストボックスに宣言しなおしてもペケ。

さらに別の問題が。
Me.ParentForm.Handle」では、ベースとなる大元の親フォームのハンドルを返す為、仮にパネルなどに張り付けている場合は動作が正常にならない問題が発生。
(後から気が付きましたが、Me.Parent.Handle」でやれば下記の処理を省略できた(涙))
縦書コントロールが格納されているコントロールのハンドルを取得する方向で調整する必要があるようです。
しかし「Control.FromChildHandle(Me.Handle).Handle」では、貼り付けているハンドルをなぜか取得できない。
仕方がないので、自分が格納されているコントロールを調べる関数を作成することに。

''' <summary>
''' 自分が格納されているコントロールのハンドルを取得する
''' </summary>
''' <param name="Value">調べるコントロール</param>
''' <returns></returns>
''' <remarks></remarks>
Private Function FindHandle(ByVal Value As Windows.Forms.Control) As Integer
    For Each i As Object In Value.Controls
        If i.handle = Me.Handle Then
            Return Value.Handle
       Else
            If i.Controls.count > 0 Then
                Dim a As Integer = FindHandle(i)
                If a <> 0 Then
                    Return a
                End If
            End If
       End If
    Next
    Return 0
End Function

次に「SetPararenthWnd」プロシを下記に修正
''' <summary>
''' オーナーウインドウを変更する
''' </summary>
''' <remarks></remarks>
Public Sub SetPararenthWnd()
    If Me.ParentForm IsNot Nothing AndAlso _SetParant = False Then
        Me.Enabled = False
        Dim MePanel As Integer = FindHandle(Me.ParentForm)
        Class_API.SetParent(hwndEdit, MePanel)
        lpord = Class_API.SetWindowLong(hwndEdit, Class_API.GWL_WNDPROC, AddressOf TateControlProc)
        _SetParant = True
    End If
End Sub
次に前回ではユーザーコントロールのLoadイベント時にオーナーウインドウを変更する「SetPararenthWnd」プロシージャを呼び出していました。
なんとかユーザーコントロール内だけでクローズしたいので、ユーザーコントロールのLoadイベント時で宣言してみました。
デザイン時の問題が発生する件については、「ParentForm」変数を調べることで実行の有無を検証させることで、問題の解決を図ります。
Private Sub _Load(sender As Object, e As System.EventArgs) Handles Me.Load
    Call SetPararenthWnd()
End Sub
残念ながら、このコードでは正常に動作しませんでした。
どうやら親フォームのLoad前にこちらのLoadが呼び出されているようです。タイミング的に逆なら解決なんですが……

また、複数の縦書きを配置する場合、それぞれパネルを事前に配置して呼び出すのであればOKなのですが、直接配置した場合、縦書コントロールが重なってしまうという問題も発生。
SetWindowPos(hwndEdit, 0, Me.Left, Me.Top, Me.Width, Me.Height, Class_API.SWP_NOMOVE Or Class_API.SWP_NOZORDER)

こちらを宣言していると、デザイン時にエディットソフトが強制終了する問題すら発生。
lpord = Class_API.SetWindowLong(hwndEdit, Class_API.GWL_WNDPROC, AddressOf TateControlProc)


結果として、デザイン時に不要だと思うコードについては識別することにしました。また、LoadについてもonLoad時に変更。
デザインモードかそうでないかは「Me.DesignMode」で判別できます。
(なお、このプロパティですが、.NetFrameworksのバージョンで変わってくるので要注意)

コードの整理も含めて、縦書きコントロールを作成し制御するコードをユーザーコントロール内ではなく、クラスを別途用意してそこに宣言することに。

あまり好きじゃないのですが、デザイン時はCreateWindow関数で縦書きコントロールは宣言せず、ユーザーコントロールが縦書きコントロールのように擬態することにしました。

<ユーザーコントロール内>

Dim c As Class_CreateTateRichText = Nothing
''' <summary>
''' 読み込み時の初期処理
''' </summary>
''' <param name="e"></param>
''' <remarks></remarks>
Protected Overrides Sub OnLoad(e As System.EventArgs)
    MyBase.OnLoad(e)
    If Me.DesignMode = False AndAlso Me.ParentForm IsNot Nothing AndAlso Me.ParentForm.Handle <> Me.Handle Then
       c = New Class_CreateTateRichText
       c.ADD(Me)
       Call c.ChangeParent(Me.Parent)
       Me.BackColor = Color.Transparent
       Me.BorderStyle = Windows.Forms.BorderStyle.None
    Else
       Me.BackColor = Color.White
       Me.BorderStyle = Windows.Forms.BorderStyle.Fixed3D
    End If
End Sub
Protected Overrides Sub Finalize()
   c.lpord = Class_API.SetWindowLong(c.Handle, Class_API.GWL_WNDPROC, c.lpord)
   Class_API.DestroyWindow(c.Handle)
   MyBase.Finalize()
End Sub
ユーザーコントロールを二つ宣言した場合では、方向キーを押しても問題ない状況まで行きました。ただし、直接テキストボックスなど、フォーカスを受け取ることができるコントロールを配置してやると相変わらずそっちにフォーカスが移動してしまいます。
調べていくと、たぶんですが、モードレスダイアログという扱いになっているっぽい。
なので、ダイアログ同士であればフォーカスの移動は発生しないのですが、親ウインドウが存在する状況でフォーカスを受け取れる環境では、ダイアログ内にコントロールが存在するという判定を行い、コントロール間の移動が発生するんだろうなと、予測を立てました。
実際、WindowProcではなくCallBackProcの方を覗いてみると、受け取っているメッセージの種類は
264:msg=0x87 (WM_GETDLGCODE) hwnd=0x60818 wparam=0x0 lparam=0x0 result=0x0
265:msg=0x87 (WM_GETDLGCODE) hwnd=0x60818 wparam=0x0 lparam=0x0 result=0x0
266:msg=0x87 (WM_GETDLGCODE) hwnd=0x60818 wparam=0x0 lparam=0x0 result=0x0
267:msg=0x87 (WM_GETDLGCODE) hwnd=0x60818 wparam=0x0 lparam=0x0 result=0x0
268:msg=0x87 (WM_GETDLGCODE) hwnd=0x60818 wparam=0x0 lparam=0x0 result=0x0
269:msg=0x87 (WM_GETDLGCODE) hwnd=0x60818 wparam=0x0 lparam=0x0 result=0x0
270:msg=0x87 (WM_GETDLGCODE) hwnd=0x60818 wparam=0x0 lparam=0x0 result=0x0
こんな感じでありましたし(「Debug.Print」でログを吐き出したもののコピペ。)

つまるところ、Windowsは次のフォーカスがある場合は、一部のキーを特殊キーとして処理しているという……これをどうにかするには、特殊キー云々を判定しているプリプロセスをどうにかしてやる必要があるっポイ。
通常のデザイナー等で配置した場合は下記のコードやらプロセスメッセージをオーバーライドしてやればいけそうですが、、APIで宣言した関係で、どうにもこういう風にはいかないっポイ。
Private Sub _PreviewKeyDown(sender As System.Object, e As System.Windows.Forms.PreviewKeyDownEventArgs) Handles MyBase.PreviewKeyDown
    Select Case e.KeyCode
        Case Keys.Up, Keys.Down, Keys.Left, Keys.Right
            e.IsInputKey = True
    End Select
End Sub
一応、ためしに
Protected Overrides Function ProcessDialogKey(keyData As System.Windows.Forms.Keys) As Boolean
   Return MyBase.ProcessDialogKey(keyData)
End Function
このようにユーザーコントロール内で宣言して、イベントメッセージを拾っているのか確認してみました。
当然といえば当然なんでしょうねぇ。受けっていませんでした。
もし拾えていればよかったんですが、、、


以下、ユーザーコントロール内で試してみたけどダメだった一覧
#Region "ダメコード(失敗)"
    Public Overrides Function PreProcessMessage(ByRef msg As System.Windows.Forms.Message) As Boolean
        If c IsNot Nothing Then msg.HWnd = c.Handle
        Return MyBase.PreProcessMessage(msg)
    End Function
    Protected Overrides Function ProcessDialogKey(keyData As System.Windows.Forms.Keys) As Boolean
        Debug.Print(keyData.ToString & vbCrLf)
        Select Case keyData
            Case Keys.Up, Keys.Down, Keys.Left, Keys.Right
                Return True
            Case Else
                Return MyBase.ProcessDialogKey(keyData)
        End Select
    End Function

    Private Sub _PreviewKeyDown(sender As System.Object, e As System.Windows.Forms.PreviewKeyDownEventArgs) Handles MyBase.PreviewKeyDown
        Select Case e.KeyCode
            Case Keys.Up, Keys.Down, Keys.Left, Keys.Right
                e.IsInputKey = True
        End Select
    End Sub

    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
        If c IsNot Nothing Then m.HWnd = c.Handle
        MyBase.WndProc(m)
    End Sub

    Protected Overrides Function IsInputKey(keyData As System.Windows.Forms.Keys) As Boolean
        Select Case keyData
            Case Keys.Up, Keys.Down, Keys.Left, Keys.Right
                Return True
            Case Else
                Return MyBase.IsInputKey(keyData)
        End Select
    End Function
#End Region

に、煮詰まってきた(汗)

0 件のコメント:

コメントを投稿