2014年7月21日月曜日

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

横書きのテキストボックスコントロールはあるのですが、どうにも縦書きのテキストボックスコントロールがない。
いろいろ調べてフリーで公開しているやつなども使用しているのですが、よくエラーが発生してどうにもならないケースがある。
というわけで、仕方がなく自作することに挑戦してみます。


テキストボックスコントロールを縦書きにするプロパティは見つかりません。
ということは、通常デフォルトで提供されているものではなく、モジュールからコントロールを呼び出してSendMessageでグリグリするしかないのかぁ、、、と。
う~ん、.net を使用しているのにWindowsAPIを使うのはなんか抵抗があるんですが、ほかに方法が思い浮かばないのは情けないなぁと思いつつ、まずはLoadLibrary関数の宣言から。

まず、コントロールを作成するためのCreateWindow関数を作成します。今回は縦書きなので、拡張スタイル付きで作成するのでCreateWindowEX関数を宣言します。
''' <summary>
''' オーバーラップウインドウ、ポップアップウインドウ、子ウインドウのいずれかを拡張スタイル付きで作成します。拡張スタイルが指定できること以外はCreateWindow関数と同じです。
''' </summary>
''' <param name="dwExStyle">拡張ウインドウスタイル</param>
''' <param name="lpClassName">登録されているクラス名</param>
''' <param name="lpWindowName">ウインドウ名</param>
''' <param name="dwStyle">ウインドウスタイル</param>
''' <param name="x">ウインドウの横方向の位置</param>
''' <param name="y">ウインドウの縦方向の位置</param>
''' <param name="nWidth">ウインドウの幅</param>
''' <param name="nHight">ウインドウの高さ</param>
''' <param name="hWndParent">親ウインドウまたはオーナーウインドウのハンドル</param>
''' <param name="hMenu">メニューハンドルまたは子拡張子</param>
''' <param name="hInstance">アプリケーションインスタンスのハンドル</param>
''' <param name="lpParam">ウインドウ作成データ</param>
''' <returns></returns>
''' <remarks></remarks>
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Public Shared Function CreateWindowEx( _
     ByVal dwExStyle As Integer, _
     ByVal lpClassName As String, _
     ByVal lpWindowName As String, _
     ByVal dwStyle As Integer, _
     ByVal x As Integer, _
     ByVal y As Integer, _
     ByVal nWidth As Integer, _
     ByVal nHight As Integer, _
     ByVal hWndParent As IntPtr, _
     ByVal hMenu As IntPtr, _
     ByVal hInstance As IntPtr, _
     ByVal lpParam As IntPtr) As IntPtr
 End Function
次に、CreateWindowEX関数で使用する定数の宣言。本来であれば使用するものだけでいいんでしょうが、まぁ後のことを考えて一応全部事前に宣言しておきます。
#Region "dwExStyle:ウインドウ拡張スタイル"
    ''' <summary>ファイルのドラッグアンドドロップを受け付ける</summary>
    Friend Const WS_EX_ACCEPTFILES As Integer = &H10
    ''' <summary>トップレベルウインドウが託すバーに上に置かれる</summary>
    Friend Const WS_EX_APPWINDOW As Integer = &H40000
    ''' <summary>境界線の縁が沈んで見えるウインドウ</summary>
    Friend Const WS_EX_CLIENTEDGE As Integer = &H200
    ''' <summary>タイトルバーに[?]ボタンを追加します。子ウインドウ上でそのボタンがクリックされると、 そのウインドウにWM_HELPメッセージが送信されます。</summary>
    Friend Const WS_EX_CONTEXTHELP As Integer = &H400
    ''' <summary>Tabキーや十字キーで子ウインドウ間を移動できる</summary>
    Friend Const WS_EX_CONTROLPARENT As Integer = &H10000
    ''' <summary>二重境界線ウインドウ</summary>
    Friend Const WS_EX_DLGMODALFRAME As Integer = &H1
    ''' <summary>Windows 2000: レイヤーウインドウ</summary>
    Friend Const WS_EX_LAYERED As Integer = &H80000
    ''' <summary>左揃えのプロパティをもつウインドウ(既定)</summary>
    Friend Const WS_EX_LEFT As Integer = &H0
    ''' <summary>垂直スクロールバーがウインドウの左側に置かれる。アラビア語やヘブライ語をサポートしてるシステムでのみ有効。</summary>
    Friend Const WS_EX_LEFTSCROLLBAR As Integer = &H4000
    ''' <summary>左から右への読み取り順序を持つプロパティを持ったウィンドウ(既定)</summary>
    Friend Const WS_EX_LTRREADING As Integer = &H0
    ''' <summary>MDI子ウインドウ</summary>
    Friend Const WS_EX_MDICHILD As Integer = &H40
    ''' <summary>このスタイルを指定した子ウインドウを作成したり破棄するとき親ウインドウに WM_PARENTNOTIFYメッセージを送信しない</summary>
    Friend Const WS_EX_NOPARENTNOTIFY As Integer = &H4
    ''' <summary>WS_EX_CLIENTEDGEWS_EX_WINDOWEDGEの組み合わせ</summary>
    Friend Const WS_EX_OVERLAPPEDWINDOW As Integer = &H300
    ''' <summary>WS_EX_WINDOWEDGEWS_EX_TOOLWINDOWWS_EX_TOPMOSTの組み合わせ</summary>
    Friend Const WS_EX_PALETTEWINDOW As Integer = &H188
    ''' <summary>右揃えのプロパティをもつウインドウ</summary>
    Friend Const WS_EX_RIGHT As Integer = &H1000
    ''' <summary>垂直スクロールバーがウインドウの右側に置かれる。(既定)アラビア語やヘブライ語をサポートしてるシステムでのみ有効。</summary>
    Friend Const WS_EX_RIGHTSCROLLBAR As Integer = &H0
    ''' <summary>右から左への読み取り順序を持つプロパティを持ったウィンドウ</summary>
    Friend Const WS_EX_RTLREADING As Integer = &H2000
    ''' <summary>ユーザー入力を受け付けない項目用の立体的に見える境界スタイルを持つウィンドウ</summary>
    Friend Const WS_EX_STATICEDGE As Integer = &H20000
    ''' <summary>ツールウインドウ</summary>
    Friend Const WS_EX_TOOLWINDOW As Integer = &H80
    ''' <summary>最前面ウインドウ</summary>
    Friend Const WS_EX_TOPMOST As Integer = &H8
    ''' <summary>透過ウインドウ</summary>
    Friend Const WS_EX_TRANSPARENT As Integer = &H20
    ''' <summary>盛り上がった縁の境界線を持つウインドウ</summary>
    Friend Const WS_EX_WINDOWEDGE As Integer = &H100
#End Region

#Region "dwStyle:ウインドウスタイル"
    ''' <summary>オーバーラップウインドウ</summary>
    Friend Const WS_OVERLAPPED As Integer = &H0
    ''' <summary>ポップアップウインドウ</summary>
    Friend Const WS_POPUP As Integer = &H80000000
    ''' <summary>子ウインドウ</summary>
    Friend Const WS_CHILD As Integer = &H40000000
    ''' <summary>ウインドウ作成時に最小化</summary>
    Friend Const WS_MINIMIZE As Integer = &H20000000
    ''' <summary>可視ウインドウ</summary>
    Friend Const WS_VISIBLE As Integer = &H10000000
    ''' <summary>無効なウインドウ</summary>
    Friend Const WS_DISABLED As Integer = &H8000000
    ''' <summary>兄弟ウインドウをクリップ</summary>
    Friend Const WS_CLIPSIBLINGS As Integer = &H4000000
    ''' <summary>このスタイルが指定された親ウインドウは子ウインドウの部分を描画しない</summary>
    Friend Const WS_CLIPCHILDREN As Integer = &H2000000
    ''' <summary>タイトルバーを持つウインドウ</summary>
    Friend Const WS_CAPTION As Integer = &HC00000
    ''' <summary>境界線を持つウインドウ</summary>
    Friend Const WS_BORDER As Integer = &H800000
    ''' <summary>ダイアログボックススタイルな境界を持つウインドウ</summary>
    Friend Const WS_DLGFRAME As Integer = &H400000
    ''' <summary>垂直スクロールバーを持つウインドウ</summary>
    Friend Const WS_VSCROLL As Integer = &H200000
    ''' <summary>水平スクロールバーを持つウインドウ</summary>
    Friend Const WS_HSCROLL As Integer = &H100000
    ''' <summary>タイトルバーにシステムメニューを持つウインドウ</summary>
    Friend Const WS_SYSMENU As Integer = &H80000
    ''' <summary>サイズ変更フレームを持つウインドウ</summary>
    Friend Const WS_THICKFRAME As Integer = &H40000
    ''' <summary>コントロールグループの最初と最後に来る子ウインドウにこのスタイルを指定</summary>
    Friend Const WS_GROUP As Integer = &H20000
    ''' <summary>Tabキーによるフォーカス移動を有効にする</summary>
    Friend Const WS_TABSTOP As Integer = &H10000
    ''' <summary>最小化ボタンを持つウインドウ</summary>
    Friend Const WS_MINIMIZEBOX As Integer = &H20000
    ''' <summary>最大化ボタンを持つウインドウ</summary>
    Friend Const WS_MAXIMIZEBOX As Integer = &H10000
    ''' <summary>WS_OVERLAPPEDWS_CAPTIONWS_SYSMENUWS_THICKFRAMEWS_MINIMIZEBOXWS_MAXIMIZEBOXの組み合わせ</summary>
    Friend Const WS_OVERLAPPEDWINDOW As Integer = &HCF0000
    ''' <summary>WS_POPUPWS_BORDERWS_SYSMENUの組み合わせ</summary>
    Friend Const WS_POPUPWINDOW As Integer = &H80880000
