2014年7月21日月曜日

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



縦書きにはできましたが、まだコントロールとしての動作で不足している部分が見受けられます。
CoreateWindow関数で作ってやっているので、プロパティを変更するにはAPIで宣言するしかないのですが、このままではやはり使いにくい、、、

ということで、作成したコントロールにプロパティを持たせてやろうと思います。


まず最初に気になったのが、コントロールのサイズが固定されてしまう部分。
hwndEdit = Class_API.CreateWindowEx(Class_API.WS_EX_CLIENTEDGE, "RICHEDIT50W", "", Class_API.WS_CHILD + Class_API.WS_VISIBLE + Class_API.ES_MULTILINE, Me.Left, Me.Top, Me.Width, Me.Height, Me.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)
という風に「Me.Left, Me.Top, Me.Width, Me.Height」で位置とサイズを宣言しています。
コントロールを作成するとき、事前にサイズと位置を固定し変更しないのであれば定数に固定値を入れてやればいいのですが、実際はデザイン時しているのは可変したいですし、コントロールをDOCKしているときはウインドウのサイズが変更するのに合わせて可変させたいわけです。

なのでまず、コントロールに「Location」と「Size」及び「top」「left」「width」「Height」当たりのプロパティをつける方向で追加したいと考えます。

で、それらを操作するAPI関数といえば、「GetWindowRect」関数と「SetWindowPos」関数の二つになるかなと思われます。
前者は位置とサイズの取得に、後者は設定に使用する形です。

まずはそれぞれの定数を含めた宣言から。なお、「GetWindowRect」は構造体に値を格納するため、それに合わせた構造体を宣言する必要があります。

#Region "GetWindowRect関連"
    ''' <summary>ウインドウの位置及びサイズ情報格納用構造体</summary>
    Structure RECT
        ''' <summary>左位置</summary>
        Public left As Integer
        ''' <summary>上位置</summary>
        Public top As Integer
        ''' <summary>右位置</summary>
        Public right As Integer
        ''' <summary>下位置</summary>
        Public bottom As Integer
    End Structure
    ''' <summary>
    ''' 指定されたウインドウの左上端と右下端の座標をスクリーン座標で取得します。
    ''' </summary>
    ''' <param name="hWnd">ウインドウのハンドル</param>
    ''' <param name="lpRect">ウインドウの座標値</param>
    ''' <returns></returns>
    ''' <remarks>スクリーン座標は、表示画面の左上端が0,0となります</remarks>
    <System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True, CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
    Public Shared Function GetWindowRect(ByVal hWnd As IntPtr, ByRef lpRect As RECT) As Boolean
    End Function
#End Region

#Region "SetWindowPos関連"
    ''' <summary>
    ''' 子ウインドウ、ポップアップウインドウ、またはトップレベルウインドウのサイズ、位置及びZオーダーを変更します。
    ''' </summary>
    ''' <param name="hWnd">ウインドウのハンドル</param>
    ''' <param name="hWndInsertAfter">配置順序のハンドル</param>
    ''' <param name="x">横方向の位置</param>
    ''' <param name="y">縦方向の位置</param>
    ''' <param name="cx"></param>
    ''' <param name="cy">高さ</param>
    ''' <param name="uFlags">ウインドウ位置のオプション</param>
    ''' <returns></returns>
    ''' <remarks>これらのウインドウは、その画面上での表示に従って順序が決められます。最前面にあるウインドウは最も高いランクを与えられ、Zオーダーの先頭におかれます</remarks>
    <System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True, CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
    Public Shared Function SetWindowPos(ByVal hWnd As IntPtr, ByVal hWndInsertAfter As IntPtr, ByVal x As Integer, ByVal y As Integer, cx As Integer, cy As Integer, uFlags As UInteger) As Boolean
    End Function

#Region "hWndInsertAfter用パラメータ"
    ''' <summary>ウインドウをZオーダーの最後に置きます。hWndパラメーターで指定したウインドウが最前面ウインドウだった場合、このウインドウは最前面ウインドウではなくなり、ほかのすべてのウインドウの下に置かれます。</summary>
    Friend Const HWND_BOTTOM As Integer = 1
    ''' <summary>ウインドウを最前面ウインド以外のすべてのウインドウの前(つまり、すべての最前面ウインドウの後ろ)に挿入します。hWndパラメータで指定したウインドウがすでに最善目ぬ引導ではなかった場合、このフラグは意味を持ちません。</summary>
    Friend Const HWND_NOTOPMOST As Integer = -2
    ''' <summary>ウインドウをZオーダーの先頭に置きます。</summary>
    Friend Const HWND_TOP As Integer = 0
    ''' <summary>ウインドウを最前面ウインドではないすべてのウインドウの前に挿入します。子のウインドウは、アクティブでないときにも最前面に表示されます。</summary>
    Friend Const HWND_TOPMOST As Integer = -1
