如何在Windows窗体应用程序中设计自定义关闭、最小化和最大化按钮?

8

我正在创建一个Windows窗体应用程序项目,我想要自定义边框和三个按钮(关闭,最小化和最大化),并且要使用我的设计。我不知道该如何实现这一点,甚至不确定是否可能。如果可以,请告诉我解决方案。谢谢。

5个回答

31

是的,这是可能的,而且不需要额外的库。

首先,隐藏窗口的原始边框。

public Form1()
{
    InitializeComponent();

    FormBorderStyle = FormBorderStyle.None;
}

接下来,创建一个面板或者其他你想要的东西,包含三个按钮(我知道它很丑,只是为了演示):

在此输入图片描述

然后,使用WindowState将每个按钮分配正确的操作:

private void minimizeButton_Click(object sender, System.EventArgs e)
{
    WindowState = FormWindowState.Minimized;
}

private void maximizeButton_Click(object sender, System.EventArgs e)
{
    WindowState = FormWindowState.Maximized;
}

private void closeButton_Click(object sender, System.EventArgs e)
{
    Close();
}

最后,使用我们的面板使表单可拖动。在类级别添加以下内容:

public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HTCAPTION = 0x2;
[DllImport("User32.dll")]
public static extern bool ReleaseCapture();
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);

然后将它们插入到面板的MouseDown事件中:

private void OnMouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        ReleaseCapture();
        SendMessage(Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0);
    }
}

现在你有了一个可拖动的表单,带有自己的顶部栏。

如果您希望它可以调整大小,如@PhilWright所提到的,您可以从WndProc捕获WM_NCHITTEST消息,并返回HTBOTTOMRIGHT以触发调整大小:

protected override void WndProc(ref Message m)
{
    if (m.Msg == 0x84)
    { 
        const int resizeArea = 10;
        Point cursorPosition = PointToClient(new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16));
        if (cursorPosition.X >= ClientSize.Width - resizeArea && cursorPosition.Y >= ClientSize.Height - resizeArea )
        {
            m.Result = (IntPtr)17;
            return;
        }
    }

    base.WndProc(ref m);
}

如 @BenVoigt 所述,您可以在按钮/面板上使用 DockAnchor 属性,以便它们可以适当地调整大小。 如果您不这样做,它们将不会随着表单的调整而改变大小。


好的示例代码。最好覆盖WM_HITTEST消息,然后在标题栏和窗口边缘上返回正确的值。这样,您就可以自动获得窗口调整大小的功能。 - Phil Wright
@PhilWright 我添加了调整大小的示例代码,感谢您的建议。 - Pierre-Luc Pineault
你必须手动调整它的宽度和按钮位置......但如果使用 DockAnchor 属性,则不需要手动调整,这些属性是专门用于此设计的。 - Ben Voigt
@BenVoigt 哦!这就是后端人员谈论前端时发生的事情。谢谢,我会立即更正,确实更清晰了。 - Pierre-Luc Pineault
通过浏览偶然发现这个问题,令人惊讶的是,在所有答案中都没有提到如何处理文档较少且令人望而生畏的WM_NCPAINT消息。.Net框架至少让我通过处理WM_NCPAINT和所有WM_NC*消息来轻松自定义窗体的非客户区。 - Abet

5

补充一下 @Pierre-Luc 的有用解决方案。当窗口最大化后,如果再次单击最大化按钮,如何将其调整回正常位置。以下是代码:

private static bool MAXIMIZED = false;
private void maximizeButton_Click(object sender, System.EventArgs e)
{
    if(MAXIMIZED)
    {
        WindowState = FormWindowState.Normal;
        MAXIMIZED = false;
    }
    else
    {
        WindowState = FormWindowState.Maximized;
        MAXIMIZED = true;
    }
}

编辑:如评论中@LarsTech所建议的

private void maximizeButton_Click(object sender, System.EventArgs e)
{
    if (this.WindowState != FormWindowState.Maximized)
        this.WindowState = FormWindowState.Maximized;
    else
        this.WindowState = FormWindowState.Normal;
}

3
这个信息不需要额外的变量,if (this.WindowState == FormWindowState.Maximized)已经可以提供这个信息了。 - LarsTech
哇,很高兴看到这个帖子在2年后仍然持续着。我当时能够实现相同的解决方案,但我想我没有恰当地阐述我的问题。所以我想要实现一个自定义解决方案,同时保持窗口动画效果,我相信如果我们使用上面的解决方案,我们会失去最小化和最大化动画效果的能力... - Karedia Noorsil

1
一个小细节没有被@Pierre_Luc提到。如果您在面板上添加任何控件,例如标题或图标,则单击这些控件时,窗体将无法拖动。为了实现这一点,我发现在窗体的加载事件中向所有控件(除了最小化、最大化和关闭按钮)添加相同的事件处理程序非常方便。
private void Form1_Load(object sender, EventArgs e)
    {
        foreach (Control Control in this.HeaderPanel.Controls)
        {
            if (!(Control is Button)) //Change here depending on the Library you use for your contols
            {
                Control.MouseDown += new System.Windows.Forms.MouseEventHandler(this.OnMouseDown);
            }
        }
    }

此外,这是一个完整的WndProc版本,可以提供所有方向(左、右、下)的调整大小功能。

protected override void WndProc(ref Message m)
    {
        if (m.Msg == 0x84)
        {
            const int resizeArea = 10;
            Point cursorPosition = PointToClient(new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16));
            if (cursorPosition.X >= ClientSize.Width - resizeArea && cursorPosition.Y >= ClientSize.Height - resizeArea)
            {
                m.Result = (IntPtr)17; //HTBOTTOMRIGHT
                return;
            }
            else if (cursorPosition.X <= resizeArea && cursorPosition.Y >= ClientSize.Height - resizeArea)
            {
                m.Result = (IntPtr)16; //HTBOTTOMLEFT
                return;
            }
            else if (cursorPosition.X <= resizeArea)
            {
                m.Result = (IntPtr)10; //HTLEFT
                return;
            }
            else if (cursorPosition.X >= ClientSize.Width - resizeArea)
            {
                m.Result = (IntPtr)11; //HTRIGHT
                return;
            }
            else if (cursorPosition.Y >= ClientSize.Height - resizeArea)
            {
                m.Result = (IntPtr)15; //HTBOTTOM
                return;
            }
        }

        base.WndProc(ref m);
    }

0

我知道这已经过时了,但希望它能帮助到某些人...

与其使用“最大化”,不如使用“工作区”,否则它会覆盖任务栏。

有一个全局布尔变量:private static bool trip = false;

然后在你的函数中:

        if (trip == false)
        {
            Left = Top = 0;
            Width = Screen.PrimaryScreen.WorkingArea.Width;
            Height = Screen.PrimaryScreen.WorkingArea.Height;
            trip = true;
        }
        else if (trip == true)
        {
            Width = 1535; // my original form size
            Height = 937; // my original form size
            this.Left = leftPOS; // POS are filled on form load for current positions
            this.Top = topPOS;
            trip = false;
        }

没有 trip bool,我无法在再次按下最大化按钮后将表单返回到正常大小。


-1

这是完全可能的,但对于初学者来说并不容易。主要的组件供应商,如Infragistics、DevExpress、ComponentOne、SyncFusion等都提供了可以自定义窗体外观的库。


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