WPF換膚之二:可拉動的窗體


讓我們接着上一章: WPF換膚之一:創建圓角窗體 來繼續。

在這一章,我主要是實現對圓角窗體的拖動,改變大小功能。

拖動自繪窗體的步驟

首先,通過上節的設計,我們知道了如何設計一個圓角窗體,通過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新增了一個修改就是限制了最小寬度和最小高度,但是效果不是很滿意,有閃爍,以后再完善吧。

 點擊下載源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM