WPF 下無邊框窗體改變大小和移動
阿新 • • 發佈:2018-12-31
最近一直在學習 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,
}