简体   繁体   English

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

[英]How do I stop Windows from blocking the program during a window drag or menu button being held down?

I am novice with Win32 , and I have been pursuing a problem (if it can be called a problem at all) with Windows blocking your program's flow during the event when a user grabs the window title bar and moves it around the screen. 我是Win32的新手,我一直在追求一个问题(如果它可以被称为问题),当用户抓住窗口标题栏并在屏幕上移动时,Windows会阻止程序的流程。

I have no legitimate reason to solve this problem, except that it bothers me. 我没有合理的理由来解决这个问题,除非它困扰我。 A few possibilities include removing the frame altogether, but it seems an inconvenient hack. 一些可能性包括完全删除框架,但它似乎是一个不方便的黑客。 Some games (single player) do not find this a problem at all. 有些游戏(单人游戏)根本没有发现这个问题。 I have read however, that multiplayer games might experience problems when the program freezes as it expects continuous flow of information and can be overwhelmed after such a delay. 然而,我已经阅读过,当程序冻结时,多人游戏可能会遇到问题,因为它期望信息不断流动,并且在这样的延迟之后可能会被淹没。

I have tried adding this to my WindowProc 我试过把它添加到我的WindowProc

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

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

And this seems a quick hack, except that when I mousedown over the close icon I can pull the mouse away and let go without closing the program, and during that time, when the close icon is held down, the program once again is blocked. 这似乎是一个快速的黑客,除了当我在关闭图标上徘徊时,我可以拉开鼠标并放开而不关闭程序,在此期间,当关闭图标时,程序再次被阻止。

Furthermore, I do not know how to manually include the code necessary to move the window when the user clicks the titlebar and drags the mouse. 此外,我不知道如何在用户单击标题栏并拖动鼠标时手动包含移动窗口所需的代码。 For starters I do not know which uMsg 's and wParam 's to handle. 对于初学者,我不知道要处理哪个uMsgwParam

My question is then, how do I disallow blocking during the case when the user clicks down the exit button (or minimize/maximize buttons) while still handling the case when the mouse is clicked and released over the button, and how do I allow the user to move/drag the window without it blocking the program (or what message is sent when the title bar is clicked without it being a button or menu)? 我的问题是,在用户点击退出按钮(或最小化/最大化按钮)的情况下,如何在单击鼠标并通过按钮释放时仍处理案例时,如何禁止阻止,以及如何允许用户移动/拖动窗口而不阻止程序(或单击标题栏时发送的消息,而不是按钮或菜单)?

I am creating the window with WS_SYSMENU | WS_MINIMIZEBOX 我正在使用WS_SYSMENU | WS_MINIMIZEBOX创建窗口 WS_SYSMENU | WS_MINIMIZEBOX . WS_SYSMENU | WS_MINIMIZEBOX

I still want the program to respond to minimize, maximize, and exit commands. 我仍然希望程序响应最小化,最大化和退出命令。

If multi-threading can fix it, then that is interesting, but I wonder if I can get it to work on single-core processors. 如果多线程可以解决它,那么这很有趣,但我想知道我是否可以让它在单核处理器上工作。 And I have read about hooks, but the MSDN page is still hard for me to interpret. 我已经阅读了有关钩子的内容,但MSDN页面仍然难以解释。

Why Is My App Freezing?—An Introduction to Message Loops & Threads 为什么我的应用程序冻结? - 消息循环和线程简介

This phenomenon is not isolated to any particular message. 这种现象不会与任何特定信息隔离。 It's a fundamental property of the Windows message loop: when one message is being processed, no other message can be processed at the same time. 它是Windows消息循环的基本属性:当处理一条消息时,不能同时处理其他消息。 It's not exactly implemented this way, but you can think of it as a queue, where your app pulls the messages out of the queue to process in the reverse order that they are inserted. 它并没有完全以这种方式实现,但您可以将其视为一个队列,您的应用程序将消息从队列中拉出以按照插入的相反顺序进行处理。

Therefore, spending too long processing any message is going to suspend the processing of other messages, effectively freezing your application (because it cannot process any input). 因此,花费太长时间处理任何消息将暂停其他消息的处理,从而有效地冻结您的应用程序(因为它无法处理任何输入)。 The only way to solve this problem is the obvious one: don't spend too long processing any one message. 解决这个问题的唯一方法是显而易见的:不要花太多时间处理任何一条消息。

