出会いセンター NativeWindowクラスを用いたサブクラス化(スナップウィンドウの作成)(C#)

NativeWindowクラスを用いたサブクラス化(スナップウィンドウの作成)

Visual Basicでプログラミングをしたことのある方なら”サブクラス化”という言葉を聞いた事があるのでは ないでしょうか。これはかつてVisual Basicを使っていて、「こんなことができればいいのに!」という 思いを実現するために多くの方が使っていた手法だと思います。ここではそれをC#で行う方法についての話です。

.NETでは継承でも解決できるが…

Visual Basic6.0つまりVBが.NETになる以前ではまだ継承が完全にはサポートされていませんでした。 従って、サブクラス化などという泥臭いことを強いられていたわけですが、 VB.NETでは完全にオブジェクト指向がサポートされたとこにより、継承も思う存分使うことができるように なりました。そうなると例えば、Formクラスなどを継承して、WndProc()メソッドをオーバーライドすることによって 、サブクラス化と同様の結果を得ることができます。これはC#についても同様です。

それで十分ではないかと思われるかもしれません。確かに継承という機構は非常に 強力なため多くの場合問題は解決されるでしょう。しかし、ある程度作ってしまったコントロール クラスに何かしらの機能を付け加えたいという場合や、同時に二つ以上の機能を付加したい場合 などには、わざわざ継承を用いるのは面倒です。こんなときにはフォームにコントロールを貼り付けるような 感覚で機能が追加できたらいいと思われます。このような要望に答えてくれ得るのがサブクラス化でしょう。

Win32APIで直接行うサブクラス化は危険

では、どのようにしてサブクラス化を行えばいいのでしょうか?VB6.0などでは、SetWindowLong()やCallWindowProc() などのWin32 APIを直接呼び出して行っていました。確かに.NETにおいてもこのような方法でサブクラス化を実現できます。 事実このあと紹介するNativeWindowクラスでも内部ではこれらのWin32 APIを使用しています。しかし、ガベージコレクション機能や、 関数ポインタが単なるプロキシである等複雑な状況下にあるマネージコードにおいては、 色々と考慮にいれて非常に高度な技術を要すると思われます。

サブクラス化はNativeWindowクラスで行う

以上のことから、マネージ環境においてはサブクラス化用に予め用意されているNativeWindowクラスを用いるのが 最良の選択であるということです。このクラスの使い方はこちらのMSDNライブラリにサンプルつきで説明がありますので、 ここでは以下のようなサンプルを作ってみました。

スナップウィンドウを作成する

WinAmpなどでおなじみのスナップ機能をフォームに付加するサンプルです。今回のサンプルプログラムでは 画面左端に吸着するようになっています。

#region using
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime.InteropServices;
#endregion

namespace NativeWindow_Subclassing
{
    /// <summary>
    /// Form1 の概要の説明です。
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        /// <summary>
        /// 必要なデザイナ変数です。
        /// </summary>
        private System.ComponentModel.Container components = null;
        private NewNativeWindow nnwWindowPrc;

        #region コンストラクタ
        public Form1()
        {
            //
            // Windows フォーム デザイナ サポートに必要です。
            //
            InitializeComponent();
            nnwWindowPrc = new NewNativeWindow(this);
        }
        #endregion

        #region Dispose処理
        /// <summary>
        /// 使用されているリソースに後処理を実行します。
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }
        #endregion

        #region Windows フォーム デザイナで生成されたコード
        /// <summary>
        /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディタで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            //
            // Form1
            //
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
            this.ClientSize = new System.Drawing.Size(264, 273);
            this.Name = "Form1";
            this.Text = "Form1";

        }
        #endregion

        #region メインエントリポイント
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.Run(new Form1());
        }
        #endregion

    }

    #region NewNativeWindowクラス

    public class NewNativeWindow : NativeWindow
    {
        #region Win32
        private const int WM_MOVING = 0x0216;

        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }
        #endregion

        private Form frmParent;
        private Point pntDragCenter = new Point(int.MaxValue, int.MaxValue);
        private int intDisplacement = 0;
        private const int cintSnapableGap = 10;

        public NewNativeWindow(Form parent)
        {
            parent.HandleCreated += new EventHandler(this.OnHandleCreated);
            parent.HandleDestroyed+= new EventHandler(this.OnHandleDestroyed);
            this.frmParent = parent;
        }

        internal void OnHandleCreated(object sender, EventArgs e)
        {
            AssignHandle(((Form)sender).Handle);
        }

        internal void OnHandleDestroyed(object sender, EventArgs e)
        {
            ReleaseHandle();
        }

        protected override void WndProc(ref Message m)
        {
            if(m.Msg == WM_MOVING)
            {
                if(frmParent.Left <= cintSnapableGap)
                {
                    if(Cursor.Position.X - pntDragCenter.X >0) ++intDisplacement;
                    RECT r = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
                    if(intDisplacement <= cintSnapableGap)
                    {
                        r.left = 0;
                        r.right = frmParent.Width;
                        pntDragCenter = Cursor.Position;
                    }
                    else
                    {
                        r.left = cintSnapableGap + 1;
                        r.right = r.left + frmParent.Width;
                        intDisplacement = 0;
                        pntDragCenter = new Point(int.MaxValue, int.MaxValue);
                    }
                    Marshal.StructureToPtr(r, m.LParam, false);
                }
            }
            base.WndProc(ref m);
        }
    }
    #endregion
}
出会い 出会い 出会い系 出会い 出会い 出会い 出会い 出会い 出会い オオクワガタ 出会い 出会い 出会い 出会い アクセスカウンター 出会い 出会い 出会い系 出会い 出会い 出会い 出会い 出会い 出会い