1. 程式人生 > >WPF 下無邊框窗體改變大小和移動

WPF 下無邊框窗體改變大小和移動

       最近一直在學習 WPF,看著別人做的WPF程式那麼漂亮,眼紅啊~ 很多漂亮的程式都是無邊框的。於是無邊框視窗操作就是最重要的了。無邊框視窗的操作一直以來相關的資料就很少。WPF 下的就更少了,有的大多是無邊框窗體的移動。在得到群裡高人的指點,再查了一些資料之後,終於把問題解決了。

      廢話不多說,直接來看看如何實現吧!其實現原理很簡單:攔截並處理 Windows 訊息:WM_NCHITTEST。

      WPF 處理 Windows 訊息的模式和 WinForm 不一樣了。Window 類裡沒有 WndProc 函數了,想要擷取 Windows 訊息必須藉助 HwndSource 新增 Hook。

protected override void OnSourceInitialized(EventArgs e)
{
    base.OnSourceInitialized(e);
    HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
    if (hwndSource != null)
    {
        hwndSource.AddHook(new HwndSourceHook(this.WndProc));
    }
}

protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    return IntPtr.Zero;
}

    OK,WndProc 註冊完成之後就可以通過 WndProc 函式完成對Windows訊息的處理了。可以發現,這裡的 WndProc 和標準的 Win32 訊息迴圈很像,只是多了一個 ref bool handled 引數,對於該引數MSDN是這樣說明的:指示該訊息是否已處理的值。如果該訊息已處理,請將值設定為 true;否則請將其設定為 false 在下面我們將會使用到這個引數數。
private const int WM_NCHITTEST = 0x0084;
private readonly int agWidth = 12; //拐角寬度
private readonly int bThickness = 4; // 邊框寬度
private Point mousePoint = new Point(); //滑鼠座標
 
protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    switch (msg)
    {
        case WM_NCHITTEST:
        this.mousePoint.X = (lParam.ToInt32() &0xFFFF);
        this.mousePoint.Y = (lParam.ToInt32() >> 16);

        測試滑鼠位置#region 測試滑鼠位置

         // 視窗左上角
         if (this.mousePoint.Y - this.Top <= this.agWidth 
            && this.mousePoint.X - this.Left <= this.agWidth) 
        {
            handled = true;
            return new IntPtr((int)HitTest.HTTOPLEFT);
        }
        // 視窗左下角    
         else if (this.ActualHeight + this.Top - this.mousePoint.Y <= this.agWidth 
            && this.mousePoint.X - this.Left <= this.agWidth)
        {
            handled = true;
            return new IntPtr((int)HitTest.HTBOTTOMLEFT);
        }
        // 視窗右上角
         else if (this.mousePoint.Y - this.Top <= this.agWidth 
            && this.ActualWidth + this.Left - this.mousePoint.X <= this.agWidth)
        {
            handled = true;
            return new IntPtr((int)HitTest.HTTOPRIGHT);
        }
        // 視窗右下角
         else if (this.ActualWidth + this.Left - this.mousePoint.X <= this.agWidth 
            && this.ActualHeight + this.Top - this.mousePoint.Y <= this.agWidth)
        {
            handled = true;
            return new IntPtr((int)HitTest.HTBOTTOMRIGHT);
        }
        // 視窗左側
         else if (this.mousePoint.X - this.Left <= this.bThickness)
        {
            handled = true;
            return new IntPtr((int)HitTest.HTLEFT);
        }
        // 視窗右側
         else if (this.ActualWidth + this.Left - this.mousePoint.X <= this.bThickness)
        {
            handled = true;
            return new IntPtr((int)HitTest.HTRIGHT);
        }
        // 視窗上方
         else if (this.mousePoint.Y - this.Top <= this.bThickness)
        {
            handled = true;
            return new IntPtr((int)HitTest.HTTOP);
        }
        // 視窗下方
         else if (this.ActualHeight + this.Top - this.mousePoint.Y <= this.bThickness)
        {
            handled = true;
            return new IntPtr((int)HitTest.HTBOTTOM);
        }
        else // 視窗移動
        {
            handled = true;
            return new IntPtr((int)HitTest.HTCAPTION);
        }
        #endregion
    }
    return IntPtr.Zero;
}

     從上面的程式碼可以看出,工作原理很簡單:擷取 WM_NCHITTEST 訊息,獲得滑鼠座標,再在你希望的地方返回不同的訊息以模擬滑鼠的狀態即可。需要注意的是,返回訊息之前必須將handled 設為 true。告訴系統你已經處理過該訊息,不然無效果。

    關於 HitTest 是自定義的列舉類,裡面包含了滑鼠的各種訊息。

1public enum HitTest:int
{
    HTERROR = -2,
    HTTRANSPARENT = -1,
    HTNOWHERE = 0,
    HTCLIENT = 1,
    HTCAPTION = 2,
    HTSYSMENU = 3,
    HTGROWBOX = 4,
    HTSIZE = HTGROWBOX,
    HTMENU = 5,
    HTHSCROLL = 6,
    HTVSCROLL = 7,
    HTMINBUTTON = 8,
    HTMAXBUTTON = 9,
    HTLEFT = 10,
    HTRIGHT = 11,
    HTTOP = 12,
    HTTOPLEFT = 13,
    HTTOPRIGHT = 14,
    HTBOTTOM = 15,
    HTBOTTOMLEFT = 16,
    HTBOTTOMRIGHT = 17,
    HTBORDER = 18,
    HTREDUCE = HTMINBUTTON,
    HTZOOM = HTMAXBUTTON,
    HTSIZEFIRST = HTLEFT,
    HTSIZELAST = HTBOTTOMRIGHT,
    HTOBJECT = 19,
    HTCLOSE = 20,
    HTHELP = 21,
}