Often that will mean delegating the processing to a background thread. 通常这意味着将处理委托给后台线程。 You will still need to handle all messages on the main thread, and the background worker threads need to report back to the main method when they are finished. 您仍然需要处理主线程上的所有消息,后台工作线程需要在完成后报告主方法。 All interaction with the GUI needs to happen on a single thread, and that is almost always the main thread in your application (which is why it is often called the UI thread). 所有与GUI的交互都需要在一个线程上进行,这几乎总是应用程序中的主线程(这就是它通常被称为UI线程的原因)。

(And to answer an objection raised in your question, yes, you can operate multiple threads on single processor machines. You won't necessarily see any performance improvements, but it will make the UI more responsive. The logic here is that a thread can only do one thing at a time, but a processor can switch between threads extremely rapidly, effectively simulating doing more than one thing at a time.) (并回答你的问题中提出的异议,是的,你可以在单处理器机器上运行多个线程。你不一定会看到任何性能改进,但它会使UI更具响应性。这里的逻辑是一个线程可以只做一件事,但处理器可以非常快速地在线程之间切换,有效地模拟一次做多件事。)

More useful information is available here in this MSDN article: Preventing Hangs in Windows Applications 本MSDN文章中提供了更多有用的信息: 防止在Windows应用程序中挂起

Special Cases: Modal Event Processing Loops 特例:模态事件处理循环

Certain window operations on Windows are modal operations. Windows上的某些窗口操作是模态操作。 Modal is a common word in computing that basically refers to locking the user into a particular mode where they cannot do anything else until they change (ie get out of that) modes. 模态是计算中常见的一个词,它基本上是指将用户锁定到一个特定的模式,在这种模式下,他们无法做任何事情,直到他们改变(即离开)模式。 Whenever a modal operation is begun, a separate new message processing loop is spun up and message handling happens there (instead of your main message loop) for the duration of the mode. 无论何时开始模态操作,都会在模式持续时间内启动单独的新消息处理循环并在那里进行消息处理(而不是主消息循环)。 Common examples of these modal operations are drag-and-drop, window resizing, and message boxes. 这些模态操作的常见示例是拖放,窗口大小调整和消息框。

Considering the example here of window resizing, your window receives a WM_NCLBUTTONDOWN message, which you pass to DefWindowProc for default processing. 考虑到窗口大小调整的示例,您的窗口会收到WM_NCLBUTTONDOWN消息,您将其传递给DefWindowProc以进行默认处理。 DefWindowProc figures out that the user intends to start a move or resize operation, and entered a moving/sizing message loop located somewhere deep in the bowels of Windows' own code. DefWindowProc指出用户打算开始移动或调整大小操作,并进入位于Windows自身代码深处某处的移动/大小调整消息循环。 Thus, your application's message loop is no longer running because you've entered into a new moving/sizing mode. 因此,您的应用程序的消息循环不再运行,因为您已进入新的移动/大小调整模式。

Windows runs this moving/sizing loop as long as the user is interactively moving/sizing the window. 只要用户以交互方式移动/调整窗口大小,Windows就会运行此移动/大小调整循环。 It does this so that it can intercept mouse messages and process them accordingly. 它这样做是为了拦截鼠标消息并相应地处理它们。 When the moving/sizing operation completes (eg, when the user releases the mouse button or presses the Esc key), control will return to your application code. 当移动/调整大小操作完成时(例如,当用户释放鼠标按钮或按下Esc键时),控制将返回到您的应用程序代码。

It is worth pointing out that you are notified that this mode change has occurred via the WM_ENTERSIZEMOVE message ; 值得指出的 ,通过WM_ENTERSIZEMOVE消息通知您已发生此模式更改; the corresponding WM_EXITSIZEMOVE message indicates that the modal event-processing loop has exited. 相应的WM_EXITSIZEMOVE消息表明模态事件处理循环已退出。 That allows you to create a timer that will continue to generate WM_TIMER messages that your application can process. 这允许您创建一个计时器,该计时器将继续生成应用程序可以处理的WM_TIMER消息。 The actual details of how this is implemented are relatively unimportant, but the quick explanation is that DefWindowProc continues to dispatch WM_TIMER messages to your application inside of its own modal event processing loop. 实现方法的实际细节相对不重要,但快速解释是DefWindowProc继续在其自己的模态事件处理循环内将WM_TIMER消息分派给您的应用程序。 Use the SetTimer function to create a timer in response to the WM_ENTERSIZEMOVE message, and the KillTimer function to destroy it in response to the WM_EXITSIZEMOVE message. 使用SetTimer函数创建一个响应WM_ENTERSIZEMOVE消息的计时器,并使用KillTimer函数销毁它以响应WM_EXITSIZEMOVE消息。

I only point that out for completeness, though. 不过,我只是指出完整性。 In the majority of Windows apps that I've written, I've never needed to do that. 在我编写的大多数Windows应用程序中,我从来不需要这样做。

So, What Is Wrong With My Code? 那么,我的代码有什么不对?

Aside from all of that, the behavior you describe in the question are unusual. 除此之外,您在问题中描述的行为是不寻常的。 If you create a new, blank Win32 application using the Visual Studio template, I doubt you will be able to replicate this behavior. 如果您使用Visual Studio模板创建一个新的空白Win32应用程序,我怀疑您将能够复制此行为。 Without seeing the rest of your window procedure, I can't tell if you're blocking on any messages (as discussed above), but the part I can see in the question is wrong. 没有看到你的窗口过程的其余部分,我不能告诉您是否封锁任何消息(如上所述),但我可以在问题中看到的部分是错误的。 You must always call DefWindowProc for messages that you do not explicitly process yourself. 您必须始终为未自行处理的消息调用DefWindowProc

In this case, you might be fooled into thinking that you're doing that, but WM_SYSCOMMAND can have lots of different values for its wParam . 在这种情况下,您可能会误以为您正在这样做,但WM_SYSCOMMAND可以为其wParam提供许多不同的值。 You only handle one of those, SC_CLOSE . 你只处理其中一个SC_CLOSE All of the rest of them just get ignored because you return 0 . 所有其余的都被忽略,因为你return 0 That includes all of the window moving and resizing functionality (eg SC_MOVE , SC_SIZE , SC_MINIMIZE , SC_RESTORE , SC_MAXIMIZE , etc. etc.). 这包括所有窗口移动和调整大小功能(例如SC_MOVESC_SIZESC_MINIMIZESC_RESTORESC_MAXIMIZE等)。

And there's really no good reason to handle WM_SYSCOMMAND yourself; 并且没有充分的理由自己处理WM_SYSCOMMAND ; just let DefWindowProc take care of it for you. DefWindowProc为您处理它。 The only time you need to handle WM_SYSCOMMAND is when you've added custom items to the window menu, and even then, you should pass every command that you do not recognize on to DefWindowProc . 您需要处理WM_SYSCOMMAND的唯一时间是将自定义项添加到窗口菜单中,即使这样,您也应该将您无法识别的每个命令传递给DefWindowProc

A basic window procedure should look like this: 基本窗口过程应如下所示:

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);
}