#End Region
#Region "uFlags用パラメータ"
    ''' <summary>この関数を呼び出したスレッドとウインドウを所有するスレッドが異なる入力キューに関連付けられている場合、ウインドウを所有するスレッドへ要求が送られます。こうすると、要求を受け取ったスレッドが要求を処理している間も、関数を呼び出したスレッドの実行が止まってしまうことはありません。</summary>
    Friend Const SWP_ASYNCWINDOWPOS As Integer = &H4000
    ''' <summary>WM_SYNCPAINTメッセージが生成されないようにします。</summary>
    Friend Const SWP_DEFERERASE As Integer = &H2000
    ''' <summary>ウインドウを囲む枠(ウインドウクラスの記述部分で定義されている)を描画します</summary>
    Friend Const SWP_DRAWFRAME As Integer = &H20
    ''' <summary>SetWindoLong関数を使って新しいフレームスタイルの設定を適用します。ウインドウサイズが変更されない場合にも、ウインドウにWM_NCCALCSIZEメッセージを送ります。このフラグを指定しなかった場合、ウインドウサイズが変更される場合にしかWM_NCCALCSIZEメッセージは送られません。</summary>
    Friend Const SWP_FRAMECHANGED As Integer = &H20
    ''' <summary>ウインドウを非表示にします</summary>
    Friend Const SWP_HIDEWINDOWS As Integer = &H80
    ''' <summary>ウインドウをアクティブ化しません。このフラグをセットしなかった場合、ウインドウはアクティブ化され、最前面ウインドウまたは非最前面ウインドウのどちらかのグルプ(hWndInsertAferパラメータの設定による)の最上位にいどうします。</summary>
    Friend Const SWP_NOACTIVATE As Integer = &H10
    ''' <summary>クライアント領域の内容全体を破棄します。このフラグをセットしなかった場合は、クライアント領域の有効な内容が保存され、再配置後のウインドウクライアント領域にコピーしなおされます。</summary>
    Friend Const SWP_NOCOPYBITS As Integer = &H100
    ''' <summary>現在の位置を維持します(XパラメーターとYパラメーターを無視します)</summary>
    Friend Const SWP_NOMOVE As Integer = &H2
    ''' <summary>オーナーウインドウのZオーダーを変更しません。</summary>
    Friend Const SWP_NOOWNERZORDER As Integer = &H200
    ''' <summary>変更結果を再描画しません。このフラグを指定すると、再描画は一切行われません。このフラグは、クライアント領域、非クライアント領域、及び親ウインドウの、子のウインドウが移動した結果あらわれた部分すべてに適用されます。このフラグをセットした場合、ウインドウや親ウインドウの再描画の必要な場合は、アプリケーションで明示的に無効化または再描画しなければなりません。</summary>
    Friend Const SWP_NOREDRAW As Integer = &H8
    ''' <summary>SWP_NOOWNERZORDERと同じです。</summary>
    Friend Const SWP_NOREPOSITION As Integer = SWP_NOOWNERZORDER
    ''' <summary>ウインドウにWM_WINDOWPOSCHANGINGメッセージが送られないようにします。</summary>
    Friend Const SWP_NOSENDCHANGING As Integer = &H400
    ''' <summary>現在のサイズを維持します(cxパラメーターとcyパラメータを無視します)</summary>
    Friend Const SWP_NOSIZE As Integer = &H1
    ''' <summary>現在のZオーダーを維持します(hWndInsertAfterパラメータを無視します)</summary>
    Friend Const SWP_NOZORDER As Integer = &H4
    ''' <summary>ウインドウを表示します。</summary>
    Friend Const SWP_SHOWWINDOW As Integer = &H40
#End Region
#End Region

使うかどうかはともかく、定数も事前に宣言しておきます。

次に、というか最初にやらなければならなかったかもしれませんが、テキストの入出力も同様にやります。
SetWindowText」関数と「GetWindowText」関数あたりを使えば行けるかな? と思ったのですが、結論から言うと「GetWindowText」関数の方がエラーを吐き出してうまくいかない。
仕方がないので「SendMessage」関数の方で実装します。

''' <summary>
''' 縦書きコントロールのサイズの取得と設定
''' </summary>
''' <value>サイズ</value>
''' <returns></returns>
''' <remarks></remarks>
Property SizeText As System.Drawing.Size
     Get
        Dim r As Class_API.RECT
        Class_API.GetWindowRect(hwndEdit, r)
        Return New Size(r.right - r.left, r.bottom - r.top)
     End Get
     Set(value As System.Drawing.Size)
         Dim r As Class_API.RECT
         Class_API.GetWindowRect(hwndEdit, r)
         Class_API.SetWindowPos(hwndEdit, 0, r.left, r.top, value.Width, value.Height, Class_API.SWP_NOMOVE Or Class_API.SWP_NOZORDER)
     End Set
End Property


 ''' <summary>
 ''' コントロールのサイズ変更に合わせて、縦書きボックスのサイズも変更する。
 ''' </summary>
 ''' <param name="sender"></param>
 ''' <param name="e"></param>
 ''' <remarks></remarks>
 Private Sub TateTextBox_SizeChanged(sender As Object, e As System.EventArgs) Handles Me.SizeChanged
     Dim s As System.Drawing.Size = Me.Size
     SizeText = s
 End Sub


 ''' <summary>
 ''' テキストの取得と設定
 ''' </summary>
 ''' <value></value>
 ''' <returns></returns>
 ''' <remarks></remarks>
 Property Texts As String
     Get
         Dim buf As New System.Text.StringBuilder
         buf.Length = 32767
         Class_API.SendMessage(hwndEdit, Class_API.WM_GETTEXT, buf.Length, buf)
         Return buf.ToString
     End Get
     Set(value As String)
         Call Class_API.SetWindowText(hwndEdit, value)
     End Set
 End Property
 
とりあえずうまくいきました。
しかし、アルファベットのフォント情報が違うのか、微妙に違うのがわかります。
テキスト情報のみの受け渡しでしかありませんので仕方がありませんが、文字のフォント情報の取得(ボックスではない)を個別設定したいときは、フォント情報ごと取得できるようなプロパティをどうにかしないといけないかもしれません。

また、メモ帳などでは右クリックでコピーや貼り付けといったメニューが現れますが、リッチテキストボックスの場合は自作してやらないとどうにもなりません。

なんだかデザイナを使わずにやるのはどうにもめんどくさくなってきたぞ、、、

0 件のコメント:

コメントを投稿