C#窗口移动特效,Download()

在C#自定义皮肤中,无边框移动特效非常需要。

第一步:

       //先定义一个座标点对象 mypoint
       
private Point mypoint;

第二步:在窗体的点击事件中写

       private void Form1_MouseDown(object sender, MouseEventArgs e)
        {

            //
鼠标在窗体内按下时,自动记录鼠标的 x y 值,并将它们改为负数
            mypoint = new Point(-e.X,-e.Y);
       }

第三步:在窗体的鼠标移动事件中写

private void Form1_MouseMove(objectsender, MouseEventArgs e)
        {

            /*
               
鼠标在窗体内移动时,首先判断e.Button按下的是哪个鼠标按钮再判断
             * 是否等于左键按下,MouseButtons.Left这句的意思是鼠标左键按下状态
             */
           if (e.Button == MouseButtons.Left)
            {

               /*
新建一个座标点对象,它的座标等于(Control.MousePosition;
                * 桌面上座标的位置
                */
                Point myposition = Control.MousePosition;
               /*
                   myposition.offset
中的Offset是座标平移的意思,现在将在窗体
                * 内点击左键时产生的负数加进来,也就等于 现在鼠标在桌面上的座标
                * 减去鼠标在窗体内的座标位置,就等于现在窗体的位置
                */
               myposition.Offset(mypoint.X,mypoint.Y);
               /*
                this.DesktopLocation
这句的意思是获取或设置窗体在桌面上的位置
                *
它的位置来自myposition
                */
                this.DesktopLocation = myposition;
         
   }

 

其实这是个让人说过无数次的内容,但是最近在写一个测试小程序的时候发现了一个问题,今天没什么事做,就做个小的总结。

通过拖动窗体的客户区来移动一个窗体并不是很新鲜的内容,很多的程序都用到了这一点,尤其是一些可以换肤的程序。

这篇文章并不打算详细论述如何在C#下实现这一功能,因为它的代码实在是简单得不能再简单。这里简单说一下实现的原理:

首先说一个概念——窗体的客户区,窗体的客户区指的是一个窗体除了标题栏和边框以外的部分。

当我们的鼠标在窗体中移动的时候,会触发WM_NCHITTEST系统消息,MSDN中对这个消息的说明为:The WM_NCHITTEST message issent to a window when the cursor moves, or when a mouse button is pressed orreleased. If the mouse is not captured, the message is sent to the windowbeneath the cursor. Otherwise, the message is posted to the window that hascaptured the mouse. (当光标移动或一个鼠标键被按下或释放时,WM_NCHITTEST消息会被发送到一个窗口中,如果光标没有被捕获,这个消息被送到光标下的窗口。否则这个消息被送到捕获了光标的窗口。)

这个消息被默认的(请注意是“默认的”)窗口过程(窗口过程这个概念后面再说)处理后,会根据触发这个消息时鼠标的位置返回一个值,例如当鼠标在窗口的标题栏上时,返回HTCAPTION;当鼠标在一个窗口的客户区中时,返回HTCLIENT;如果鼠标指向某个窗口的字窗口的“关闭”按钮或系统菜单 (就是点击窗口图标后出现的那个菜单),就返回HTSYSMENU。

所以我们要做的就是骗!我们要欺骗Windows,当我们的鼠标在窗体的客户区中移动时,默认的窗口过程处理后会返回 HTCLIENT,Windows系统根据这个值进行相应的操作,把适当的消息插入到应用程序的消息队列(这个概念同样在后面讨论)中。这时如果我们做一些改变,人为地修改窗口过程的返回值,把HTCLIENT修改为HTCAPTION并返回给系统,系统就会认为鼠标这时在窗体的标题栏中,而拖动标题栏可以移动一个窗体,所以当我们在一个被这样修改后的应用程序的客户区按下鼠标并拖动时,Windows会认我们在拖动一个窗体的标题栏,于是它把一个移动窗体的消息插入到程序的消息队列中,再经过窗口过程的处理,就实现了我们需要的功能——拖动窗体的客户区移动窗体。

于是就有了下面的代码:

protected override WndProc(ref message m)

{

      switch (m.Msg)

      {

         caseWM_NCHITTEST: //如果鼠标移动或单击                 

           base.WndProc(ref m);//调用基类的窗口过程——WndProc方法处理这个消息

           if (m.Result == (IntPtr)HTCLIENT)//如果返回的是HTCLIENT

           {

                 m.Result = (IntPtr)HTCAPTION;//把它改为HTCAPTION

                 return;//直接返回退出方法

           }

           break;

      }

      base.WndProc(ref m);//如果不是鼠标移动或单击消息就调用基类的窗口过程进行处理

}

这也是MSDN上的一个例子,重写窗体的WndProc方法,判断消息然后返回。

我本来也是想这样就完事了,可是后来测试的时候却发现有个大问题:双击窗体的时候,窗体会最大化。

那天脑子有点迷糊,也没多想就到网上问了一下,一个高手提醒我说可能是那个重写的窗口过程的问题,于是一下子明白了。我们向Windows传递了假信息,Windows我们在窗体的客户区双击鼠标在Windows看来是双击了窗体的标题栏,理所当然的Windows会告诉窗口过程窗体需要最大化,窗 口过程又理所当然的照做了,于是就出现了这样的现象。

刚开始的时候还不是很确定这样想对不对,不过后来出现的另一个现象证实了这个推断是正确的:

因为窗体没有标题栏,每次退出程序都要按Alt+F4实在是比较不爽,我就给窗体添加了一个ContextMenuStrip,只有一个菜单项用来 关闭窗体退出程序。我发现当我修改了窗体的默认窗口过程后,这个右键菜单就显示不出来了。出现这个现象的原因只能有一个——Windows真的把窗体的客 户区当成标题栏处理了,而默认ContextMenuStrip只能用在窗体的客户区或控件上。

后来我又跟那位高手讨论了一下,他给我提供了另一种方法,这个方法需要两个API函数:

LRESULT SendMessage(

HWND hWnd,// handle of destination window

UINT Msg,// message to send

WPARAM wParam,// first message parameter

LPARAM lParam // second message parameter

);

BOOL ReleaseCapture(VOID)

SendMessage函数想必不用多说了,无数个木马制作教学贴里都有详细的描述,这个函数用来向指定的窗体发送Windows消息,功能的确十分强大^_^

ReleaseCapture函数相对陌生一些,这个函数用来释放被当前线程中某个窗口捕获的光标。

他给出的代码是这样的:

private void Form1_MouseDown(object sender, MouseEventArgse)

{

      ReleaseCapture();

      SendMessage(this.Handle,WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);

}

也就是说,在窗体的MouseDown(鼠标按下)事件中,首先释放光标,然后向窗口过程发送WM_SYSCOMMAND消息。

说实话这个方式的SendMessage函数调用我也是第一次遇到,刚开始怎么也不明白为什么要把它们(SC_MOVE 和 HTCAPTION)用一个加号连接起来。

这个WM_SYSCOMMAND消息包含了一些信息表示用户要进行的操作,消息的wParam字段表示用户要最大化、最小化或移动窗体。

我去掉HTCAPTION试了一下,每当我在窗体上按下鼠标左键时,鼠标就会移动到窗体的最顶端——原来标题栏的位置,与我们在系统菜单中选择“移 动”菜单项的效果一样。于是查阅MSDN,在WM_SYSCOMMAND的帮助中查到这样一句话:In WM_SYSCOMMAND messages, the four low-order bits of the uCmdTypeparameter are used internally by Windows. To obtain the correct result whentesting the value of uCmdType, an application must combine the value 0xFFF0with the uCmdType value by using the bitwise AND operator. (在WM_SYSCOMMAND消息中,uCmdType的低四位是Windows内部使用的。如果测试时要获得uCmdType的正确结果,应用程序必须把0xFFF0这个值与uCmdType的值通过位相加的方式合并在一起。)于是豁然开朗:这个所谓的“Windows内部使用的值”在移动这个窗体的 时候其实就是表示“窗体的标题栏”,所以我们使用SC_MOVE+ HTCAPTION表示窗体将被移动且目前鼠标正在窗体的标题栏中。

至于这里为什么要先释放光标再发送消息,根据前面的WM_NCHITTEST消息的说明,如果光标被当前窗口捕获,那么当鼠标移动时,就会发送 WM_NCHITTEST到我们的窗体,而由于我们这时没有重写窗口过程,这个消息会被按照默认的方式处理,Windows会发现鼠标在窗体的客户区移 动,SendMessage发过去的HTCAPTION被WM_NCHITTEST消息中的HTCLIENT覆盖,这样窗体就不会有任何动作。

还有两个名词解释:

窗口过程:窗口过程是一个应用程序定义的、用来处理Windows消息的回调(CallBack)函数,当有消息发生时,窗口过程负责处理消息。

消息队列:Windows会把有关一个应用程序的全部消息放到一块内存区域中,这个区域使用一种先进先出(FIFO)的方式工作,就是说最先进入这 个区域的消息会第一个离开这个区域,故称消息队列。应用程序从消息队列中取出一条消息,进行相应的处理,返回一定的信息,然后再取出下一条处理。

例1

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace LiBo.WinControls.Forms {
/// <summary>
/// DragForm 类是可
/// </summary>
public class DragForm : System.Windows.Forms.Form {

private bool dragEnable;
private bool dragging;
private int xOld, yOld;

public DragForm() : base() {
dragEnable = false;
}

/// <summary>
/// 获取或设置一个值,该值指示窗体是否可以通过鼠标左键拖动。
/// </summary>
[DefaultValue(false)]
[Category("Behavior")]
[Description("指示窗体是否可以通过鼠标左键拖动")]
public bool DragEnable {
get { return dragEnable; }
set { dragEnable = value; }
}

protected override void OnMouseDown(MouseEventArgs e) {
if(dragEnable && e.Button == MouseButtons.Left) {
// 保存当前鼠标的位置,可以用它来计算鼠标移动的距离
xOld = MousePosition.X;
yOld = MousePosition.Y;
// 标识鼠标正在拖动窗体
dragging = true;
}
}

protected override void OnMouseMove(MouseEventArgs e) {
if(dragEnable && dragging) {
// 计算出鼠标在 X 和 Y 座标方向上移动的距离
int dx = MousePosition.X - xOld;
int dy = MousePosition.Y - yOld;

// 根据上面的结果计算出窗体偏移后的位置
Point point = this.Location;
point.Offset(dx, dy);

// 设置上面的偏移位置为窗体的位置
this.Location = point;

// 保存当前鼠标的位置,用于下一个循环的计算
xOld = MousePosition.X;
yOld = MousePosition.Y;
}
}

protected override void OnMouseUp(MouseEventArgs e) {
if(dragEnable && e.Button == MouseButtons.Left)
dragging = false;
}
}
}  

例2

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime.InteropServices;

namespace Example013_无标题窗体的拖动
{
/// <summary>
/// Form1 的摘要说明。
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
/// <summary> 



/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.Container components = null;

public Form1()
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();

//
// TODO: 在 InitializeComponent 调用后添加任何构造函数代码
//
}

/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code
/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1));
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.BackgroundImage =((System.Drawing.Bitmap)(resources.GetObject("$this.BackgroundImage")));
this.ClientSize = new System.Drawing.Size(292, 273);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Name = "Form1";
this.Text = "Form1";
this.MouseDown += newSystem.Windows.Forms.MouseEventHandler(this.Form1_MouseDown);

}
#endregion

/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}

[DllImport("user32.dll")]
public static extern bool ReleaseCapture();
[DllImport("user32.dll")]
public static extern bool SendMessage(IntPtr hwnd,int wMsg,int wParam,int lParam);

public const int WM_SYSCOMMAND=0x0112;
public const int SC_MOVE=0xF010;
public const int HTCAPTION=0x0002;

private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgse)
{
ReleaseCapture();
SendMessage(this.Handle,WM_SYSCOMMAND,SC_MOVE+HTCAPTION, 0);
}
}
}

 


//这篇文章貌似是来自“百度文库”

发布了34 篇原创文章 · 获赞 1 · 访问量 4万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章