【问题标题】:Custom Resize Handle in Border-less Form C#无边框表单 C# 中的自定义调整大小句柄
【发布时间】:2013-07-18 21:04:42
【问题描述】:

我正在尝试制作从工具栏弹出的无边框表单。我希望用户能够抓住右下角(“调整大小手柄”)并能够调整表单的大小,但不能以任何其他方式调整表单的大小或重新定位。

我听说我可以拦截发送到表单的WM_NCHITTEST 消息并将其结果设置为HTBOTTOMRIGHT,这将让操作系统处理表单的重新调整大小,就像它有一个相当大的框架。我的想法是检测鼠标指针是否进入了我在角落定义的框,如果进入则返回HTBOTTOMRIGHT 结果。

这并不像我预期的那样工作。我能够拦截该消息,但似乎只有当用户将鼠标光标放在表单的 1px 粗边框上时才会发送消息。这意味着它可以按照我的意愿工作,如果您将光标非常精确地定位在右下角。

这是我的WndProc 覆盖:

protected override void WndProc(ref Message m)
{
    const UInt32 WM_NCHITTEST = 0x0084;
    const UInt32 HTBOTTOMRIGHT = 17;
    const int RESIZE_HANDLE_SIZE = 40;
    bool handled = false;
    if (m.Msg == WM_NCHITTEST)
    {
        Size formSize = this.Size;
        Point screenPoint = new Point(m.LParam.ToInt32());
        Point clientPoint = this.PointToClient(screenPoint);
        Rectangle hitBox = new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE);
        if (hitBox.Contains(clientPoint))
        {
            m.Result = (IntPtr)HTBOTTOMRIGHT;
            handled = true;
        }
    }

    if (!handled)
        base.WndProc(ref m);
}

我做错了什么还是有更好的方法来做我想做的事情?

非常感谢。

【问题讨论】:

  • 我是不是在考虑这个问题,或者当您的用户将鼠标悬停在您的自定义 hitbox 上时,您不能将 FormBorderStyle 更改为 Sizeable,而当他们离开时,您就不能更改 FixedSingle 吗?处理来自 Rectangle 类的 MouseEnter 和 MouseLeave 事件。
  • @AntonSemenov:第二种解决方案绕过操作系统处理程序。第一个符合我想要的,但它不起作用。除了窗口的边缘,我没有收到 WM_NCHITTEST 消息。
  • @glace:这会导致窗口边框明显改变,我真的不希望发生这种情况。
  • @Frank Weindel 我明白了。我使用了您示例中的设置,它对窗口外观没有明显影响,我想您还有其他自定义设置。

标签: c# winforms winapi window-resize


【解决方案1】:

只需对您的代码进行少量修改。我添加了WM_MOUSEMOVE 消息处理:

    protected override void WndProc(ref Message m)
    {
        const UInt32 WM_NCHITTEST = 0x0084;
        const UInt32 WM_MOUSEMOVE = 0x0200;
        const UInt32 HTBOTTOMRIGHT = 17;
        const int RESIZE_HANDLE_SIZE = 10;
        bool handled = false;
        if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE )
        {
            Size formSize = this.Size;
            Point screenPoint = new Point(m.LParam.ToInt32());
            Point clientPoint = this.PointToClient(screenPoint);
            Rectangle hitBox = new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE);
            if (hitBox.Contains(clientPoint))
            {
                m.Result = (IntPtr)HTBOTTOMRIGHT;
                handled = true;
            }
        }

        if (!handled)
            base.WndProc(ref m);
    }

顺便说一句,您可以使用ControlPaint.DrawSizeGrip Method http://msdn.microsoft.com/en-us/library/2e1yx2sa.aspx 绘制系统特定的窗口大小夹点

