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

让我们接着上一章: WPF换肤之一:创建圆角窗体 来继续。

在这一章,我主要是实现对圆角窗体的拖动,改变大小功能。

拖动自绘窗体的步骤

首先,通过上节的设计,我们知道了如何设计一个圆角窗体,通过XAML代码量,我们发现设置这个窗体是多么的简单。但是如何让窗体能够进行Resize呢?

在Winform时代,我们通过WndProc(ref Message m)处理窗体界面消息来实现,那么在WPF中是否也是如此呢?

其实在WPF中,虽说封装比较紧密,但是对于处理界面消息这块,和WINFORM一样,未有所改变。下面请看具体设计:

首先,由于要涉及到和Win32交互,我们需要订阅SourceInitialized事件。

  1. public MsgWindow()
  2. {
  3. InitializeComponent();
  4. this.SourceInitialized += new EventHandler(WSInitialized);
  5. }

然后,由于涉及到SourceInitialized Event,我们就需要使用到HwndSource,它主要功能就是WPF放入到Win32窗体中。让我们看看WindowSourceInitialized事件:

  1. void WSInitialized(object sender, EventArgs e)
  2. {
  3. hs = PresentationSource.FromVisual(this) as HwndSource;
  4. hs.AddHook(new HwndSourceHook(WndProc));
  5. }

接下来我们看到,窗体Hook了一个 HwndSourceHook的委托,这个委托能够接受所有经过Windows的消息。我们来看看WndProc函数:

  1. Dictionary<int, int> messages = new Dictionary<int, int>();
  2.  
  3. private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
  4. {
  5. Debug.Print(string.Format("窗体消息: {0}, wParam: {1} , lParam: {2}", msg.ToString(), wParam.ToString(), lParam.ToString()));
  6. if (messages.ContainsKey(msg) == false)
  7. {
  8. messages.Add(msg, msg);
  9. // 添加接收到的WIndows信息到ListBox中
  10. listMsg.Items.Add(msg);
  11. }
  12. return new IntPtr();
  13. }

这个函数会接收到所有windows消息,打印到Debug台上。

接下来,知道了事件处理流程,我们开始讨论拖拉窗体的问题。

首先,我们先给窗体添加一个ResetCursor事件,以便于拖拉结束后,恢复鼠标指针:

  1. <Window x:Class="WpfApplication1.MsgWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="TestWindow" Height="391" Width="418" WindowStyle="None" AllowsTransparency="True" Background="Transparent" OpacityMask="White" ResizeMode="NoResize" PreviewMouseMove="ResetCursor" WindowStartupLocation="CenterScreen">

其次,我们给Border元素添加一个MouseMove事件,用来显示鼠标特定情况下的鼠标指针形状(如达到了窗体边缘,则变换为拖拉的鼠标形状),同时添加一个PreviewMouseDown事件,用来进行Resize操作(也就是鼠标左键按下,开始进行拖放):

  1. <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:

  1. public enum ResizeDirection
  2. {
  3. Left = ,
  4. Right = ,
  5. Top = ,
  6. TopLeft = ,
  7. TopRight = ,
  8. Bottom = ,
  9. BottomLeft = ,
  10. BottomRight = ,
  11. }

在Win32中,由于61440+1 代表左边,61440+2代表右边,一次类推,所以我们需要进行如下设计:

  1. [DllImport("user32.dll", CharSet = CharSet.Auto)]
  2. private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
  3.  
  4. private void ResizeWindow(ResizeDirection direction)
  5. {
  6. SendMessage(hs.Handle, WM_SYSCOMMAND, (IntPtr)( + direction), IntPtr.Zero);
  7. }

其中,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事件,这个主要是用来恢复鼠标形状:

  1. private void ResetCursor(object sender, MouseEventArgs e)
  2. {
  3. if (Mouse.LeftButton != MouseButtonState.Pressed)
  4. {
  5. this.Cursor = Cursors.Arrow;
  6. }
  7. }

然后我们来看看DisplayResizeCursor事件,它主要是用来改变鼠标形状,当鼠标达到一定区域,则显示拖拉的鼠标形状(<->):

其计算方式,请参看下图:

  1. private void DisplayResizeCursor(object sender, MouseEventArgs e)
  2. {
  3. Border clickBorder = sender as Border;
  4.  
  5. Point pos = Mouse.GetPosition(this);
  6. double x = pos.X;
  7. double y = pos.Y;
  8. double w= this.Width;
  9. double h= this.Height;
  10.  
  11. this.label1.Content = x + "--" + y;
  12.  
  13. if (x <= relativeClip & y <= relativeClip) // left top
  14. {
  15. this.Cursor = Cursors.SizeNWSE;
  16. }
  17. if (x >= w - relativeClip & y <= relativeClip) //right top
  18. {
  19. this.Cursor = Cursors.SizeNESW;
  20. }
  21.  
  22. if (x >= w - relativeClip & y >= h - relativeClip) //bottom right
  23. {
  24. this.Cursor = Cursors.SizeNWSE;
  25. }
  26.  
  27. if (x <= relativeClip & y >= h - relativeClip) // bottom left
  28. {
  29. this.Cursor = Cursors.SizeNESW;
  30. }
  31.  
  32. if ((x >= relativeClip & x <= w - relativeClip) & y <= relativeClip) //top
  33. {
  34. this.Cursor = Cursors.SizeNS;
  35. }
  36.  
  37. if (x >= w - relativeClip & (y >= relativeClip & y <= h - relativeClip)) //right
  38. {
  39. this.Cursor = Cursors.SizeWE;
  40. }
  41.  
  42. if ((x >= relativeClip & x <= w - relativeClip) & y > h - relativeClip) //bottom
  43. {
  44. this.Cursor = Cursors.SizeNS;
  45. }
  46.  
  47. if (x <= relativeClip & (y <= h - relativeClip & y >= relativeClip)) //left
  48. {
  49. this.Cursor = Cursors.SizeWE;
  50. }
  51. }

最后就是Resize的函数,和上面的计算方式类似,只是拖拉的时候需要调用ResizeWindow函数来改变大小:

  1. private void Resize(object sender, MouseButtonEventArgs e)
  2. {
  3. Border clickedBorder = sender as Border;
  4.  
  5. Point pos = Mouse.GetPosition(this);
  6. double x = pos.X;
  7. double y = pos.Y;
  8. double w = this.Width;
  9. double h = this.Height;
  10.  
  11. if (x <= relativeClip & y <= relativeClip) // left top
  12. {
  13. this.Cursor = Cursors.SizeNWSE;
  14. ResizeWindow(ResizeDirection.TopLeft);
  15. }
  16. if (x >= w - relativeClip & y <= relativeClip) //right top
  17. {
  18. this.Cursor = Cursors.SizeNESW;
  19. ResizeWindow(ResizeDirection.TopRight);
  20. }
  21.  
  22. if (x >= w - relativeClip & y >= h - relativeClip) //bottom right
  23. {
  24. this.Cursor = Cursors.SizeNWSE;
  25. ResizeWindow(ResizeDirection.BottomRight);
  26. }
  27.  
  28. if (x <= relativeClip & y >= h - relativeClip) // bottom left
  29. {
  30. this.Cursor = Cursors.SizeNESW;
  31. ResizeWindow(ResizeDirection.BottomLeft);
  32. }
  33.  
  34. if ((x >= relativeClip & x <= w - relativeClip) & y <= relativeClip) //top
  35. {
  36. this.Cursor = Cursors.SizeNS;
  37. ResizeWindow(ResizeDirection.Top);
  38. }
  39.  
  40. if (x >= w - relativeClip & (y >= relativeClip & y <= h - relativeClip)) //right
  41. {
  42. this.Cursor = Cursors.SizeWE;
  43. ResizeWindow(ResizeDirection.Right);
  44. }
  45.  
  46. if ((x >= relativeClip & x <= w - relativeClip) & y > h - relativeClip) //bottom
  47. {
  48. this.Cursor = Cursors.SizeNS;
  49. ResizeWindow(ResizeDirection.Bottom);
  50. }
  51.  
  52. if (x <= relativeClip & (y <= h - relativeClip & y >= relativeClip)) //left
  53. {
  54. this.Cursor = Cursors.SizeWE;
  55. ResizeWindow(ResizeDirection.Left);
  56. }
  57. }

最后效果图如下所示:

源码下载

PS:20121130新增了一个修改就是限制了最小宽度和最小高度,但是效果不是很满意,有闪烁,以后再完善吧。

点击下载源码

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

  1. WPF换肤之五:创建漂亮的窗体

    原文:WPF换肤之五:创建漂亮的窗体 换肤效果 经过了前面四章的讲解,我们终于知道了如何拖拉窗体使之改变大小,也知道了如何处理鼠标事件,同时,也知道了如何利用更好的编写方式来编写一个方便实用和维护的换 ...

  2. WPF换肤之七:异步

    原文:WPF换肤之七:异步 在WinForm时代,相信大家都遇到过这种情形,如果在程序设计过程中遇到了耗时的操作,不使用异步会导致程序假死.当然,在WPF中,这种情况也是存在的,所以我们就需要寻找一种 ...

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

    原文:WPF换肤之八:创建3D浏览效果 上节中,我们展示了WPF中的异步以及界面线程交互的方式,使得应用程序的显示更加的流畅.这节我们主要讲解如何设计一个具有3D浏览效果的天气信息浏览器. 效果显示 ...

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

    原文:WPF换肤之六:酷炫的时区浏览小精灵 由于工作需要,经常要查看到不同地区的 当前时间,以前总是对照着时区表来进行加减运算,现在有了这个小工具以后,感觉省心了不少.下面是软件的截图: 效果图赏析 ...

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

    原文:WPF换肤之四:界面设计和代码设计分离 说起WPF来,除了总所周知的图形处理核心的变化外,和Winform比起来,还有一个巨大的变革,那就是真正意义上做到了界面设计和代码设计的分离.这样可以让美 ...

  6. WPF换肤之三:WPF中的WndProc

    原文:WPF换肤之三:WPF中的WndProc 在上篇文章中,我有提到过WndProc中可以处理所有经过窗体的事件,但是没有具体的来说怎么可以处理的. 其实,在WPF中,要想利用WndProc来处理所 ...

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

    原文:WPF换肤之一:创建圆角窗体 我们都期望自己的软件能够有一套看上去很吸引人眼球的外衣,使得别人看上去既专业又有美感.这个系列就带领着大家一步一步的讲解如何设计出一套自己的WPF的窗体皮肤,如果文 ...

  8. 有点激动,WPF换肤搞定了!

    一如既往没废话! wpf桌面应用开发都是window内引入很多个UserControl. 如果你有通过不同颜色来换肤的需求,那么下面我就将整个过程! 分2个步骤: 1.主窗体背景色替换: 2.同时界面 ...

  9. Android动态换肤(二、apk免安装插件方式)

    在上一篇文章Android动态换肤(一.应用内置多套皮肤)中,我们了解到,动态换肤无非就是调用view的setBackgroundResource(R.drawable.id)等方法设置控件的背景或者 ...

随机推荐

  1. doT js模板入门

    doT.js github地址: doT.js 官方站点 实例1:简单 <!DOCTYPE html> <html lang="en"> <head& ...

  2. Swift - 让StoryBoard设计视图,程序运行时都使用横屏形式

    1,运行时横屏 将项目属性“General”->“DeviceOritentation”的Portrait复选框去掉 2,storyboard设计视图横屏 在storyboard中,单击中间界面 ...

  3. C嵌入汇编

    概述:linux内核源码中,有很多C语言中嵌入了汇编语句,如何理解这些汇编语句,对理解内核有很重要的作用. 具有输入和输出参数的嵌入式汇编语句的基本格式为: asm("汇编语句" ...

  4. 超炫的Button按钮展开弧形动画效果

    ----------------------收藏备用  ------------------------------- 代码下载:http://download.csdn.net/detail/qq2 ...

  5. 旧版QT的名称:qt-win-commercial-4.4.3-vc60.exe

    qt-win-commercial-4.4.3-vc60.exeqt-vsaddin-collection-2.1.4.exeqt-win-commercial-4.4.3-v2005.exeqt-v ...

  6. 应用程序初始化正常(0xc015002)失败解决方法

    VS2005 sidebyside manifest error Microsoft.VC80.MFC Microsoft.VC80.CRT Microsoft.VC80.MFCLOC msvcr80 ...

  7. javascripte (三) 改变html图像

    <script> function changeImage(){ element=document.getElementById("myimage") if (elem ...

  8. OpenJDK1.8.0 源码解析————HashMap的实现(一)

    HashMap是Java Collection Framework 的重要成员之一.HashMap是基于哈希表的 Map 接口的实现,此实现提供所有可选的映射操作,映射是以键值对的形式映射:key-v ...

  9. Delphi接口的底层实现(接口在内存中仍然有其布局,它依附在对象的内存空间中,有汇编解释)——接口的内存结构图,简单清楚,深刻 good

    引言 接口是面向对象程序语言中一个很重要的元素,它被描述为一组服务的集合,对于客户端来说,我们关心的只是提供的服务,而不必关心服务是如何实现的:对于服务端的类来说,如果它想实现某种服务,实现与该服务相 ...

  10. 知识网之C++总结

    米老师常说的一句话:构造知识网. 立即要考试了.就让我们构造一下属于C++的知识网.首先从总体上了解C++: 从图中能够了解到,主要有五部分.而当我们和之前的知识联系的话,也就剩下模板和运算符重载以及 ...