【问题标题】:How do I stop Windows from blocking the program during a window drag or menu button being held down?如何在按住窗口拖动或菜单按钮时阻止 Windows 阻止程序?
【发布时间】:2013-08-05 04:24:40
【问题描述】:

我是Win32 的新手,我一直在追查一个问题(如果它可以被称为问题的话),当用户抓住窗口标题栏并四处移动时,Windows 会阻塞你的程序流屏幕。

我没有正当理由解决这个问题,只是它让我很困扰。一些可能性包括完全移除框架,但这似乎是一个不方便的黑客攻击。有些游戏(单人游戏)根本不觉得这是个问题。然而,我已经读到,多人游戏在程序冻结时可能会遇到问题,因为它需要持续的信息流,并且在这样的延迟后可能会不堪重负。

我已尝试将此添加到我的WindowProc

switch (uMsg)
{
    case WM_SYSCOMMAND:
        if (wParam == SC_CLOSE)
            PostQuitMessage(0);

        return 0;
    ...
    ...
    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;

这似乎是一个快速的技巧,除了当我将鼠标放在关闭图标上时,我可以将鼠标拉开并松开而不关闭程序,并且在此期间,当关闭图标被按住时,程序再次被屏蔽了。

此外,当用户单击标题栏并拖动鼠标时,我不知道如何手动包含移动窗口所需的代码。对于初学者,我不知道要处理哪个uMsgwParam

然后我的问题是,在用户单击退出按钮(或最小化/最大化按钮)的情况下如何禁止阻塞,同时在单击鼠标并在按钮上释放时仍处理该情况,以及如何做我允许用户在不阻塞程序的情况下移动/拖动窗口(或者单击标题栏而不是按钮或菜单时会发送什么消息)?

我正在使用WS_SYSMENU | WS_MINIMIZEBOX 创建窗口。

我仍然希望程序响应最小化、最大化和退出命令。

如果多线程可以修复它,那很有趣,但我想知道我是否可以让它在单核处理器上工作。而且我已经阅读了有关钩子的内容,但 MSDN 页面对我来说仍然很难解释。

【问题讨论】:

  • program is blocked,我认为您的意思是您没有收到任何 Windows 消息; it expects continuous flow of information 到底是什么意思?您是否期望 Windows 消息、WM_TIMER 消息、来自端口的信息...?
  • 没有被屏蔽,只是鼠标捕捉有问题。您可以通过拦截需要鼠标捕获才能正常工作的消息来避免它。使用 Spy++ 实用程序是查看这些消息并知道您想要自己处理哪些消息的好方法。

标签: c++ c winapi windows-messages


【解决方案1】:

如果多线程可以解决它,那很有趣,但我想知道 如果我能让它在单核处理器上工作。我读过 关于钩子,但我仍然很难解释 MSDN 页面。

可以在单核处理器上使用多个线程。在多核系统上性能会更好,但这不应妨碍您编写多线程应用程序。不管怎样,去吧。

【讨论】:

  • 这很有趣,但我需要学习多线程,我不确定它是否能解决我的问题。
  • 感谢您的热情,我想我找到了另一个学习多线程的理由。
  • @Leonardo 另外,请考虑改用 GUI 库。 FLTK 会很棒,因为它是跨平台的并且也支持 OpenGL。
【解决方案2】:

通过打印发送到WindowProc 的所有消息,WM_NCLBUTTONDOWN 似乎是在块发生之前最后发送的。您可以在此事件发生后检查鼠标位置,但这似乎是解决简单问题的不方便方法。

【讨论】:

  • 您应该将 GUI 和您的处理单元保持在单独的线程中。这是一个规则:)
【解决方案3】:

为什么我的应用程序冻结?——消息循环和线程简介

这种现象并非孤立于任何特定消息。这是 Windows 消息循环的一个基本属性:在处理一条消息时,不能同时处理其他消息。它并不是完全以这种方式实现的,但您可以将其视为一个队列,您的应用将消息从队列中拉出,以与插入消息相反的顺序进行处理。

因此,处理 any 消息的时间过长将暂停其他消息的处理,从而有效地冻结您的应用程序(因为它无法处理任何输入)。解决这个问题的唯一方法是显而易见的:不要花太长时间处理任何一条消息。

这通常意味着将处理委托给后台线程。您仍然需要在主线程上处理所有消息,并且后台工作线程需要在完成后向主方法报告。与 GUI 的所有交互都需要在单个线程上进行,而这几乎总是应用程序中的主线程(这就是它通常被称为 UI 线程的原因)。