#End Region
#Region "エディットスタイル"
    ''' <summary>複数行エディットの指定</summary>
    Friend Const ES_MULTILINE As Integer = 4
    ''' <summary>左詰で表示</summary>
    Friend Const ES_LEFT As Integer = 0
    ''' <summary>センタリングで表示(ES_MULTILINE のときのみ有効)</summary>
    Friend Const ES_CENTER As Integer = 1
    ''' <summary>右詰で表示(ES_MULTILINE のときのみ有効)</summary>
    Friend Const ES_RIGHT As Integer = 2
    ''' <summary>入力した文字を大文字にする</summary>
    Friend Const ES_UPPERCASE As Integer = 8
    ''' <summary>入力した文字を小文字にする</summary>
    Friend Const ES_LOWERCASE As Integer = 16
    ''' <summary>パスワード入力</summary>
    Friend Const ES_PASSWORD As Integer = 32
    ''' <summary>自動縦スクロール(ES_MULTILINE のときのみ有効)ユーザーが最下行で[Enter]キーを押すと、テキストを自動的に上にスクロールします。</summary>
    Friend Const ES_AUTOVSCROLL As Integer = &H40
    ''' <summary>自動横スクロールユーザーが行末に文字を入力すると、エディットコントロール内のテキストが自動的に右スクロールします。</summary>
    Friend Const ES_AUTOHSCROLL As Integer = &H80
    ''' <summary>入力不可。ユーザーによるテキストの入力や編集をできないようにします。</summary>
    Friend Const ES_READONLY As Integer = &H800
    ''' <summary>リターンキーを受け付ける(ES_MULTILINE のときのみ有効)ダイアログボックス内の複数行エディットコントロールにテキストを入力しているときに[Enter]キーが押されると、改行が挿入されるようにします。</summary>
    Friend Const ES_WANTRETURN As Integer = &H1000
    ''' <summary>数字のみ入力可能</summary>
    Friend Const ES_NUMBER As Integer = &H2000
    ''' <summary>フォーカスを失っても選択範囲を表示する。選択されているテキストは、コントロールがフォーカス持っていない場合も反転表示されます。</summary>
    Friend Const ES_NOHIDESEL As Integer = &H100
    ''' <summary>入力文字をOEM文字セットに変換する</summary>
    Friend Const ES_OEMCONVERT As Integer = &H400
    ''' <summary>ダブルクリックで自動的に単語を選択します。</summary>
    Friend Const ECO_AUTOWORDSELECTION As Integer = &H1
    ''' <summary>コントロールが入力フォーカスを失うときに、現在選択されている範囲が保存され、次にフォーカスを得たときに同じ領域が選択されている状態になります。</summary>
    Friend Const ES_SAVESEL As Integer = &H8000
    ''' <summary>ES_SELECTIONBAR スタイルと同じです。</summary>
    Friend Const ECO_SELECTIONBAR As Integer = &H1000000
    ''' <summary>縦書きリッチエディットコントロールを作成します。アジアの言語のバージョンのみで利用可能です。</summary>
    Friend Const ES_VERTICAL As Integer = &H400000

    Friend Const EM_SETOPTIONS As Integer = 1101
    ''' <summary>オプションをlParamで指定した値に設定します</summary>
    Friend Const ECOOP_SET As Integer = 1
    ''' <summary>現在のオプションにlParamで指定したオプションを結合します</summary>
    Friend Const ECOOP_OR As Integer = 2
    ''' <summary>現在のオプションのなかでlParamで指定したオプションのみを残します</summary>
    Friend Const ECOOP_AND As Integer = 3
    ''' <summary>現在のオプションとlParamで指定したオプションのXOR値を新しいオプションに設定します。</summary>
    Friend Const ECOOP_XOR As Integer = 4
