Compact FrameworkでWndProc 2009

今週は久しぶりにCompact Frameworkメインで、ライブラリ群の整備に集中していました(・∀・)
現行システムの機種変更とC++→.NET化みたいな話があったりして、その準備として色々派生ができてしまっていたりするライブラリ類のチェックと整理、再統合なんかをば少し。
まあ、新機種の正式発表も8月とかいう話で、そんなに急ぐわけでも無いですが、ライブラリ類ももう少し標準化したいよね、っということで、今月はしばらくそっち方面に注力する予定(・ω・)
っで、その中から小ネタを1つ。


Compact Frameworkでは、Control.WndProcがサポートされないわけですが(´・ω・`)
っで、標準コントロールの拡張をしたいときにはどうするかと言えば、SetWindowLong()でウィンドウプロシージャを書き換えるというのが普通のやりかた。
まあ、ボタンやラベル程度のものであればControl派生でスクラッチしても良いんですが…というか実際そうしているわけですが(・ω・)
でも、既存のC++ソースの中には、描画、ソート、選択とかカスタム処理を色々と入れたListVewなんかもあったりして…っというか、自分が昔作ったものだな、コレ(゚Д゚;)
まあ、Controlベースのカスタムリストとかもあるんですが、ここは既存にあわせてListViewをサブクラス化する方向で対応予定。


っで、サブクラス化処理についても、コントロールで個別にやっているものがあったり、MSDNにあるWndProcHookerベースのものがあったり、SDF方面のMessageControlベースのものがあったりして、ここも標準を用意したいと言うことで今日作ってみたのが下記のソース。

internal delegate int HookProc(HookCode nCode, IntPtr wParam, IntPtr lParam);
internal static class NativeMethods
{
    [DllImport( "coredll.dll", SetLastError = true )]
    internal static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, WndProcDelegate newProc);

    [DllImport( "coredll.dll", SetLastError = true )]
    internal static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr nValue);

    [DllImport( "coredll.dll", SetLastError = true )]
    internal static extern IntPtr CallWindowProc(IntPtr pfn, IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
}
public delegate void WndProcCallback(ref Message m, ref bool handled);
public class MessageHook
{
    private const int GWL_WNDPROC = -4;

    private static Dictionary<IntPtr, MessageHook> hooks = new Dictionary<IntPtr, MessageHook>();

    private IntPtr handle;
    private IntPtr orgWndProc;
    private WndProcDelegate hookedWndProc;
    private List<WndProcCallback> callbacks = new List<WndProcCallback>();

    // サブクラス化
    public static void Subclass(IntPtr handle, WndProcCallback callback)
    {
        MessageHook messageHook;
        if( hooks.TryGetValue( handle, out messageHook ) )
        {
            messageHook.AddHook( callback );
        }
        else
        {
            messageHook = new MessageHook( handle );
            hooks[ handle ] = messageHook;
            messageHook.AddHook( callback );
        }
    }

    // サブクラス解除
    public static void UnSubclass(IntPtr handle, WndProcCallback callback)
    {
        MessageHook messageHook;
        if( hooks.TryGetValue( handle, out messageHook ) )
        {
            messageHook.RemoveHook( callback );
        }
    }

    public static void DefaultWindowProc(ref Message m)
    {
        MessageHook messageHook;
        if( hooks.TryGetValue( m.HWnd, out messageHook ) )
        {
            messageHook.CallWindowProc( ref m );
        }
    }

    public MessageHook(IntPtr handle)
    {
        this.handle = handle;
    }

    public void AddHook(WndProcCallback callback)
    {
        if( callbacks.Count == 0 )
        {
            Subclass();
        }

        callbacks.Add( callback );
    }

    public void RemoveHook(WndProcCallback callback)
    {
        this.callbacks.Remove( callback );

        if( callbacks.Count == 0 )
        {
            Release();
        }
    }

    private void Subclass()
    {
        this.hookedWndProc = new WndProcDelegate( Callback );
        this.orgWndProc = NativeMethods.SetWindowLong( this.handle, GWL_WNDPROC, this.hookedWndProc );
    }

    private void UnSubclass()
    {
        NativeMethods.SetWindowLong( this.handle, GWL_WNDPROC, this.orgWndProc );
    }

    private void Release()
    {
        UnSubclass();

        this.orgWndProc = IntPtr.Zero;
        this.hookedWndProc = null;

        hooks.Remove( handle );
    }

    private IntPtr Callback(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam)
    {
        Message m = Message.Create( hWnd, msg, wParam, lParam );
        bool handled = false;
        foreach( WndProcCallback callback in callbacks )
        {
            callback( ref m, ref handled );
            if( handled == true )
            {
                break;
            }
        }

        if( msg == 2 ) // WM_DESTROY
        //if( msg == 130 ) // WM_NCDESTROY
        {
            Release();
        }

        if( handled == true )
        {
            return m.Result;
        }

        CallWindowProc( ref m );

        return m.Result;
    }

    private void CallWindowProc(ref Message m)
    {
        m.Result = NativeMethods.CallWindowProc( this.orgWndProc, m.HWnd, m.Msg, m.WParam, m.LParam );
    }
}

WndProcHookerやMessageControlでは微妙に気に入らないところがあったので、私家版を作成(´д`;)
使い方としては、自分自身へのメッセージをフックする際はこんなカンジで。

public class MyCustomControl : Control
{
    public MyCustomControl()
    {
        MessageHook.Subclass( this.Handle, WndProc );
    }

    private void WndProc(ref Message m, ref bool handled)
    {
        if( m.Msg == WM_PAINT )
        {
...
        }
    }
}

WM_DESTROYを拾ったタイミングで登録情報のクリアも行うので、ハンドルとコールバックを登録するだけでOK。
また、親へ通知されるメッセージをフックする場合にはこんなカンジで。

public class TreeViewEx : TreeView
{
    Control prevParent = null;

    protected override void OnParentChanged(EventArgs e)
    {
        if( this.prevParent != null )
        {
            MessageHook.UnSubclass( this.prevParent.Handle, WndProc );
        }

        this.prevParent = this.Parent;

        if( this.Parent != null )
        {
            MessageHook.Subclass( this.Parent.Handle, WndProc );
        }

        base.OnParentChanged( e );
    }

    private void WndProc(ref Message m, ref bool handled)
    {
        if( m.Msg == WM_NOTIFY )
        {
...
        }
    }

っというわけで、これでCompact Frameworkのコントロールではサポートされていない機能を使うためのインフラを準備できましたヽ(´ー`)ノ


っで、来週はこの辺を使って、コントロール拡張周りの整理ってところ。
HW系の処理はネットワーク系を除いて整理済みなので、コントロール周りの後はIO系かなな〜(・∀・)