WPF换肤之二:可拉动的窗体

简介: 原文:WPF换肤之二:可拉动的窗体让我们接着上一章: WPF换肤之一:创建圆角窗体 来继续。 在这一章,我主要是实现对圆角窗体的拖动,改变大小功能。 拖动自绘窗体的步骤 首先,通过上节的设计,我们知道了如何设计一个圆角窗体,通过XAML代码量,我们发现设置这个窗体是多么的简单。
原文: 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新增了一个修改就是限制了最小宽度和最小高度,但是效果不是很满意,有闪烁,以后再完善吧。

 点击下载源码

目录
相关文章
WPF控件和窗体一起放大一起缩小
WPF控件和窗体一起放大一起缩小
216 0
|
C# 容器
WPF框架下,窗体的嵌套显示
WPF框架下,窗体的嵌套显示
212 0
|
C# 索引
WPF实用指南二:移除窗体的图标
原文:WPF实用指南二:移除窗体的图标 WPF没有提供任何功能来移除窗体上的icon图标。一般的做法是设置一个空白的图标,如下图1: 这种做法在窗体边框与标题之间仍然会保留一片空白。
1234 0
|
C#
WPF去除窗体边框及白色边框
原文:WPF去除窗体边框及白色边框        0
1451 0
|
C# Windows
WPF 设置类库项目为启动项,设置窗体跟随。
原文:WPF 设置类库项目为启动项,设置窗体跟随。 1、添加用于启动的类Program.cs,需要一个静态的Main函数入口。
1021 0
|
C# Windows
WPF实用指南一:在WPF窗体的边框中添加搜索框和按钮
原文:WPF实用指南一:在WPF窗体的边框中添加搜索框和按钮 在边框中加入一些元素,在应用程序的界面设计中,已经开始流行起来。
1357 0
|
C# 程序员 测试技术
WPF之动态换肤
原文:WPF之动态换肤 如何实现换肤呢,对于复杂的换肤操作,如,更换按钮样式、窗口样式等,我们需要写多个资源字典来表示不同的皮肤,通过动态加载不同的资源字典来实现换肤的效果;对于简单的换肤操作,如更改背景颜色、设置窗体透明度,这种换肤操作,我们就不能使用上面的方法了,这个时候,我们只要在一个全局对象中添加几个属性,如背景颜色、前景颜色、窗体透明度等,然后,再绑定这几个属性就能达到我们想要的效果。
983 0
|
C#
WPF 透明窗体
原文:WPF 透明窗体 窗体属性中设置:Background="Transparent" AllowsTransparency="True" WindowStyle="None"注:单独设置 Background="Transparent" 窗体默认显示为黑色。
1057 0
|
C#
wpf窗体定位
原文:wpf窗体定位 据WPF外包小编了解,通常,不需要在屏幕上明确定位窗口。而是简单地将WindowState属性设置为Normal,并忽略其他所有细节。另一方面,很少会将WindowStartupLocation属性设置为Manual,并使用Left属性和Right属性明确设置窗口的位置。
862 0
|
C# C++ Windows
WPF中不规则窗体与WindowsFormsHost控件的兼容问题完美解决方案
原文:WPF中不规则窗体与WindowsFormsHost控件的兼容问题完美解决方案          首先先得瑟一下,有关WPF中不规则窗体与WindowsFormsHost控件不兼容的问题,网上给出的解决方案不能满足所有的情况,是有特定条件的,比如  WPF中不规则窗体与WebBrowser控件的兼容问题解决办法。
1297 0