#End Region

とりあえず使いそうなものを列挙しました。

最後に、よく使うであろうSendMessageを宣言しておきます。
 ''' <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 SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
  End Function

 ''' <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 SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As System.Text.StringBuilder) As IntPtr
 End Function
二つ宣言したにはわけがあります。テキストボックスへの文字列の入出力について、「ByVal lParam As IntPtr」よりも「ByVal lParam As System.Text.StringBuilder」の方が楽だからです。
これでは足らないのですが、現在選択されているテキストの位置とか選択されている文字列、文字列の長さなど、様々な情報を取得するためのパラメーターなどもありますが、まずはまぁテキストボックスを作るのが目的です。

これらのコードを格納するべく、「Class_API」というクラスを作成してそこの中にぶち込みます。
次にユーザーコントロールを配置してコードを開き、CreateWindowEX関数でコントロールを作成します。
テキストボックスのクラス名は「EDIT」なので、引数には「EDIT」を指定してやります。
なお、ボタンは「BUTTON」でリッチテキストの場合は「RichEdit」と指定してやるそうです。
hwndEdit = Class_API.CreateWindowEx(Class_API.WS_EX_CLIENTEDGE, "EDIT", "", 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)
次に縦書きなので、SendMessageで縦書き指定をしてみます。
Class_API.SendMessage(hwndEdit, Class_API.EM_SETOPTIONS, Class_API.ECOOP_OR, Class_API.ES_VERTICAL)

