如何使无边框窗体可移动?

131

有没有办法在表单的边框设置为“无”(FormBorderStyle被设置为“none”)时,使其像有边框一样在鼠标按下时能够移动?

20个回答

299

这篇 CodeProject 上的文章详细介绍了一种技术。基本上归结为:

public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;

[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool ReleaseCapture();

private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{     
    if (e.Button == MouseButtons.Left)
    {
        ReleaseCapture();
        SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
    }
}

从窗口管理器的角度来看,这基本上与抓取窗口标题栏完全相同。


4
对我完全没用。代码可以正常运行,一切都正确,但窗口仍然不动。有什么想法吗? - dbrree
15
您可能复制了代码,但这不会起作用,因为Form1_MouseDown没有分配到Form1的实际MouseDown事件。 - Remon Ramy
3
如果您有一个标签或图标(或任何其他前景对象!),在拖动表单时,请为这些项目添加mousedown事件。表单事件无法穿过表单对象。 - Paul Nelson
2
你需要在窗体的 Main() 函数中添加 this.MouseDown += ... - Richard Peck
1
对我来说非常好用!!!我不得不将事件处理移动到面板上,以代替表单,但它起作用了。 - Justin
显示剩余5条评论

77

让我们不要把事情弄得比必要的更难。我已经遇到了很多代码片段,允许你拖动一个表单(或其他控件)。其中许多都有自己的缺点/副作用。特别是那些骗过Windows认为表单上的控件是实际表单的控件。

话虽如此,这里是我的代码片段。我经常使用它。我还想指出,你不应该像其他人一样使用this.Invalidate();,因为在某些情况下会导致窗体闪烁。有些情况下,this.Refresh也会闪烁。使用this.Update,我没有遇到任何闪烁问题:

private bool mouseDown;
private Point lastLocation;

    private void Form1_MouseDown(object sender, MouseEventArgs e)
    {
        mouseDown = true;
        lastLocation = e.Location;
    }

    private void Form1_MouseMove(object sender, MouseEventArgs e)
    {
        if(mouseDown)
        {
            this.Location = new Point(
                (this.Location.X - lastLocation.X) + e.X, (this.Location.Y - lastLocation.Y) + e.Y);

            this.Update();
        }
    }

    private void Form1_MouseUp(object sender, MouseEventArgs e)
    {
        mouseDown = false;
    }

2
WinForms...我找了将近两个小时的答案...虽然我是C#新手,但你帮了我大忙! - Technivorous
这里可能是最好的选择,因为它运行良好且不依赖于WndProc(可以轻松移植到使用Mono的其他平台)。如果Y<10,则可以通过更改状态为最大化/正常来改进。 - Sylverdrag
4
这很好。有两个问题。1)双击会导致混乱。 只有在 e.Clicks == 1 时才设置 mouseDown = true。2)如果您的窗体上有控件(面板),则此方法无效。 我会挂钩表单上大多数控件的事件,除了某些没有意义的控件,如按钮。 - Dave Cousineau
1
绝对是正确的解决方案,即使在2021年。 - Nick Juelich
1
这似乎对我不起作用。我不知道为什么。 - Ooker
显示剩余6条评论

44

另一个更简单的方法来做同样的事情。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        // set this.FormBorderStyle to None here if needed
        // if set to none, make sure you have a way to close the form!
    }
    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        if (m.Msg == WM_NCHITTEST)
            m.Result = (IntPtr)(HT_CAPTION);
    }

    private const int WM_NCHITTEST = 0x84;
    private const int HT_CLIENT = 0x1;
    private const int HT_CAPTION = 0x2;
}

2
有没有办法让用户通过拖动特定的工具(例如标签)来移动窗体? - Thomas Wagenaar
4
双击表格可最大化显示。同时也有消息称这会影响右键点击功能。 - Sebastiano

20

使用MouseDown(鼠标按下),MouseMove(鼠标移动)和MouseUp(鼠标松开)事件,可以设置一个变量标志。我有一个示例,但我认为您需要进行修改。

我正在将鼠标操作编码到面板中。一旦你点击面板,你的窗体就会随之移动。

//Global variables;
private bool _dragging = false;
private Point _offset;
private Point _start_point=new Point(0,0);