It is also possible that your message loop is wrong. 您的消息循环可能是错误的。 The idiomatic Win32 message loop (located near the bottom of your WinMain function) looks like this: 惯用的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;
    }
}

You do not need hooks of any kind. 你不需要任何类型的钩子。 The MSDN documentation on these is very good, but you're right: they're complicated. 关于这些的MSDN文档是非常好的,但你是对的:它们很复杂。 Stay away until you have a better understanding of the Win32 programming model. 远离,直到您更好地了解Win32编程模型。 It is a rare case indeed where you need the functionality provided by a hook. 在您需要钩子提供的功能的情况下,这是一种罕见的情况。

If multi-threading can fix it, then that is interesting but I wonder if I can get it to work on single-core processors. 如果多线程可以解决它,那么这很有趣,但我想知道我是否可以让它在单核处理器上工作。 And I have read about hooks, but the MSDN page is still hard for me to interpret. 我已经阅读了有关钩子的内容,但MSDN页面仍然难以解释。

You can use multiple threads on a single-core processor. 可以在单核处理器上使用多个线程。 Performance would be better on multi-core systems, but that shouldn't prevent you from writing multithreaded applications. 在多核系统上性能会更好,但这不应该阻止您编写多线程应用程序。 Anyway, go for it. 无论如何,去吧。

By printing all messages sent to WindowProc it appears WM_NCLBUTTONDOWN is sent last before the block occurs. 通过打印发送到WindowProc所有消息,在块发生之前最后发送WM_NCLBUTTONDOWN You could check the mouse location after this event occurs, but it seems an inconvenient way to solve a simple problem. 您可以在此事件发生后检查鼠标位置,但这似乎是解决简单问题的一种不方便的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM