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();
}
这个函数会接收到所有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 = ,
Right = ,
Top = ,
TopLeft = ,
TopRight = ,
Bottom = ,
BottomLeft = ,
BottomRight = ,
}
在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)( + 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换肤之五:创建漂亮的窗体
原文:WPF换肤之五:创建漂亮的窗体 换肤效果 经过了前面四章的讲解,我们终于知道了如何拖拉窗体使之改变大小,也知道了如何处理鼠标事件,同时,也知道了如何利用更好的编写方式来编写一个方便实用和维护的换 ...
- WPF换肤之七:异步
原文:WPF换肤之七:异步 在WinForm时代,相信大家都遇到过这种情形,如果在程序设计过程中遇到了耗时的操作,不使用异步会导致程序假死.当然,在WPF中,这种情况也是存在的,所以我们就需要寻找一种 ...
- WPF换肤之八:创建3D浏览效果
原文:WPF换肤之八:创建3D浏览效果 上节中,我们展示了WPF中的异步以及界面线程交互的方式,使得应用程序的显示更加的流畅.这节我们主要讲解如何设计一个具有3D浏览效果的天气信息浏览器. 效果显示 ...
- WPF换肤之六:酷炫的时区浏览小精灵
原文:WPF换肤之六:酷炫的时区浏览小精灵 由于工作需要,经常要查看到不同地区的 当前时间,以前总是对照着时区表来进行加减运算,现在有了这个小工具以后,感觉省心了不少.下面是软件的截图: 效果图赏析 ...
- WPF换肤之四:界面设计和代码设计分离
原文:WPF换肤之四:界面设计和代码设计分离 说起WPF来,除了总所周知的图形处理核心的变化外,和Winform比起来,还有一个巨大的变革,那就是真正意义上做到了界面设计和代码设计的分离.这样可以让美 ...
- WPF换肤之三:WPF中的WndProc
原文:WPF换肤之三:WPF中的WndProc 在上篇文章中,我有提到过WndProc中可以处理所有经过窗体的事件,但是没有具体的来说怎么可以处理的. 其实,在WPF中,要想利用WndProc来处理所 ...
- WPF换肤之一:创建圆角窗体
原文:WPF换肤之一:创建圆角窗体 我们都期望自己的软件能够有一套看上去很吸引人眼球的外衣,使得别人看上去既专业又有美感.这个系列就带领着大家一步一步的讲解如何设计出一套自己的WPF的窗体皮肤,如果文 ...
- 有点激动,WPF换肤搞定了!
一如既往没废话! wpf桌面应用开发都是window内引入很多个UserControl. 如果你有通过不同颜色来换肤的需求,那么下面我就将整个过程! 分2个步骤: 1.主窗体背景色替换: 2.同时界面 ...
- Android动态换肤(二、apk免安装插件方式)
在上一篇文章Android动态换肤(一.应用内置多套皮肤)中,我们了解到,动态换肤无非就是调用view的setBackgroundResource(R.drawable.id)等方法设置控件的背景或者 ...
随机推荐
- HDU 4974 A simple water problem(贪心)
HDU 4974 A simple water problem pid=4974" target="_blank" style="">题目链接 ...
- SAP ABAP exporting list to memory ...SUBMIT 程序传输屏幕参数
SUBMIT report EXPORTING LIST TO MEMORY AND RETURN. submit 关键字的作用就是在程序内部调用一个程序,and retur ...
- 在VC下显示JPEG、GIF格式图像的一种简便方法
在VC下显示JPEG.GIF格式图像的一种简便方法 一. 引言 JPEG图像压缩标准随然是一种有损图像压缩标准,但由于人眼视觉的不敏感,经压缩后的画质基本没有发生变化,很快便以较高的压缩率得到了广泛 ...
- C/C++ Resources
C/C++ Resources 一.C/C++相关资源 语言标准及文档● ISO C99http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1124.pd ...
- uva 10581 - Partitioning for fun and profit(记忆化搜索+数论)
题目链接:uva 10581 - Partitioning for fun and profit 题目大意:给定m,n,k,将m分解成n份,然后依照每份的个数排定字典序,而且划分时要求ai−1≤ai, ...
- 单页应用Scrat实践
单页应用Scrat实践 1.开始 随着前端工程化深入研究,前端工程师现在碉堡了,甚至搞了个自己的前端网站http://div.io/需要邀请码才能注册,不过里面的技术确实牛.距离顶级的前端架构,目前博 ...
- c# in depth 之泛型实参的类型推断
调用泛型方法时,指定类型实参常常会显得很多余.为简化工作,c#2编译器被赋予了一定的“智能”,让你在调用方法时,不需要显式声明类型实参. 在深入讨论这个主题之前,必须强调一下:类型推断只适用于泛型方法 ...
- ABAP 中 Table Control例子
实现了Table Control的主要的一些功能,可以作为例子参考,实现的功能有是否可编辑切换,选择某一条记录点击按钮显示详细信息,新增记录,删除记录,选择所有记录,选择光标所有记录,取消选择所有,排 ...
- 【剑指offer】不用加减乘除做加法
转载请注明出处:http://blog.csdn.net/ns_code/article/details/27966641 题目描写叙述: 写一个函数,求两个整数之和,要求在函数体内不得使用+.-.* ...
- C语言编写的bmp读写程序
C语言编写的bmp读写程序 建议先把bmp的数据存储格式了解下 <span style="font-size:16px;">#include "Windows ...