private void panel1_MouseDown(object sender, MouseEventArgs e)
{
   _dragging = true;  // _dragging is your variable flag
   _start_point = new Point(e.X, e.Y);
}

private void panel1_MouseUp(object sender, MouseEventArgs e)
{
   _dragging = false; 
}

private void panel1_MouseMove(object sender, MouseEventArgs e)
{
  if(_dragging)
  {
     Point p = PointToScreen(e.Location);
     Location = new Point(p.X - this._start_point.X,p.Y - this._start_point.Y);     
  }
}

就像先前提到的一样,这取决于窗体是否仍然能够产生MouseMove事件。以一个简单的情况为例,假设你在最顶部的像素行处抓住了窗体并向上拖动,那么并不会发生任何事情,直到你将鼠标再次移回窗体上时,它才会跳来跳去。 - Joey

13

仅适用于WPF。


我手头没有确切的代码,但在最近的项目中,我认为我使用了MouseDown事件,并简单地将其放置在此处:

frmBorderless.DragMove();

Window.DragMove 方法 (MSDN)


2
那是WPF,不过好吧,原帖并没有明确说明这一点。 - Joey
是的,这是我在做项目时忘记的事情。我刚刚查看了表单,但它不可用。抱歉! - Chris
3
@Chris 这对我在一个 WPF 项目中起作用。谢谢你没有删除这个答案。 - Rembunator

12

它适用于我。

    private Point _mouseLoc;

    private void Form1_MouseDown(object sender, MouseEventArgs e)
    {
        _mouseLoc = e.Location;
    }

    private void Form1_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            int dx = e.Location.X - _mouseLoc.X;
            int dy = e.Location.Y - _mouseLoc.Y;
            this.Location = new Point(this.Location.X + dx, this.Location.Y + dy);
        }
    }

是的,如果简单的方法能够良好地运行,为什么要去复杂化呢? - Sudhananda Biswas

9

参考视频链接

这是经过测试并易于理解的。

protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case 0x84:
            base.WndProc(ref m);
            if((int)m.Result == 0x1)
                m.Result = (IntPtr)0x2;
            return;
    }

    base.WndProc(ref m);
}

6
这段代码能够运行,但是它非常难以理解。除了 switch 语句之外,我对其中的任何内容都不了解! - Jamshaid K.
1
这是伪装成 WM_NCHITTEST 的东西。 - Andreas Rejbrand
只有WinAPI开发人员才能理解。 WndProc是低级API中的一种,它控制所有事件,例如窗体创建、控件添加、删除等。因此,他只需编辑WndProc即可。 - Gray Programmerz

5

由于某些答案不允许子控件可拖动,因此我创建了一个小的辅助类。应该将顶级表单传递给它。如果需要,可以更加通用。

class MouseDragger
{
    private readonly Form _form;
    private Point _mouseDown;

    protected void OnMouseDown(object sender, MouseEventArgs e)
    {
        _mouseDown = e.Location;
    }

    protected void OnMouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            int dx = e.Location.X - _mouseDown.X;
            int dy = e.Location.Y - _mouseDown.Y;
            _form.Location = new Point(_form.Location.X + dx, _form.Location.Y + dy);
        }
    }
    public MouseDragger(Form form)
    {
        _form = form;

        MakeDraggable(_form);            
    }

    private void MakeDraggable(Control control)
    {
        var type = control.GetType();
        if (typeof(Button).IsAssignableFrom(type))
        {
            return;
        }

        control.MouseDown += OnMouseDown;
        control.MouseMove += OnMouseMove;

        foreach (Control child in control.Controls)
        {
            MakeDraggable(child);
        }
    }
}

4

我发现的最好方法(当然进行了修改)

// This adds the event handler for the control
private void AddDrag(Control Control) { Control.MouseDown += new System.Windows.Forms.MouseEventHandler(this.DragForm_MouseDown); }
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;
[System.Runtime.InteropServices.DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
[System.Runtime.InteropServices.DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();

private void DragForm_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        ReleaseCapture();
        SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
        // Checks if Y = 0, if so maximize the form
        if (this.Location.Y == 0) { this.WindowState = FormWindowState.Maximized; }
    }
}

在 InitializeComponent() 后面插入以下内容即可对控件应用拖动功能。
AddDrag(NameOfControl);

4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接