如何修复带有边框控件的无边框窗体调整大小问题?

5

我有一个无边框的winForm需要进行调整大小,我通过以下方式实现:

protected override void WndProc(ref Message m)
    {
        const int wmNcHitTest = 0x84;
        const int htLeft = 10;
        const int htRight = 11;
        const int htTop = 12;
        const int htTopLeft = 13;
        const int htTopRight = 14;
        const int htBottom = 15;
        const int htBottomLeft = 16;
        const int htBottomRight = 17;

        if (m.Msg == wmNcHitTest)
        {
            Console.Write(true + "\n");
            int x = (int)(m.LParam.ToInt64() & 0xFFFF);
            int y = (int)((m.LParam.ToInt64() & 0xFFFF0000) >> 16);
            Point pt = PointToClient(new Point(x, y));
            Size clientSize = ClientSize;
            ///allow resize on the lower right corner
            if (pt.X >= clientSize.Width - 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(IsMirrored ? htBottomLeft : htBottomRight);
                return;
            }
            ///allow resize on the lower left corner
            if (pt.X <= 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(IsMirrored ? htBottomRight : htBottomLeft);
                return;
            }
            ///allow resize on the upper right corner
            if (pt.X <= 16 && pt.Y <= 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(IsMirrored ? htTopRight : htTopLeft);
                return;
            }
            ///allow resize on the upper left corner
            if (pt.X >= clientSize.Width - 16 && pt.Y <= 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(IsMirrored ? htTopLeft : htTopRight);
                return;
            }
            ///allow resize on the top border
            if (pt.Y <= 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(htTop);
                return;
            }
            ///allow resize on the bottom border
            if (pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(htBottom);
                return;
            }
            ///allow resize on the left border
            if (pt.X <= 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(htLeft);
                return;
            }
            ///allow resize on the right border
            if (pt.X >= clientSize.Width - 16 && clientSize.Height >= 16)
            {
                m.Result = (IntPtr)(htRight);
                return;
            }
        }
        else
        {
            Console.Write(false + "\n");
        }
        base.WndProc(ref m);
    }

问题在于我的表单左右边框上有控件,因此上述代码中使用的调整大小覆盖不适用于任何类型的控件所在的这些区域。
以下是一个示例:
在上面的图像中,您可以看到标记区域内的标签位于我的表单的左边框上,它不会让我调整大小。
有没有办法解决这个问题?

一个想法是在代码中删除按钮并重新创建它。虽然不是最干净的方法... - SteveFerg
1
你没有理解问题的重点,@SteveFerg。在运行时,标签会捕获鼠标消息,因此当用户将鼠标悬停在标签上方时(而不是窗体边缘),他们无法调整窗体大小。由于鼠标位于标签上方,所以窗体不会收到非客户区命中测试消息。 - Idle_Mind
1个回答

8
这里的问题在于,接收鼠标通知的是标签控件,而不是无边框窗体。解决这个问题最好的方法是使标签对鼠标透明。你已经知道如何做了,WM_NCHITTEST也允许返回HTTRANSPARENT。Windows会继续寻找下一个通知候选者,它将是标签的父级。
对于标签来说这非常容易实现,因为通常你根本不需要使用它的鼠标事件:
using System;
using System.Windows.Forms;

public class LabelEx : Label {
    protected override void WndProc(ref Message m) {
        const int wmNcHitTest = 0x84;
        const int htTransparent = -1;
        if (!DesignMode && m.Msg == wmNcHitTest) m.Result = new IntPtr(htTransparent);
        else base.WndProc(ref m);
    }
}

适用于任何控件类,如果是按钮,则需要更加选择性。如果您有很多不同类型的控件靠近边缘,这种方法可能已经足够,但仍然相当笨拙。在本机Windows编程中,可以使用称为“子类化”的另一种技术。在Winforms中普遍用于为本机Windows控件创建包装器.NET类。这种方法在此处也很有效,您可以窥视任何控件的消息,并以此拦截WM_NCHITTEST:

    const int edge = 16;

    class MouseFilter : NativeWindow {
        private Form form;
        public MouseFilter(Form form, Control child) {
            this.form = form;
            this.AssignHandle(child.Handle);
        }
        protected override void WndProc(ref Message m) {
            const int wmNcHitTest = 0x84;
            const int htTransparent = -1;

            if (m.Msg == wmNcHitTest) {
                var pos = new Point(m.LParam.ToInt32());
                if (pos.X < this.form.Left + edge ||
                    pos.Y < this.form.Top + edge||
                    pos.X > this.form.Right - edge ||
                    pos.Y > this.form.Bottom - edge) {
                    m.Result = new IntPtr(htTransparent);
                    return;
                }
            }
            base.WndProc(ref m);
        }
    }

当控件靠近窗口边缘时,只需为每个控件创建一个MouseFilter实例:

    protected override void OnLoad(EventArgs e) {
        base.OnLoad(e);
        subClassChildren(this.Controls);
    }

    private void subClassChildren(Control.ControlCollection ctls) {
        foreach (Control ctl in ctls) {
            var rc = this.RectangleToClient(this.RectangleToScreen(ctl.DisplayRectangle));
            if (rc.Left < edge || rc.Right > this.ClientSize.Width - edge ||
                rc.Top < edge || rc.Bottom > this.ClientSize.Height - edge) {
                new MouseFilter(this, ctl);
            }
            subClassChildren(ctl.Controls);
        }
    }

汉斯,一个快速的问题:在你的MouseFilter类中,我们真的需要捕获WM_NCDESTROY并手动调用ReleaseHandle()吗?从备注中可以看到,“如果接收到本机Win32 WM_NCDESTROY消息,则窗口会自动调用此方法,表示Windows已销毁句柄。”那么当执行base.WndProc(ref m);时,这个调用是否已经为我们处理了呢? - Idle_Mind
默认处理中存在FUD,NativeWindow.Callback()调用ReleaseHandle(false)不会取消子类化窗口。同意,在这里可能更好。 - Hans Passant

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