在這一章,我主要是實現對圓角窗體的拖動,改變大小功能。
拖動自繪窗體的步驟
首先,通過上節的設計,我們知道了如何設計一個圓角窗體,通過XAML代碼量,我們發現設置這個窗體是多么的簡單。但是如何讓窗體能夠進行Resize呢?
在Winform時代,我們通過WndProc(ref Message m)處理窗體界面消息來實現,那么在WPF中是否也是如此呢?
其實在WPF中,雖說封裝比較緊密,但是對於處理界面消息這塊,和WINFORM一樣,未有所改變。下面請看具體設計:
首先,由於要涉及到和Win32交互,我們需要訂閱SourceInitialized事件。
public MsgWindow() { InitializeComponent(); this.SourceInitialized += new EventHandler(WSInitialized); }
然后,由於涉及到SourceInitialized Event,我們就需要使用到HwndSource,它主要功能就是WPF放入到Win32窗體中。讓我們看看WindowSourceInitialized事件:
void WSInitialized(object sender, EventArgs e) { hs = PresentationSource.FromVisual(this) as HwndSource; hs.AddHook(new HwndSourceHook(WndProc)); }
接下來我們看到,窗體Hook了一個 HwndSourceHook的委托,這個委托能夠接受所有經過Windows的消息。我們來看看WndProc函數:
Dictionary<int, int> messages = new Dictionary<int, int>(); private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { Debug.Print(string.Format("窗體消息: {0}, wParam: {1} , lParam: {2}", msg.ToString(), wParam.ToString(), lParam.ToString())); if (messages.ContainsKey(msg) == false) { messages.Add(msg, msg); // 添加接收到的WIndows信息到ListBox中 listMsg.Items.Add(msg); } return new IntPtr(0); }
這個函數會接收到所有windows消息,打印到Debug台上。
接下來,知道了事件處理流程,我們開始討論拖拉窗體的問題。
首先,我們先給窗體添加一個ResetCursor事件,以便於拖拉結束后,恢復鼠標指針:
<Window x:Class="WpfApplication1.MsgWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="TestWindow" Height="391" Width="418" WindowStyle="None" AllowsTransparency="True" Background="Transparent" OpacityMask="White" ResizeMode="NoResize" PreviewMouseMove="ResetCursor" WindowStartupLocation="CenterScreen">
其次,我們給Border元素添加一個MouseMove事件,用來顯示鼠標特定情況下的鼠標指針形狀(如達到了窗體邊緣,則變換為拖拉的鼠標形狀),同時添加一個PreviewMouseDown事件,用來進行Resize操作(也就是鼠標左鍵按下,開始進行拖放):
<Border BorderThickness="5" BorderBrush="DarkGreen" CornerRadius="10,10,10,10" MouseMove="DisplayResizeCursor" PreviewMouseDown="Resize" Name="top">
這樣,當事件添加好以后,我們轉換到后台代碼:
由於窗體總共有八個地方可以進行拖拉,分別是Top,TopRight,Right,BottomRight,Bottom,BottomLeft,Left,TopLeft,那么我們先聲明一個Enum:
public enum ResizeDirection { Left = 1, Right = 2, Top = 3, TopLeft = 4, TopRight = 5, Bottom = 6, BottomLeft = 7, BottomRight = 8, }
在Win32中,由於61440+1 代表左邊,61440+2代表右邊,一次類推,所以我們需要進行如下設計:
[DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); private void ResizeWindow(ResizeDirection direction) { SendMessage(hs.Handle, WM_SYSCOMMAND, (IntPtr)(61440 + direction), IntPtr.Zero); }
其中,WM_SYSCOMMAND為Int類型,初始值為0x112,它的解釋如下:
WM_SYSCOMMAND |
0x112 |
A window receives this message when the user chooses a command from the Window menu (formerly known as the system or control menu) or when the user chooses the maximize button, minimize button, restore button, or close button. |
這樣,通過上面的函數,我們就可以實現窗體的Resize,下面我們來響應鼠標事件:
首先是窗體的ResetCursor事件,這個主要是用來恢復鼠標形狀:
private void ResetCursor(object sender, MouseEventArgs e) { if (Mouse.LeftButton != MouseButtonState.Pressed) { this.Cursor = Cursors.Arrow; } }
然后我們來看看DisplayResizeCursor事件,它主要是用來改變鼠標形狀,當鼠標達到一定區域,則顯示拖拉的鼠標形狀(<->):
其計算方式,請參看下圖:
private void DisplayResizeCursor(object sender, MouseEventArgs e) { Border clickBorder = sender as Border; Point pos = Mouse.GetPosition(this); double x = pos.X; double y = pos.Y; double w= this.Width; double h= this.Height; this.label1.Content = x + "--" + y; if (x <= relativeClip & y <= relativeClip) // left top { this.Cursor = Cursors.SizeNWSE; } if (x >= w - relativeClip & y <= relativeClip) //right top { this.Cursor = Cursors.SizeNESW; } if (x >= w - relativeClip & y >= h - relativeClip) //bottom right { this.Cursor = Cursors.SizeNWSE; } if (x <= relativeClip & y >= h - relativeClip) // bottom left { this.Cursor = Cursors.SizeNESW; } if ((x >= relativeClip & x <= w - relativeClip) & y <= relativeClip) //top { this.Cursor = Cursors.SizeNS; } if (x >= w - relativeClip & (y >= relativeClip & y <= h - relativeClip)) //right { this.Cursor = Cursors.SizeWE; } if ((x >= relativeClip & x <= w - relativeClip) & y > h - relativeClip) //bottom { this.Cursor = Cursors.SizeNS; } if (x <= relativeClip & (y <= h - relativeClip & y >= relativeClip)) //left { this.Cursor = Cursors.SizeWE; } }
最后就是Resize的函數,和上面的計算方式類似,只是拖拉的時候需要調用ResizeWindow函數來改變大小:
private void Resize(object sender, MouseButtonEventArgs e) { Border clickedBorder = sender as Border; Point pos = Mouse.GetPosition(this); double x = pos.X; double y = pos.Y; double w = this.Width; double h = this.Height; if (x <= relativeClip & y <= relativeClip) // left top { this.Cursor = Cursors.SizeNWSE; ResizeWindow(ResizeDirection.TopLeft); } if (x >= w - relativeClip & y <= relativeClip) //right top { this.Cursor = Cursors.SizeNESW; ResizeWindow(ResizeDirection.TopRight); } if (x >= w - relativeClip & y >= h - relativeClip) //bottom right { this.Cursor = Cursors.SizeNWSE; ResizeWindow(ResizeDirection.BottomRight); } if (x <= relativeClip & y >= h - relativeClip) // bottom left { this.Cursor = Cursors.SizeNESW; ResizeWindow(ResizeDirection.BottomLeft); } if ((x >= relativeClip & x <= w - relativeClip) & y <= relativeClip) //top { this.Cursor = Cursors.SizeNS; ResizeWindow(ResizeDirection.Top); } if (x >= w - relativeClip & (y >= relativeClip & y <= h - relativeClip)) //right { this.Cursor = Cursors.SizeWE; ResizeWindow(ResizeDirection.Right); } if ((x >= relativeClip & x <= w - relativeClip) & y > h - relativeClip) //bottom { this.Cursor = Cursors.SizeNS; ResizeWindow(ResizeDirection.Bottom); } if (x <= relativeClip & (y <= h - relativeClip & y >= relativeClip)) //left { this.Cursor = Cursors.SizeWE; ResizeWindow(ResizeDirection.Left); } }
最后效果圖如下所示:
源碼下載
PS:20121130新增了一個修改就是限制了最小寬度和最小高度,但是效果不是很滿意,有閃爍,以后再完善吧。