研究控件拖拉时,发现当控件为用户控件(可以理解成一个包含多个控件的容器控件),此时拖拉时,不再是点击控件任意位置能使其移动,只有当拖动边界或空白区域才会有反应。这个问题困扰了我好久,我想过给其中的每个控件都加上拖动方法,或是直接在鼠标捕获这些控件时都去调用那个拖动方法,我坚信还有其他办法,但是从哪里入手呢???
我们都知道windows窗体本身也是一个容器,也只能点击它上面的标题栏让其移动,如果能实现在窗体上任意位置(包括边框和用户区内)随意移动,那么我这个问题也许能够得到解决,于是开始在GOOGLE上展开了全面搜索,发现实现窗体任意移动的方案有几下几种:
1.方案一:在窗体MouseDown事件中添加标题栏移动的消息响应
namespace WindowsApplication1
{
public partial class Form1 : Form
{
[DllImport("user32.dll")] //需添加using System.Runtime.InteropServices
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;
public Form1()
{
InitializeComponent();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
ReleaseCapture();
SendMessage(this.Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
}
}
}
2.方案二:重载 WndProc 并改写鼠标事件
首先必须了解Windows的消息传递机制,当有鼠标活动消息时,系统发送WM_NCHITTEST 消息给窗体作为判断消息发生地的根据。假如你点击的是标题栏,窗体收到的消息值就是 HTCAPTION ,同样地,若接受到的消息是 HTCLIENT,说明用户点击的是客户区,也就是鼠标消息发生在客户区。
当重载窗体的 WndProc 方法时,可以截获 WM_NCHITTEST 消息并改些该消息,当判断鼠标事件发生在客户区时,改写改消息,发送 HTCAPTION 给窗体,这样,窗体收到的消息就时 HTCAPTION ,在客户区通过鼠标来拖动窗体就如同通过标题栏来拖动一样。
注意:当你重载 WndProc 并改写鼠标事件后,整个窗体的鼠标事件也就随之改变了。
在Form1类中添加如下代码:
private const int WM_NCHITTEST = 0x84;
private const int HTCLIENT = 0x1;
private const int HTCAPTION = 0x2;
//在Form1中改写鼠标消息
protected override void WndProc(ref Message m)
{
switch(m.Msg)
{
case WM_NCHITTEST:
base.WndProc(ref m);
if ((int)m.Result == HTCLIENT)
m.Result = (IntPtr)HTCAPTION;
return;
break;
}
base.WndProc(ref m);
}
3.方案三:响应基本鼠标事件,捕获光标移动位置
首先,我们来复习一下一个Windows窗体的组成。它是一个形式化的标准Windows窗体。首先,窗体的顶部是一个标题栏,其余的部分是窗体的主体,包围在窗体主体外围的是一个边框,边框内不就是我们放置控件或绘制图形的用户区。
对于用户区,System.Windows.Forms.Form提供了实例属性ClientSize,相信大家已经很熟悉了。而要想活棋一般性的窗体构造元素(如标题栏、边框等)的尺寸,我们可以使用.NET类库中提供的一个类:System.Windows.Forms.SystemInformation,这个类提供了一些静态属性如表示标题栏高度的CaptionHeight。有关SystemInformation类的信息可以在.NET SDK文档目录“.NET Framework SDK -> 参考 -> 类库 -> System.Windows.Forms -> SystemInformation 类”处找到(注:这里的超链接只在您安装了.NET Framework 1.1简体中文版并且安装了配套文档时才有效)。这是一个很有用的类,希望大家能够记住它(可能您早就知道了,可我是才知道的-_-汗~~)。
接下来,我们来看看如何在在用户区拖动鼠标时移动窗体。很容易可以发现:在窗体被拖动的过程中,鼠标在窗体内的相对位置是始终不变的!那么,我们只要检测到鼠标在屏幕中的移动并修改窗体的位置就可以达到拖动窗体的目的!
我们知道,在鼠标消息/事件处理中,只能得到鼠标相对于窗体的位置。那么,如何知道鼠标在屏幕中的位置呢?这里又要提到一个类:System.Windows.Forms.Control类。也许你会很吃惊:这不是所有控件的基类么?呵呵~是这样di。不过,尽管是这样,Control类却没有像其他广泛使用的基类那样被声明为抽象类,而且它提供了一个静态属性:MousePosition,通过这个属性可以得到鼠标相对于屏幕的位置。有关Control类的信息可以在.NET Framework文档目录“.NET Framework SDK -> 参考 -> 类库 -> System.Windows.Forms -> Control 类”处找到(注:这里的超链接只在您安装了.NET Framework 1.1简体中文版并且安装了配套文档时才有效)。
知道了如何获取这些信息之后,制作移动窗体实际上就成了一个很简单的问题了。基本过程是这样的:首先,在鼠标(左键或一个你喜欢的键)按下时,记录鼠标位置;由于在移动的过程中,鼠标的屏幕坐标发生变化但窗体相对坐标不变,我们可以推算出窗体位置的变化为(假设mousePosition具有System.Drawing.Point类型,表示鼠标在窗体中的相对坐标):
// 示例代码1
Form.Top = Control.MousePosition.Y - mousePosition.Y;
Form.Left = Control.MousePosition.X - mousePosition.X;
这样还不行,因为我们的mousePosition表示的是鼠标在窗体用户区内的相对坐标,但在移动窗体的时候还要考虑窗体标题栏和边框的尺寸。在上面的基础上,我们将代码修正为:
// 示例代码2
Form.Top = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Height - SystemInformation.CaptionHeight;
Form.Left = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Width;
也就是说,在高度上(纵坐标)要减去标题栏的高度和边框的高度,而在宽度上(横坐标)要减去边框的宽度。然而,当制作一个既没有标题栏也没有边框的可拖动窗体时,使用“示例代码1”所示的代码就可以了。
上面的代码只是一个示范性代码。具体的操作如下:
首先,为窗体添加一个私有域:
private System.Drawing.Point mousePoint;
然后,为窗体添加鼠标按下事件处理方法(我这里是MainForm_MouseDown,别忘了将该方法链接到MainForm.MouseDown事件,这不用多说了吧?):
private void MainForm_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) {
if(e.Button == MouseButtons.Left) {
this.mousePosition.X = e.X;
this.mousePosition.Y = e.Y;
}
}
在这里注意对鼠标按键进行筛选。
接下来,为窗体添加鼠标移动事件处理方法(我这里是MainForm_MouseMove):
private void MainForm_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) {
if(e.Button == MouseButtons.Left) {
Form.Top = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Height - SystemInformation.CaptionHeight;
Form.Left = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Width;
}
}
上述方案实现的窗体任意移动,仍然有一定的局限性,当窗体用户区域包含有很多其他控件时,鼠标点击在这些控件之上时,窗体仍无法移动,也只有点击用户区的空白区域时,才会移动。要想点击任何地方移动,除非给每个控件添加上述类似FORM的处理。所以我开始提出的问题,也只能按照我最初的想法去解决了。希望有高人来给出其他意见!欢迎大家前来共同讨论。
我们都知道windows窗体本身也是一个容器,也只能点击它上面的标题栏让其移动,如果能实现在窗体上任意位置(包括边框和用户区内)随意移动,那么我这个问题也许能够得到解决,于是开始在GOOGLE上展开了全面搜索,发现实现窗体任意移动的方案有几下几种:
1.方案一:在窗体MouseDown事件中添加标题栏移动的消息响应
namespace WindowsApplication1
{
public partial class Form1 : Form
{
[DllImport("user32.dll")] //需添加using System.Runtime.InteropServices
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;
public Form1()
{
InitializeComponent();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
ReleaseCapture();
SendMessage(this.Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
}
}
}
2.方案二:重载 WndProc 并改写鼠标事件
首先必须了解Windows的消息传递机制,当有鼠标活动消息时,系统发送WM_NCHITTEST 消息给窗体作为判断消息发生地的根据。假如你点击的是标题栏,窗体收到的消息值就是 HTCAPTION ,同样地,若接受到的消息是 HTCLIENT,说明用户点击的是客户区,也就是鼠标消息发生在客户区。
当重载窗体的 WndProc 方法时,可以截获 WM_NCHITTEST 消息并改些该消息,当判断鼠标事件发生在客户区时,改写改消息,发送 HTCAPTION 给窗体,这样,窗体收到的消息就时 HTCAPTION ,在客户区通过鼠标来拖动窗体就如同通过标题栏来拖动一样。
注意:当你重载 WndProc 并改写鼠标事件后,整个窗体的鼠标事件也就随之改变了。
在Form1类中添加如下代码:
private const int WM_NCHITTEST = 0x84;
private const int HTCLIENT = 0x1;
private const int HTCAPTION = 0x2;
//在Form1中改写鼠标消息
protected override void WndProc(ref Message m)
{
switch(m.Msg)
{
case WM_NCHITTEST:
base.WndProc(ref m);
if ((int)m.Result == HTCLIENT)
m.Result = (IntPtr)HTCAPTION;
return;
break;
}
base.WndProc(ref m);
}
3.方案三:响应基本鼠标事件,捕获光标移动位置
首先,我们来复习一下一个Windows窗体的组成。它是一个形式化的标准Windows窗体。首先,窗体的顶部是一个标题栏,其余的部分是窗体的主体,包围在窗体主体外围的是一个边框,边框内不就是我们放置控件或绘制图形的用户区。
对于用户区,System.Windows.Forms.Form提供了实例属性ClientSize,相信大家已经很熟悉了。而要想活棋一般性的窗体构造元素(如标题栏、边框等)的尺寸,我们可以使用.NET类库中提供的一个类:System.Windows.Forms.SystemInformation,这个类提供了一些静态属性如表示标题栏高度的CaptionHeight。有关SystemInformation类的信息可以在.NET SDK文档目录“.NET Framework SDK -> 参考 -> 类库 -> System.Windows.Forms -> SystemInformation 类”处找到(注:这里的超链接只在您安装了.NET Framework 1.1简体中文版并且安装了配套文档时才有效)。这是一个很有用的类,希望大家能够记住它(可能您早就知道了,可我是才知道的-_-汗~~)。
接下来,我们来看看如何在在用户区拖动鼠标时移动窗体。很容易可以发现:在窗体被拖动的过程中,鼠标在窗体内的相对位置是始终不变的!那么,我们只要检测到鼠标在屏幕中的移动并修改窗体的位置就可以达到拖动窗体的目的!
我们知道,在鼠标消息/事件处理中,只能得到鼠标相对于窗体的位置。那么,如何知道鼠标在屏幕中的位置呢?这里又要提到一个类:System.Windows.Forms.Control类。也许你会很吃惊:这不是所有控件的基类么?呵呵~是这样di。不过,尽管是这样,Control类却没有像其他广泛使用的基类那样被声明为抽象类,而且它提供了一个静态属性:MousePosition,通过这个属性可以得到鼠标相对于屏幕的位置。有关Control类的信息可以在.NET Framework文档目录“.NET Framework SDK -> 参考 -> 类库 -> System.Windows.Forms -> Control 类”处找到(注:这里的超链接只在您安装了.NET Framework 1.1简体中文版并且安装了配套文档时才有效)。
知道了如何获取这些信息之后,制作移动窗体实际上就成了一个很简单的问题了。基本过程是这样的:首先,在鼠标(左键或一个你喜欢的键)按下时,记录鼠标位置;由于在移动的过程中,鼠标的屏幕坐标发生变化但窗体相对坐标不变,我们可以推算出窗体位置的变化为(假设mousePosition具有System.Drawing.Point类型,表示鼠标在窗体中的相对坐标):
// 示例代码1
Form.Top = Control.MousePosition.Y - mousePosition.Y;
Form.Left = Control.MousePosition.X - mousePosition.X;
这样还不行,因为我们的mousePosition表示的是鼠标在窗体用户区内的相对坐标,但在移动窗体的时候还要考虑窗体标题栏和边框的尺寸。在上面的基础上,我们将代码修正为:
// 示例代码2
Form.Top = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Height - SystemInformation.CaptionHeight;
Form.Left = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Width;
也就是说,在高度上(纵坐标)要减去标题栏的高度和边框的高度,而在宽度上(横坐标)要减去边框的宽度。然而,当制作一个既没有标题栏也没有边框的可拖动窗体时,使用“示例代码1”所示的代码就可以了。
上面的代码只是一个示范性代码。具体的操作如下:
首先,为窗体添加一个私有域:
private System.Drawing.Point mousePoint;
然后,为窗体添加鼠标按下事件处理方法(我这里是MainForm_MouseDown,别忘了将该方法链接到MainForm.MouseDown事件,这不用多说了吧?):
private void MainForm_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) {
if(e.Button == MouseButtons.Left) {
this.mousePosition.X = e.X;
this.mousePosition.Y = e.Y;
}
}
在这里注意对鼠标按键进行筛选。
接下来,为窗体添加鼠标移动事件处理方法(我这里是MainForm_MouseMove):
private void MainForm_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) {
if(e.Button == MouseButtons.Left) {
Form.Top = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Height - SystemInformation.CaptionHeight;
Form.Left = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Width;
}
}
上述方案实现的窗体任意移动,仍然有一定的局限性,当窗体用户区域包含有很多其他控件时,鼠标点击在这些控件之上时,窗体仍无法移动,也只有点击用户区的空白区域时,才会移动。要想点击任何地方移动,除非给每个控件添加上述类似FORM的处理。所以我开始提出的问题,也只能按照我最初的想法去解决了。希望有高人来给出其他意见!欢迎大家前来共同讨论。