DDEML関数のラッパを作っているはずが、何故か今Managed C++でSusieプラグインのラッパを作っています。本当に何故なんでしょうね。意外に構造が単純なので不慣れなC++でもどうとでもなるもんですね。しかしWin64時代に残るんだろうか、Susieプラグイン。
さて、TextBoxのも放置していますが、今回はToolBar。こいつの未実装メッセージを一つメソッドに仕立てましょう。ターゲットはTB_HITTESTです。
TB_HITTESTは、指定したクライアント座標に一番近いボタンのインデックスを返すメッセージ。単純なクリックならButtonClickイベントのハンドラ引数でToolBarButtonが取得できますから不要なんですが、D&D操作やダブルクリックの処理には対応できません。
TB_HITTESTは、SendMessageのlParamに確認するPoint構造体の参照を入れ、返値でボタンのインデックスを取得します。返値にはクセがあって、普通にボタン上ではボタンのインデックスが返るんですが、StyleがToolBarButtonStyle.Separatorになっているものはその右隣のボタンのインデックスを負数にしたものが返ります。例えば左から3つ目がセパレータの場合それをテストすると-3(右隣のボタンが3なので)になります。またボタンの範囲外だった場合、Yが0よりも小さければボタンの数の負数にしたもの(4つボタンがあるのなら-4。セパレータもボタン扱いです)が、0よりも大きければ更にその-1(4つボタンなら-5)と言うことになります。
実装する際には、ToolBarButtonClick.Buttonと同じようにToolBarButtonを返すのが自然でしょう。範囲外の場合はどうせ範囲外なのですからnullで問題ないかと思います。そうすると注意するのはセパレータの場合のみですね。
ところで、SendMessageは当然Win32APIなわけで、DllImport/Declareする必要があります。これがイヤな人も多いでしょう。今回や過去TextBoxで扱ったようなのは、自身にメッセージを送信しているわけですから、つまりは自身のWndProcで処理していると言うことになります。そしてWndProcはprotectedですから、(派生クラスを作りさえすれば)呼ぼうと思えば自由に呼べると言うことになります。難点は構造体の参照を渡す必要があるときに自前でマーシャリングしなければならないという点辺りでしょうか。そしてそれによって得られるものはDllImport/Declareを一応しなくなったと言うだけで、あまりメリットはなさそうです。セキュリティ的にも、確かWndProcを呼ぶのにUnmanagedCodeの実行許可が必要なはずですから利点にはなり得ないでしょう。
今回はメソッドが実質一つな簡単なクラスなので、XMLドキュメントは省略。
まずはC#のコードです。
using System; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; namespace HongliangSoft.Utilities { public class LittleConvinientToolBar : ToolBar { [DllImport("user32.dll")] private static extern int SendMessage(IntPtr window, int msg, IntPtr wParam, ref Point lParam); //Pointはクライアント座標です。 public ToolBarButton GetButtonAt(Point point) { const int wmHitTest = 0x400 + 69; int index = SendMessage(this.Handle, wmHitTest, IntPtr.Zero, ref point); //インデックスが正の数の場合は問題ないですね? if (index >= 0) return this.Buttons[index]; //負の数の場合は多少問題があります……。 index = -index; //indexがボタン総数よりも小さい場合、それはセパレータです。 //右隣のインデックスを指すので、返すのは-1したインデックスのボタン。 if (index < this.Buttons.Count) return this.Buttons[index - 1]; //通常はpoint.Yが0よりも大きい場合は-(Buttons.Count + 1)です。 //もしpoint.Yが0以上なのにindexが-Buttons.Countの場合、 //それは右端がセパレータになっています……。 if (index == this.Buttons.Count && point.Y >= 0) return this.Buttons[index - 1]; //どれにも該当しない場合はボタンの外です。 return null; } public ToolBarButton GetButtonAt(int x, int y) { return GetButtonAt(new Point(x, y)); } //参考までに、WndProcを使用する場合。 private ToolBarButton GetButtonAtWithWndProc(Point point) { const int wmHitTest = 0x400 + 69; //まずはメモリ領域を確保。 IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(point)); try { //確保した領域にPointをコピー。 Marshal.StructureToPtr(point, ptr, true); //送信するメッセージを作成。 Message m = Message.Create(this.Handle, wmHitTest, IntPtr.Zero, ptr); this.WndProc(ref m); int index = m.Result.ToInt32(); if (index >= 0) return this.Buttons[index]; index = -index; if (index < this.Buttons.Count) return this.Buttons[index - 1]; if (index == this.Buttons.Count && point.Y >= 0) return this.Buttons[index - 1]; return null; } finally { //確保した領域は忘れず確実に解放。 Marshal.FreeCoTaskMem(ptr); } } } }
そしてVB.NETのコード。
Namespace HongliangSoft.Utilities Public Class LittleConvinientToolBar Inherits ToolBar Private Declare Function SendMessage Lib "user32.dll" ( _ ByVal window As IntPtr, ByVal msg As Integer, _ ByVal wParam As IntPtr, ByRef lParam As Point) As Integer Public Function GetButtonAt(ByVal p As Point) As ToolBarButton Const wmHitTest As Integer = &H400 + 69 Dim index As Integer = SendMessage(Me.Handle, wmHitTest, IntPtr.Zero, p) 'インデックスが正の数の場合は問題ないですね? If index >= 0 Then Return Me.Buttons(index) End If '負の数の場合は多少問題があります……。 index = -index 'indexがボタン総数よりも小さい場合、それはセパレータです。 '右隣のインデックスを指すので、返すのは-1したインデックスのボタン。 If index < Me.Buttons.Count Then Return Me.Buttons(index - 1) End If '通常はp.Yが0よりも大きい場合は-(Buttons.Count + 1)です。 'もしp.Yが0以上なのにindexが-Buttons.Countの場合、 'それは右端がセパレータになっています……。 If index = Me.Buttons.Count AndAlso p.Y >= 0 Then Return Me.Buttons(index - 1) End If 'どれにも該当しない場合はボタンの外です。 Return Nothing End Function Public Function GetButtonAt(ByVal x As Integer, _ ByVal y As Integer) As ToolBarButton Return GetButtonAt(New Point(x, y)) End Function '参考までに、WndProcを使用する場合。 Private Function GetButtonAtWithWndProc(ByVal p As Point) As ToolBarButton Const wmHitTest As Integer = &H400 + 69 'まずはメモリ領域を確保。 Dim ptr As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(p)) Try '確保した領域にPointをコピー。 Marshal.StructureToPtr(p, ptr, True) '送信するメッセージを作成。 Dim m As Message = Message.Create(Me.Handle, wmHitTest, _ IntPtr.Zero, ptr) Me.WndProc(m) Dim index As Integer = m.Result.ToInt32() If index >= 0 Then Return Me.Buttons(index) End If index = -index If index < Me.Buttons.Count Then Return Me.Buttons(index - 1) End If If index = Me.Buttons.Count AndAlso p.Y >= 0 Then Return Me.Buttons(index - 1) End If Return Nothing Finally '確保した領域は忘れず確実に解放。 Marshal.FreeCoTaskMem(ptr) End Try End Function End Class End Namespace