原文: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();
}

这个函数会接收到所有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换肤之二:可拉动的窗体的更多相关文章

  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. QMediaPlayer的duration问题

    遇到了一个坑QMediaPlayer::duration的坑. 这个坑是当你setMedia之后, 直接使用duration获取播放时长会得到0, 出错时候的代码片段例如以下: void MainWi ...

  2. 嵌入式Linux常见问题

    Linux问题集 1 linux设置环境变量及保存地点 1. 显示环境变量HOME $ echo $HOME /home/terry 2. 设置一个新的环境变量WELCOME $ exportWELC ...

  3. VC/MFC ListCtrl 控件功能使用汇总(转)

    以下未经说明,listctrl默认view 风格为report 相关类及处理函数 MFC:CListCtrl类 SDK:以 “ListView_”开头的一些宏.如 ListView_InsertCol ...

  4. Blackboard - 百度百科

    http://wapbaike.baidu.com/view/1969844.htm?ssid=0&from=844b&uid=0&pu=sz%401320_1001%2Cta ...

  5. 如何在github中贡献自己的力量

    如何参与github的开源项目? 1.找一个发出“pull requests”的项目.有以下几个方法:最简单的方式是,读项目的readme文件.它会告诉你,项目的拥有者是否急切的需要协助.如果read ...

  6. VMware vSphere服务器虚拟化实验十五 vCenter vShield Manager

    VMware vSphere服务器虚拟化实验十五 vCenter vShield Manager VMware  vShield Manager是专为 VMware vCenter Server 集成 ...

  7. vdsm的SSL证书验证过程

    1. Copy the VDSM certificate of the RHEV-H(Red Hat Enterprise Virtualization Hypervisor ) host to th ...

  8. Android获得Manifest在&lt;meta-data&gt;元件的值

    前段时间攻略完成游戏开发项目.其中用于包装散装. 目前市场上的网络不提交.但是,通过设置Manifest中的Meta_data>去获得相关參数,游戏ID号改变.游戏ID改变,然后游戏内容就改变. ...

  9. UDP 通信

    import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; public ...

  10. MATLAB——scatter的简单应用

    scatter可用于描绘散点图. 1.scatter(X,Y) X和Y是数据向量,以X中数据为横坐标,以Y中数据位纵坐标描绘散点图,点的形状默认使用圈. 样例: X = [1:10];  Y = X ...