(为了回答您的问题中提出的反对意见,是的,您可以在单处理器机器上操作多个线程。您不一定会看到任何性能改进,但它会使 UI 更具响应性。这里的逻辑是一个线程一次只能做一件事,但处理器可以非常快速地在线程之间切换,有效地模拟一次做不止一件事。)

更多有用的信息可以在这篇 MSDN 文章中找到:Preventing Hangs in Windows Applications

特殊情况:模态事件处理循环

Windows 上的某些窗口操作是模态 操作。模态是计算中的一个常用词,基本上是指将用户锁定在特定模式中,在这种模式下他们无法做任何其他事情,直到他们改变(即退出)模式。每当模式操作开始时,就会启动一个单独的新消息处理循环,并且在模式期间那里(而不是您的主消息循环)进行消息处理。这些模态操作的常见示例是拖放、窗口大小调整和消息框。

考虑这里的窗口大小调整示例,您的窗口会收到一个WM_NCLBUTTONDOWN 消息,您将其传递给DefWindowProc 进行默认处理。 DefWindowProc 发现用户打算开始移动或调整大小操作,并进入了位于 Windows 自己代码深处某处的移动/调整大小消息循环。因此,您的应用程序的消息循环不再运行,因为您已进入新的移动/调整大小模式。

只要用户以交互方式移动/调整窗口大小,Windows 就会运行此移动/调整大小循环。它这样做是为了拦截鼠标消息并相应地处理它们。当移动/调整大小操作完成时(例如,当用户释放鼠标按钮或按下 Esc 键时),控制将返回到您的应用程序代码。

值得指出的是,您通过WM_ENTERSIZEMOVE message 通知您此模式更改已发生;对应的WM_EXITSIZEMOVE message 表示模式事件处理循环已退出。这允许您创建一个计时器,该计时器将继续生成您的应用程序可以处理的WM_TIMER 消息。如何实现的实际细节相对不重要,但简单的解释是DefWindowProc 继续在其自己的模式事件处理循环内向您的应用程序发送WM_TIMER 消息。使用SetTimer function 创建一个计时器以响应WM_ENTERSIZEMOVE 消息,并使用KillTimer function 销毁它以响应WM_EXITSIZEMOVE 消息。

不过,我只是为了完整性而指出这一点。在我编写的大多数 Windows 应用程序中,我从来不需要这样做。

那么,我的代码有什么问题?

除此之外,您在问题中描述的行为很不寻常。如果您使用 Visual Studio 模板创建一个新的空白 Win32 应用程序,我怀疑您是否能够复制此行为。在没有看到您的窗口过程的其余部分的情况下,我无法判断您是否阻止了任何消息(如上所述),但我在问题中看到的部分 可以 是错误的。对于您自己未明确处理的消息,您必须始终致电DefWindowProc

在这种情况下,您可能会误以为您正在这样做,但WM_SYSCOMMANDwParam 可以有很多不同的值。你只处理其中的一个SC_CLOSE。因为你return 0,其余的都被忽略了。这包括所有的窗口移动和调整大小功能(例如SC_MOVESC_SIZESC_MINIMIZESC_RESTORESC_MAXIMIZE 等)。

真的没有充分的理由自己处理WM_SYSCOMMAND;让DefWindowProc 为您处理。唯一需要处理WM_SYSCOMMAND 的情况是当您将自定义项添加到窗口菜单时,即使这样,您也应该将您无法识别的每个命令传递给DefWindowProc

一个基本的窗口过程应该是这样的:

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
        case WM_CLOSE:
            DestroyWindow(hWnd);
            return 0;
        
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

您的消息循环可能是错误的。惯用的 Win32 消息循环(位于 WinMain 函数底部附近)如下所示:

BOOL ret;
MSG msg;
while ((ret = GetMessage(&msg, nullptr, 0, 0)) != 0)
{
    if (ret != -1)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else
    {
        // An error occurred! Handle it and bail out.
        MessageBox(nullptr, L"Unexpected Error", nullptr, MB_OK | MB_ICONERROR);
        return 1;
    }
}

您不需要任何类型的钩子。有关这些的 MSDN 文档非常好,但您是对的:它们很复杂。在您对 Win32 编程模型有更好的理解之前,请远离。需要钩子提供的功能确实很少见。

【讨论】:

  • 绝妙的答案,我确切地知道从这里去哪里。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-11
  • 2019-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多