[C#|VS] Form為None 時如何加入拖易、改變大小視窗事件

當 Form 的 BorderStyle 變成 None 的時候,視窗的雙擊最大化、拖拉視窗位置、拖拉改變視窗大小等等視窗事件都會無效。這時,就需要使用到 “WndProc” 也就是 Windows 視窗訊息接收,來恢復消失的那些視窗功能。

-程式碼 WndProc

以下是 WndProc 所需要的宣告。裡面有好幾個 “Rectangle” 代表的是允許讓使用者點選更改視窗大小的範圍,一共有上、下、左、右。

//Result Message
const int HT_MOVE = 2;
const int HT_LEFT = 10;
const int HT_RIGHT = 11;
const int HT_TOP = 12;
const int HT_BOTTOM = 15;
const int HT_TOPLEFT = 13;
const int HT_TOPRIGHT = 14;
const int HT_BOTTOMLEFT = 16;
const int HT_BOTTOMRIGHT = 17;

//WndProc Message
const int WM_NCHITTEST = 0x84;
const int WM_SIZE = 5;

//設定使用者可拖曳區域
const int BasicDis = 10; //允許使用者可拖曳區域大小
Rectangle Top { get { return new Rectangle(0, 0, this.ClientSize.Width, BasicDis); } }
Rectangle Left { get { return new Rectangle(0, 0, BasicDis, this.ClientSize.Height); } }
Rectangle Bottom { get { return new Rectangle(0, this.ClientSize.Height - BasicDis, this.ClientSize.Width, BasicDis); } }
Rectangle Right { get { return new Rectangle(this.ClientSize.Width - BasicDis, 0, BasicDis, this.ClientSize.Height); } }
Rectangle TopMove { get { return new Rectangle(0, BasicDis, this.ClientSize.Width, TopPanel.Height - BasicDis); } }

Rectangle TopLeft { get { return new Rectangle(0, 0, BasicDis, BasicDis); } }
Rectangle TopRight { get { return new Rectangle(this.ClientSize.Width - BasicDis, 0, BasicDis, BasicDis); } }
Rectangle BottomLeft { get { return new Rectangle(0, this.ClientSize.Height - BasicDis, BasicDis, BasicDis); } }
Rectangle BottomRight { get { return new Rectangle(this.ClientSize.Width - BasicDis, this.ClientSize.Height - BasicDis, BasicDis, BasicDis); } }

這邊使用複寫 WndProc 來擷取並使用 “WM_NCHITTEST” 這個 Windows Messages。
可以看到當 Windows Messages 為 WM_NCHITTEST 的時候可以判斷現在的滑鼠座標是否在剛剛宣告的 Rectangle 範圍裡面,如果有的話就運用 message.Result 來回傳對應的結果代碼。

protected override void WndProc(ref Message message)
{
  var cursor = this.PointToClient(Cursor.Position);

  if (message.Msg == WM_NCHITTEST) // WM_NCHITTEST
  {
    if (TopLeft.Contains(cursor)) message.Result = (IntPtr)HT_TOPLEFT;
    else if (TopRight.Contains(cursor)) message.Result = (IntPtr)HT_TOPRIGHT;
    else if (BottomLeft.Contains(cursor)) message.Result = (IntPtr)HT_BOTTOMLEFT;
    else if (BottomRight.Contains(cursor)) message.Result = (IntPtr)HT_BOTTOMRIGHT;
    else if (TopMove.Contains(cursor)) message.Result = (IntPtr)HT_MOVE;

    else if (Top.Contains(cursor)) message.Result = (IntPtr)HT_TOP;
    else if (Left.Contains(cursor)) message.Result = (IntPtr)HT_LEFT;
    else if (Right.Contains(cursor)) message.Result = (IntPtr)HT_RIGHT;
    else if (Bottom.Contains(cursor)) message.Result = (IntPtr)HT_BOTTOM;

    return;
 }
 // Normal WndProc message
 else
   base.WndProc(ref message);
}

Windows Messages List 參考網頁: https://wiki.winehq.org/List_Of_Windows_Messages

使用 WndProc 來恢復視窗事件的隱藏問題

如果只是一般單純的 Windows Form 到這邊應該就沒什麼問題了。但如果你的 Form 裡面是含有 Panel、TableLayout等等物件蓋住 Form 的時候,這時候你就會發現剛剛設定的 WndProc 起不了作用,因為我們設定的是 Form 的 WndProc,這時候 Panel 在 Form 的上層,就會以 Panel 的 WndProc 為主。要解決這種窘境的話,就須要複寫 Panel 的 WndProc 來達到該效果:

-程式碼 繼承並複寫 Panel

using System;
using System.Windows.Forms;

namespace NoneReSizeEx
{
    public partial class ReSizePanel : Panel
    {
        private const int WM_NCHITTEST = 0x84;		
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WM_NCHITTEST:                 
					m.Result = (IntPtr)(-1); // HTTRANSPARENT
					return;
            }

            // Do the normal message handling
            base.WndProc(ref m);
        }
    }
}

如果對於不熟悉如何加入”自訂控制項”的話,可以參考這篇文章。

-範例程式

範例中就是四邊都是 Panel,如果對於文章看不懂的可以參考範例裡面的作法喔!

檔案名稱:NoneReSizeEx_HelloCode.zip
檔案大小:265 KB
存放空間:pCloud、AsusWebStorge
檔案下載:下載點1下載點2

-結語

Form 為 None 時要恢復原本的視窗功能,這個方法我找了很多資料,尤其是有 Panel 的時候,資料更為稀少,希望有幫助到大家,如有任何問題歡迎留言互相增進技術唷!

有話大聲說!