在按住窗口拖动或菜单按钮期间,如何阻止Windows阻止程序?

我是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创建窗口 WS_SYSMENU | WS_MINIMIZEBOX

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

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

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

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

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

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

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

本MSDN文章中提供了更多有用的信息: 防止在Windows应用程序中挂起

特例:模态事件处理循环

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

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

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

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

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

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

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

在这种情况下,您可能会误以为您正在这样做,但WM_SYSCOMMAND可以为其wParam提供许多不同的值。 你只处理其中一个SC_CLOSE 。 所有其余的都被忽略,因为你return 0 。 这包括所有窗口移动和resizefunction(例如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编程模型。 在您需要钩子提供的function的情况下,这是一种罕见的情况。

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

可以在单核处理器上使用多个线程。 在多核系统上性能会更好,但这不应该阻止您编写multithreading应用程序。 无论如何,去吧。

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