【讨论】:

    【解决方案2】:

    我一直在寻找类似的东西,而 Anton 的代码是一个很好的基础。这就是我最终从各个方面调整大小的工作。我不确定Dictionary 是存储碰撞箱的最佳方式,但我想这并不重要。

    由于我的表单使用 Fill as Dock 参数填充控件,因此我只需向 Form 添加一个 5px 的内边距即可正常工作。

    protected override void WndProc(ref Message m)
    {
        const UInt32 WM_NCHITTEST = 0x0084;
        const UInt32 WM_MOUSEMOVE = 0x0200;
    
        const UInt32 HTLEFT = 10;
        const UInt32 HTRIGHT = 11;
        const UInt32 HTBOTTOMRIGHT = 17;
        const UInt32 HTBOTTOM = 15;
        const UInt32 HTBOTTOMLEFT = 16;
        const UInt32 HTTOP = 12;
        const UInt32 HTTOPLEFT = 13;
        const UInt32 HTTOPRIGHT = 14;
    
        const int RESIZE_HANDLE_SIZE = 10;
        bool handled = false;
        if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE)
        {
            Size formSize = this.Size;
            Point screenPoint = new Point(m.LParam.ToInt32());
            Point clientPoint = this.PointToClient(screenPoint);
    
            Dictionary<UInt32, Rectangle> boxes = new Dictionary<UInt32, Rectangle>() {
                {HTBOTTOMLEFT, new Rectangle(0, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
                {HTBOTTOM, new Rectangle(RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
                {HTBOTTOMRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
                {HTRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE)},
                {HTTOPRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
                {HTTOP, new Rectangle(RESIZE_HANDLE_SIZE, 0, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
                {HTTOPLEFT, new Rectangle(0, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
                {HTLEFT, new Rectangle(0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE) }
            };
    
            foreach (KeyValuePair<UInt32, Rectangle> hitBox in boxes)
            {
                if (hitBox.Value.Contains(clientPoint))
                {
                    m.Result = (IntPtr) hitBox.Key;
                    handled = true;
                    break;
                }
            }
        }
    
        if (!handled)
            base.WndProc(ref m);
    }
    

    【讨论】:

    • 我无法验证这是否有效,因为我放弃了这种方法,但我确实记得我的表格中充满了面板。添加表单填充可能会有所不同。
    • 我已经验证过了。有用。不过记得做填充。
    【解决方案3】:

    Anton Semenov,我没看懂你的代码。

    无论如何,我对 Charles P 的第一个代码有问题,
    当我最大化窗口然后尝试更改其大小时 - 它正在调整大小。
    之后我无法再次将其修复为正常大小,也无法使用正常的最大按钮再次最大化它。

    我为解决这个问题所做的就是在底部的“foreach”循环中添加条件:

        protected override void WndProc(ref Message m)
        {
            const UInt32 WM_NCHITTEST = 0x0084;
            const UInt32 WM_MOUSEMOVE = 0x0200;
    
            const UInt32 HTLEFT = 10;
            const UInt32 HTRIGHT = 11;
            const UInt32 HTBOTTOMRIGHT = 17;
            const UInt32 HTBOTTOM = 15;
            const UInt32 HTBOTTOMLEFT = 16;
            const UInt32 HTTOP = 12;
            const UInt32 HTTOPLEFT = 13;
            const UInt32 HTTOPRIGHT = 14;
    
            const int RESIZE_HANDLE_SIZE = 10;
            bool handled = false;
            if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE)
            {
                Size formSize = this.Size;
                Point screenPoint = new Point(m.LParam.ToInt32());
                Point clientPoint = this.PointToClient(screenPoint);
    
                Dictionary<UInt32, Rectangle> boxes = new Dictionary<UInt32, Rectangle>() {
            {HTBOTTOMLEFT, new Rectangle(0, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
            {HTBOTTOM, new Rectangle(RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
            {HTBOTTOMRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
            {HTRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE)},
            {HTTOPRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
            {HTTOP, new Rectangle(RESIZE_HANDLE_SIZE, 0, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
            {HTTOPLEFT, new Rectangle(0, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
            {HTLEFT, new Rectangle(0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE) }
                };
    
                foreach (KeyValuePair<UInt32, Rectangle> hitBox in boxes)
                {
                    if (this.WindowState != FormWindowState.Maximized 
                        && hitBox.Value.Contains(clientPoint))
                        {
                            m.Result = (IntPtr)hitBox.Key;
                            handled = true;
                            break;
                        }
                }
            }
    
            if (!handled)
                base.WndProc(ref m);
        }
    

    【讨论】:

      【解决方案4】:

      基于 Charles P. 解决方案对其进行了一些修改,希望对其他人也有帮助:) 每次调用 windows 消息时不声明额外变量的小检查和改进。 还检查当窗口状态最大化时不绘制夹点锚。 我想用它创建一个自定义控件,但不幸的是,我最终用这段代码填写了表单。

      构造函数或在设计器文件中:

      this.DoubleBuffered = true;
      this.ResizeRedraw   = true;
      

      覆盖窗口函数:

          const uint WM_NCHITTEST = 0x0084, WM_MOUSEMOVE = 0x0200,
                       HTLEFT = 10, HTRIGHT = 11, HTBOTTOMRIGHT = 17,
                       HTBOTTOM = 15, HTBOTTOMLEFT = 16, HTTOP = 12,
                       HTTOPLEFT = 13, HTTOPRIGHT = 14;
          Size formSize;
          Point screenPoint;
          Point clientPoint;
          Dictionary<uint, Rectangle> boxes;
          const int RHS = 10; // RESIZE_HANDLE_SIZE
          bool handled;
      
          protected override void WndProc(ref Message m)
          {
              if (this.WindowState == FormWindowState.Maximized)
              {
                  base.WndProc(ref m);
                  return;
              }
      
              handled  = false;
              if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE)
              {
                  formSize    = this.Size;
                  screenPoint = new Point(m.LParam.ToInt32());
                  clientPoint = this.PointToClient(screenPoint);
      
                  boxes = new Dictionary<uint, Rectangle>() {
                      {HTBOTTOMLEFT, new Rectangle(0, formSize.Height - RHS, RHS, RHS)},
                      {HTBOTTOM, new Rectangle(RHS, formSize.Height - RHS, formSize.Width - 2*RHS, RHS)},
                      {HTBOTTOMRIGHT, new Rectangle(formSize.Width - RHS, formSize.Height - RHS, RHS, RHS)},
                      {HTRIGHT, new Rectangle(formSize.Width - RHS, RHS, RHS, formSize.Height - 2*RHS)},
                      {HTTOPRIGHT, new Rectangle(formSize.Width - RHS, 0, RHS, RHS) },
                      {HTTOP, new Rectangle(RHS, 0, formSize.Width - 2*RHS, RHS) },
                      {HTTOPLEFT, new Rectangle(0, 0, RHS, RHS) },
                      {HTLEFT, new Rectangle(0, RHS, RHS, formSize.Height - 2*RHS) }
                  };
      
                  foreach (var hitBox in boxes)
                  {
                      if (hitBox.Value.Contains(clientPoint))
                      {
                          m.Result = (IntPtr)hitBox.Key;
                          handled  = true;
                          break;
                      }
                  }
              }
      
              if (!handled)
                  base.WndProc(ref m);
          }
      
      
          protected override void OnPaint(PaintEventArgs e)
          {
              if (this.WindowState != FormWindowState.Maximized)
              {
                  ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor,
                      this.ClientSize.Width - 16, this.ClientSize.Height - 16, 16, 16);
              }
      
              base.OnPaint(e);
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-18
        • 1970-01-01
        • 2018-03-18
        • 1970-01-01
        相关资源
        最近更新 更多