【问题标题】:Moving frameless window by dragging it from a portion of client area通过从客户区的一部分拖动来移动无框窗口
【发布时间】:2016-02-20 11:19:50
【问题描述】:

正如标题所说,我只想在用户从客户区的一部分拖动窗口时才移动窗口。这将是对正常标题栏运动的模仿,这是因为我的表单是自定义的,并且没有任何标题或标题栏。目前,我使用的代码如下:

...
case WM_NCHITTEST:
        return HTCAPTION;

这对于让用户无论从哪里拖动都能够移动窗口非常有效。我想限制这种可能性(只有窗口顶部允许移动)。我没有尝试检查鼠标按下的位置,因为我不知道如何在WM_NCHITTEST 消息中进行检查。 我在 Visual Studio 2015 中使用纯 Win32 (winapi) C 代码(目前没有 MFC 或其他任何东西)。

【问题讨论】:

  • 没有称为 C/C++ 的语言。请告诉我们您正在编程的两个中的哪一个并删除另一个标签。
  • 如果消息没有提供光标位置,请使用 GetMessagePos() 获取。
  • @FUZxxl 表示 C 或 C++。 OP 正在寻找答案,因为 WinAPI 和 VS2015 都支持。两个标签都是合适的。
  • @2501 正如其他地方所讨论的那样,这种问题是不鼓励的,因为“C/C++”通常实际上意味着“实际上我需要 C++,但如果有 C 的答案,我可能会把它改造成有用的东西。”询问您的需求,而不是产生尽可能广泛的受众。
  • 指定正确的语言很重要。例如,我查看了标签以了解要使用哪种类型的演员表。不知道。我掷骰子选择了 C++。之所以这么简单,是因为问题完全是关于 Win32,它是一个 C API,因此两种语言的代码看起来非常相似。将来,请务必选择一种语言并坚持下去。不得不重复这个评论讨论 ad nauseum 变老了。

标签: c user-interface winapi custom-controls


【解决方案1】:

如果你只是返回HTCAPTION 来响应所有 WM_NCHITTEST 消息,你会遇到麻烦。您将破坏滚动条、关闭按钮、调整边框大小等都是通过不同的HT* 值实现的。

不过,您的想法是对的。您想让窗口的客户区可拖动,因此您需要诱使 Windows 认为您的客户区实际上是标题区(如您所知,它是可拖动的)。该代码如下所示:

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    // ...
    case WM_NCHITTEST:
    {
        // Call the default window procedure for default handling.
        const LRESULT result = ::DefWindowProc(hWnd, uMsg, wParam, lParam);

        // You want to change HTCLIENT into HTCAPTION.
        // Everything else should be left alone.
        return (result == HTCLIENT) ? HTCAPTION : result;
    }
    // ...
}

但是,根据您问题中的图片,您似乎只想将其限制在窗口的某个区域。您需要准确定义该区域是什么,然后进行命中测试以查看用户是否单击了该区域。假设rcDraggable 是一个包含the red box shown in your image 边界的RECT 结构(在屏幕坐标中),您可以使用以下代码:

static RECT rcDraggable = ...

LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    // ...
    case WM_NCHITTEST:
    {
        // Call the default window procedure for default handling.
        const LRESULT result = ::DefWindowProc(hWnd, uMsg, wParam, lParam);

        // Get the location of the mouse click, which is packed into lParam.
        POINT pt;
        pt.x = GET_X_LPARAM(lParam);
        pt.y = GET_Y_LPARAM(lParam);

        // You want to change HTCLIENT into HTCAPTION for a certain rectangle, rcDraggable.
        // Everything else should be left alone.
        if ((result == HTCLIENT) && (PtInRect(&rcDraggable, pt))
        {
            return HTCAPTION;
        }
        return result;
    }
    // ...
}

如果您根据客户端坐标定义rcDraggable,则需要在进行命中测试以响应WM_NCHITTEST 之前将其转换为屏幕坐标。为此,请致电MapWindowPoints function,如下所示:

RECT rc = rcDraggable;
MapWindowPoints(hWnd,   /* a handle to your window       */
                NULL,   /* convert to screen coordinates */
                reinterpret_cast<POINT*>(&rc),
                (sizeof(RECT) / sizeof(POINT)));

【讨论】:

  • 我实现了这段代码,但即使我认为它是正确的,它也不起作用。 static RECT rcDraggable = {1, 1, 949, 80}; // left, top, right, bottom 作为全局变量,然后是 case WM_NCHITTEST: { const LRESULT result = ::DefWindowProc(hwnd, uMsg, wParam, lParam); POINT pt; pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); RECT rc = rcDraggable; MapWindowPoints(hwnd, NULL, reinterpret_cast&lt;POINT*&gt;(&amp;rc), (sizeof(RECT) / sizeof(POINT))); if ((result == HTCLIENT) &amp;&amp; (PtInRect(&amp;rcDraggable, pt))) return HTCAPTION; return result; }
  • 不要以这种方式复制/粘贴答案。您在正确的屏幕坐标中拥有rc 以在WM_NCHITTEST 中使用,但随后您忽略rc 并查看仍在客户端坐标中的rcDraggable。请改用PtInRect(&amp;rc, pt)
  • 是的,你必须使用正确的矩形,否则你会得到错误的结果。 MapWindowPoints 就地修改矩形。
  • 谢谢,问题解决了。抱歉,很容易发现我的错误,但我无法弄清楚。
【解决方案2】:

您可以在 WM_LBUTTONDOWN 处理程序中调用一些魔术代码,AFAIR 这个:

 ReleaseCapture();
 SendMessage(yourWindowHandle, WM_SYSCOMMAND, 0xf012, 0) ;

几年前我在 Delphi 和 Windows XP 中使用过这种方法。我认为它必须与 c++ 类似。当然,您可以在执行此操作之前检查 x 和 y。

【讨论】:

  • 仅供参考,0xF012SC_DRAGMOVE,即 SC_MOVEHTCAPTION 或在一起。
  • ...这是一个未记录的值,因此像这样使用它会给向后兼容性团队带来麻烦。哦,好吧,我想现在改变它为时已晚。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-27
  • 2011-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-06
相关资源
最近更新 更多