[C#]在.NET程式中要如何指定Windows的ClassName去接收視窗的訊息

[C#]在.NET程式中要如何指定Windows的ClassName去接收視窗的訊息

有使用過.NET程式做視窗訊息的接收的應該都會知道,好像沒有比較直接的方法去設定視窗的ClassName。就算去覆寫Form.CreateParams也不太行,若是指定的ClassName沒有註冊過,運行起來會丟出例外。


    {
        public Form1()
        {
            InitializeComponent();
        }

        protected override CreateParams CreateParams
        {
            get
            {
                base.CreateParams.ClassName = "test";
                return base.CreateParams;
            }
        }
    }

 

若是指定的是有註冊過的ClassName,像是Button之類的。


        protected override CreateParams CreateParams
        {
            get
            {
                base.CreateParams.ClassName = "Button";
                return base.CreateParams;
            }
        }
        ...

 

運行起來視窗的ClassName也是不太對勁。

image

 

因此在.NET程式中做視窗訊息的傳送,多半都還是使用視窗標題去找尋視窗的Handle,這樣就衍生出視窗標題會有重覆、或是要隱藏接收訊息的視窗、隱藏的視窗會閃爍或突然出現之類的問題。

 

為了解決這樣的問題我們得使用比較低階的方法,去跟作業系統註冊視窗的名稱,然後建立視窗出來。這邊筆者稍微整理了一個簡單的MessageReceiver類別以方便使用:


    {
        #region Struct
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        struct WNDCLASS
        {
            public uint style;
            public WndProcDelegate lpfnWndProc;
            public int cbClsExtra;
            public int cbWndExtra;
            public IntPtr hInstance;
            public IntPtr hIcon;
            public IntPtr hCursor;
            public IntPtr hbrBackground;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpszMenuName;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpszClassName;
        }
        #endregion


        #region Delegate
        delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); 
        #endregion


        #region DllImport
        /// <summary>
        /// Registers the class W.
        /// </summary>
        /// <param name="lpWndClass">The lp WND class.</param>
        /// <returns></returns>
        [DllImport("user32.dll", SetLastError = true)]
        static extern UInt16 RegisterClassW(
            [In] ref WNDCLASS lpWndClass
        );

        /// <summary>
        /// Creates the window ex W.
        /// </summary>
        /// <param name="dwExStyle">The dw ex style.</param>
        /// <param name="lpClassName">Name of the lp class.</param>
        /// <param name="lpWindowName">Name of the lp window.</param>
        /// <param name="dwStyle">The dw style.</param>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="nWidth">Width of the n.</param>
        /// <param name="nHeight">Height of the n.</param>
        /// <param name="hWndParent">The h WND parent.</param>
        /// <param name="hMenu">The h menu.</param>
        /// <param name="hInstance">The h instance.</param>
        /// <param name="lpParam">The lp param.</param>
        /// <returns></returns>
        [DllImport("user32.dll", SetLastError = true)]
        static extern IntPtr CreateWindowExW(
           UInt32 dwExStyle,
           [MarshalAs(UnmanagedType.LPWStr)]
       string lpClassName,
           [MarshalAs(UnmanagedType.LPWStr)]
       string lpWindowName,
           UInt32 dwStyle,
           Int32 x,
           Int32 y,
           Int32 nWidth,
           Int32 nHeight,
           IntPtr hWndParent,
           IntPtr hMenu,
           IntPtr hInstance,
           IntPtr lpParam
        );

        /// <summary>
        /// Defs the window proc W.
        /// </summary>
        /// <param name="hWnd">The h WND.</param>
        /// <param name="msg">The MSG.</param>
        /// <param name="wParam">The w param.</param>
        /// <param name="lParam">The l param.</param>
        /// <returns></returns>
        [DllImport("user32.dll", SetLastError = true)]
        static extern IntPtr DefWindowProcW(
            IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam
        );

        /// <summary>
        /// Destroys the window.
        /// </summary>
        /// <param name="hWnd">The h WND.</param>
        /// <returns></returns>
        [DllImport("user32.dll", SetLastError = true)]
        static extern bool DestroyWindow(
            IntPtr hWnd
        );
        #endregion


        #region Const
        private const int ERROR_CLASS_ALREADY_EXISTS = 1410; 
        #endregion


        #region Static Var
        private static Dictionary<IntPtr, MessageReceiver> _receiverPool;
        #endregion


        #region Private Static Property
        /// <summary>
        /// Gets the m_ receiver pool.
        /// </summary>
        /// <value>
        /// The m_ receiver pool.
        /// </value>
        public static Dictionary<IntPtr, MessageReceiver> m_ReceiverPool 
        {
            get
            {
                return _receiverPool ?? (_receiverPool = new Dictionary<IntPtr, MessageReceiver>());
            }
        }
        #endregion


        #region Private Property
        /// <summary>
        /// Gets or sets a value indicating whether [m_ disposed].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [m_ disposed]; otherwise, <c>false</c>.
        /// </value>
        private bool m_Disposed { get; set; }

        /// <summary>
        /// Gets or sets the M_HWND.
        /// </summary>
        /// <value>
        /// The M_HWND.
        /// </value>
        private IntPtr m_Hwnd { get; set; }
        #endregion

        #region Event
        public event EventHandler<MessageEventArgs> WndProc; 
        #endregion


        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="MessageReceiver" /> class.
        /// </summary>
        /// <param name="className">Name of the class.</param>
        /// <param name="title">The title.</param>
        /// <exception cref="System.Exception">Could not register window class</exception>
        public MessageReceiver(string className, string title)
        {
            if (string.IsNullOrEmpty(className))
                className = Guid.NewGuid().ToString();

            // Create WNDCLASS
            var wndClass = new WNDCLASS() 
            {
                lpszClassName = className,
                lpfnWndProc = CustomWndProc
            };


            UInt16 class_atom = RegisterClassW(ref wndClass);

            int last_error = Marshal.GetLastWin32Error();

            if (class_atom == 0 && last_error != ERROR_CLASS_ALREADY_EXISTS)
            {
                throw new System.Exception("Could not register window class");
            }

            // Create window
            m_Hwnd = CreateWindowExW(
                0,
                className,
                title,
                0,
                0,
                0,
                0,
                0,
                IntPtr.Zero,
                IntPtr.Zero,
                IntPtr.Zero,
                IntPtr.Zero
            );

            if (m_Hwnd == IntPtr.Zero)
                return;

            m_ReceiverPool[m_Hwnd] = this;
        }
        #endregion


        #region Private Static Method
        /// <summary>
        /// Customs the WND proc.
        /// </summary>
        /// <param name="hWnd">The h WND.</param>
        /// <param name="msg">The MSG.</param>
        /// <param name="wParam">The w param.</param>
        /// <param name="lParam">The l param.</param>
        /// <returns></returns>
        private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
        {
            if (m_ReceiverPool.ContainsKey(hWnd))
                m_ReceiverPool[hWnd].OnWndProc(new MessageEventArgs(msg, wParam, lParam));

            return DefWindowProcW(hWnd, msg, wParam, lParam);
        }
        #endregion


        #region Private Method
        private void Dispose(bool disposing)
        {
            if (!m_Disposed)
            {
                if (disposing)
                {
                    // Dispose managed resources
                }

                // Dispose unmanaged resources
                if (m_Hwnd != IntPtr.Zero)
                {
                    m_ReceiverPool.Remove(m_Hwnd);
                    DestroyWindow(m_Hwnd);
                    m_Hwnd = IntPtr.Zero;
                }
                m_Disposed = true;
            }
        }
        #endregion


        #region Protected Method
        /// <summary>
        /// Raises the <see cref="E:WndProc" /> event.
        /// </summary>
        /// <param name="e">The <see cref="MessageEventArgs" /> instance containing the event data.</param>
        protected void OnWndProc(MessageEventArgs e)
        {
            if (WndProc == null)
                return;
            WndProc(this, e);
        }
        #endregion


        #region Public Method
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
	}

 


    {
        #region Property
        public uint Message { get; set; }
        public IntPtr wParam { get; set; }
        public IntPtr lParam { get; set; }
        #endregion

        #region Constructor
        public MessageEventArgs(uint message, IntPtr wparam, IntPtr lparam)
        {
            Message = message;
            wParam = wparam;
            lParam = lparam;
        }
        #endregion
    }

 

使用上就只要帶入視窗標題與視窗的ClassName去建立出物件實體,然後繫結事件下去處理就可以了,像是下面這段程式碼,筆者建立了MessageReceiver類別,並用FindWindow透過ClassName找尋出MessageReceiver,然後發送個簡單的訊息給它。


    {
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll")]
        public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

        MessageReceiver m_receiver = new MessageReceiver("testClassName", "testWindowName");
        public Form1()
        {
            InitializeComponent();

            m_receiver.WndProc += m_receiver_WndProc;
        }

        void m_receiver_WndProc(object sender, MessageEventArgs e)
        {
            if ((int)e.Message == 0x401)
            {
                listBox1.Items.Add("Received Message: 0x401");
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            var handle = FindWindow("testClassName", null);

            if (handle == IntPtr.Zero)
                return;

            SendMessage(handle, 0x401, IntPtr.Zero, IntPtr.Zero);
        }
    }

 

運行起來可以看到我們可以透過指定ClassName的方式去傳送訊息了。

image