2005年10月06日

ToolBarのあるボタン

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



posted by Hongliang at 23:45| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。