ここでパラメーターの「ES_VERTICAL」で縦書きを指定してやるのですが、リッチエディットコントロールを作成するとなっており、どうやらテキストボックスでは無理そうな予感
    ''' <summary>縦書きリッチエディットコントロールを作成します。アジアの言語のバージョンのみで利用可能です。</summary>
    Friend Const ES_VERTICAL As Integer = &H400000
とりあえずこれで作ってみたところ
予感した通り、うまくいかなかった。
しかたがないので、
hwndEdit = Class_API.CreateWindowEx(Class_API.WS_EX_CLIENTEDGE, "EDIT", "", 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)
hwndEdit = Class_API.CreateWindowEx(Class_API.WS_EX_CLIENTEDGE, "RichEdit", "", 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)
に変更して実行。
あんれぇ? 今度は表示されない状況になりました。
う~ん、うまくいかない。

hwndEditの戻り値は0になってる。う~ん、テキストボックスの方は表示されてリッチテキストボックスの方がエラーを起こすというのは解せないんですけど、、、
状況から察するに、"RichEdit"が見つからないのだと思われます。とはいえ、マイクロソフトのMSDSに書いてあるクラス名を書いたのですが、どうやら本家のMSDSが間違っているようっぽい?
じゃぁ、ということでリッチテキストのVersion1ではなくVersion2のほうである「RICHEDIT_CLASS」を指定してみましたが、、、
Ou……同じく何も表示されない。

ここまでくると、テキストボックスの指定がたまたまだったのか? と思って「BUTTON」を指定してやると
うん、きちんと表示されている。

やはり、クラス名の指定の問題のようです。表示されました。
で、さらに調べていくと、
LoadLibrary("Riched32.dll")
と、DLLを読み込まないといけないっポイ? 

''' <summary>
''' 指定された実行可能モジュールを呼び出し側プロセスのアドレス空間内にマップします。
''' </summary>
''' <param name="lpFileName">モジュールのファイル名</param>
''' <returns>関数が成功すると、モジュールのハンドルが帰ります。失敗するとNULLが帰ります。拡張エラー情報を取得するには、関数を使います。</returns>
''' <remarks></remarks>
<System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet:=System.Runtime.InteropServices.CharSet.Auto, SetLastError:=True)> _
Public Shared Function LoadLibrary(ByVal lpFileName As String) As IntPtr
End Function

しかたがないので、こいつを追加しました。
で、さらにグリグリやっているのですが、、、「RICHEDIT_CLASS」を指定すると表示されなかったりするし「"Riched20.dll"」の方は動作しない。
よくわかんない状況に陥ってしまいました。

とりあえず、リッチテキストのViersion4.1DLL"Msftedit.dll"でクラスが「"MSFTEDIT_CLASS"」なので、それを指定してみますと
表示されない……

なんといいますか、こんなところで躓くとは……
さらに調べると
Private const string MSFTEDIT_CLASS = ”RICHEDIT50W”;
という文字が!?
MSFTEDIT_CLASSって、変数名かよorz
となると、格納する値はどれが妥当か? という話になります。

MSFTEDIT_CLASS」でググったところ、やはり実態は「RICHEDIT50W」のようです。
となると「RICHEDIT_CLASS」も変数名で中に入れる「値」があるのか!?と思ってググってみましたが、さっぱりという結果にw

しかたがないので、Msftedit.dllロードしてMSFTEDIT_CLASSRICHEDIT50Wを指定してやることにしました。
 
テキストボックスの表示と、縦書きがようやくうまくいった。
ここまで長かった……

0 件のコメント:

コメントを投稿