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

原文: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新增了一个修改就是限制了最小宽度和最小高度,但是效果不是很满意,有闪烁,以后再完善吧。

 点击下载源码

时间: 2024-08-31 09:07:25

WPF换肤之二:可拉动的窗体的相关文章

WPF换肤之七:异步

原文:WPF换肤之七:异步 在WinForm时代,相信大家都遇到过这种情形,如果在程序设计过程中遇到了耗时的操作,不使用异步会导致程序假死.当然,在WPF中,这种情况也是存在的,所以我们就需要寻找一种解决方法来让程序界面响应和耗时操作异步进行,那么上述假死的情况就不会发生了. 这一节就着重讲解异步以及线程和界面交互. 异步使用方式(APM模式) 在上节中,我们给一个普通的Window窗口做了换肤处理,呈现出了一个非常酷的时区浏览小工具.当然,这一节,我们还是以那个工具为主,为其增加天气预报功能,

WPF换肤之一:创建圆角窗体

原文:WPF换肤之一:创建圆角窗体     我们都期望自己的软件能够有一套看上去很吸引人眼球的外衣,使得别人看上去既专业又有美感.这个系列就带领着大家一步一步的讲解如何设计出一套自己的WPF的窗体皮肤,如果文中有任何错误或者不足,还请指出.     WPF是微软大战略中的一个重心所在,学习WPF可谓是一举多得:首先,学习WPF可以让你了解SilverLight的80%:其次,XAML语言可以让你快速的入手WCF和WF:更甚者,就是WPF给予DX渲染核心,抛弃了传统WINFORM以GDI+为主的渲

WPF换肤之八:创建3D浏览效果

原文:WPF换肤之八:创建3D浏览效果 上节中,我们展示了WPF中的异步以及界面线程交互的方式,使得应用程序的显示更加的流畅.这节我们主要讲解如何设计一个具有3D浏览效果的天气信息浏览器. 效果显示 下面我们看截图: 是不是能够感受到一种与众不同的感觉.如果你能够感受到它的与众不同,这也是我本节所要达到的目标. 实现方式 上面的只是一个简单的3D图形,它的产生需要依赖于WPF中的MeshGeometry3D对象,这个对象按照微软官方的解释就是用于生成3D形状的三角形基元,它有三个比较重要的属性:

WPF换肤之四:界面设计和代码设计分离

原文:WPF换肤之四:界面设计和代码设计分离 说起WPF来,除了总所周知的图形处理核心的变化外,和Winform比起来,还有一个巨大的变革,那就是真正意义上做到了界面设计和代码设计的分离.这样可以让美工和程序分开进行,而不是糅合在一块,这样做的好处当然也是显而易见的:提高了开发效率. 原先的设计方式 在我们之前设计的代码中,每当添加一个新的窗体的时候,我总是会在这个新的窗体的XAML文件中加入如下的代码,以便使样式能够应用上去: View Code <Window x:Class="Wpf

WPF换肤之六:酷炫的时区浏览小精灵

原文:WPF换肤之六:酷炫的时区浏览小精灵 由于工作需要,经常要查看到不同地区的 当前时间,以前总是对照着时区表来进行加减运算,现在有了这个小工具以后,感觉省心了不少.下面是软件的截图: 效果图赏析   在界面上,有能够冉冉升起的太阳或者月亮,有缓慢飘动的浮云,有青葱翠绿的花叶, 当然,也有显目的时区显示.如果要是放在WinForm时代,要实现这样的界面,真的是繁琐和复杂,但是在WPF中,利用XAML控制前台界面,利用CodeBehind控制窗口拖动,日月变换等等逻辑,真的是简便而且效果强大.其

WPF换肤设计原理浅析_C#教程

WPF换肤的设计原理,利用资源字典为每种皮肤资源添加不同的样式,在后台切换皮肤资源文件. 截图 上图中,第一张图采用规则样式,第二张图采用不规则样式,截图的时候略有瑕疵. 资源字典 规则样式资源Skin.RegularStyle.xaml <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microso

仿酷狗音乐播放器开发日志十八——换肤功能的实现二:改变控件和窗体透明度(附挂件类源码)

转载请说明原出处,谢谢~~          昨天把大致布局分析了一下,昨天晚上把布局写好实现了,今天把大致的功能完成了一下,现在的外观已经和原酷狗的换肤界面完全一样,其中的调整播放列表透明度和设置整个软件透明度的代码已经完成了,先把效果图贴一下,然后开发说开发过程.    开发步骤一:        布局的部分我就不说了,昨天已经分析了,只要用好素材,花点时间就能把界面效果做出来,其中"官方皮肤"和 "我的皮肤"调用CTabLayout可以实现两个界面的切换,如图

仿酷狗音乐播放器开发日志二十——换肤功能背景图片控件的制作(附源码)

转载请说明原出处,谢谢~~           <仿酷狗音乐播放器开发日志二十>里做了换肤功能的一部分,今天完成其他的部分.酷狗的换肤窗口里的背景图可以让用户选择来换图,原酷狗的背景图的小图标,有normal.hover.down等多种状态,鼠标移动上去便会发生变化.如图        当鼠标经过时会有个黑色图片覆盖,边框变为浅蓝色.并且在他上面显示出作者的一些信息.要实现这个效果应该另外开发一个控件来支持动态的信息展示效果.我把这个小控件的开发过程和源码发一下,给学习做duilib控件的新手

C#Winform换肤

问题描述 如何实现C#Winform换肤,把相应的皮肤文件放到DEBUG里面或者里面的一个文件夹里面,然后可以把这些文件自动加载到类似于toolstripmenuitem中,直接点击换肤,就在后面列出全部皮肤,点击相应的皮肤文件即可换肤,就像暴风影音某个版本的换肤那样 解决方案 解决方案二:用第三方控件吧,像dotNetBar2,devexpress,等等一大堆解决方案三:myblog解决方案四:皮肤很好1解决方案五:现在不是有皮肤的一个包提供下载吗你去搜搜我忘记了两三年前我用过解决方案六:用皮