有没有一种方法可以“延迟”WM_ACTIVATE / WM_ACTIVATEAPP消息?

3
考虑一个只有一个窗口的应用程序。
如果窗口被激活 - 也就是窗口过程(WindowProc回调函数)接收到消息WM_ACTIVATE(或类似的WM_ACTIVATEAPP),它会执行一个操作。 (该操作是检查已打开的文件是否有更改;如果有,则询问用户是否要重新加载该文件)
问题在于:当用户执行像单击应用程序的关闭按钮或通过拖动标题栏移动窗口这样的操作时,WM_ACTIVATE/WM_ACTIVATEAPP消息也会被发送。但是,在接收到WM_CLOSE或WM_MOVE等相应消息之前,这些消息会先被接收。在这些情况下,显然希望在询问用户之前等待用户完成操作。
是否有可能延迟处理WM_ACTIVATE/WM_ACTIVATEAPP消息(当窗口即将被激活时分派的消息),并首先处理其他消息(在激活窗口的过程中分派的消息)?关键在于(如果我没有错的话),我没有办法知道最初处理WM_ACTIVATE/WM_ACTIVATEAPP消息时是否会接收到另一条消息,那么我应该如何根据未来发生的事情改变自己的行为呢?同时,我也找不到另一条消息,在窗口被激活后分派(这基本上是所需的)。

1
WM_CLOSE 是一个特殊情况,但是仅仅因为用户稍微移动了窗口,就破坏你的应用程序这个功能似乎有些愚蠢。你可以将“是否要重新加载文件?”对话框设置为非模态,这样用户仍然可以自由地与主窗口交互(如果他们关闭它,你只需要关闭对话框)。 - undefined
1
只是作为一个数据点,当应用程序做这种事情时,它让我非常恼火。我经常因为各种原因切换窗口,而且我不希望它产生副作用。 - undefined
我同意@HarryJohnston的观点,从用户体验的角度来看,任何你所做的事情都需要首先是非侵入性的。弹出一个模态对话框是不可接受的。我会讨厌你的软件。找到一种更优雅的方式来解决这个问题,比如使用非侵入式的覆盖层,或者其他不会完全打断我正在进行的操作的方法。一旦变得非侵入性,它立即也解决了你整个问题! - undefined
一个可能的替代方案(供参考)是使用FindFirstChangeNotification持续监视文件,而不仅在接收到WM_ACTIVATE消息时检查它。 - undefined
3个回答

2

我认为你不能延迟这条消息,因为消息序列是由Windows定义的。

实现你想要的行为的一种方法是使用带有一些毫秒延迟的定时器来推迟检查文件更改。这将允许您在清楚用户是否想要编辑某些内容或正在关闭窗口后检查文件更新。如果延迟足够短,用户甚至不会注意到...

以下代码片段显示了如何实现此操作:

#define IDT_UPDATE_TIMER   1000


UINT_PTR timerId = NULL;   /* needs to be stored along with other window data */

switch (uMsg)
{
   WM_ACTIVATE:
   WM_ACTIVATEAPP:
      /* create timer to delay checking for file updates... */
      timerId = SetTimer(hWnd, IDT_UPDATE_TIMER, 50, NULL);
      break;

   WM_CLOSE:
      /* cancel timer since window is being closed */
      KillTimer(hWnd, timerId);
      break;

   WM_TIMER:
      switch (wParam)
      {
         case IDT_UPDATE_TIMER:
            /* cancel timer to avoid retesting the file every 50ms */
            KillTimer(hWnd, timerId);

            /* check for file updates... */
            break;
      }
      break;
}

不得不给这个点赞,看起来我们得出了相同的结论! - undefined
1
我强烈反对在这里使用定时器:如果我缓慢移动窗口,没有任何延迟是足够的,但另一方面,长时间的延迟会让用户在弹出窗口出现之前修改文件的陈旧内容。 - undefined
@VladFeinstein:没错,但我只是使用定时器来延迟检查文件更新,直到你收到进一步的消息并知道为什么窗口被激活。所以,如果在WM_ACTIVATE之后收到WM_MOVE消息,你必须重置(而不是取消)定时器。因此,在停止移动窗口(不再接收WM_MOVE消息)之后,重置定时器将会过期,并且会检查文件是否有更新(在我的情况下,是在你停止移动窗口后的50毫秒)。对于涉及用户操作的WM_SIZE和其他所有消息也需要进行相同的操作... - undefined
这个基本上是有缺陷的:你的更新不依赖于时间,而是依赖于状态。例如,如果我抓住你(不活动的)标题栏并暂停,那么在我持续拖动的时候将不会有任何消息,包括MOVE或其他消息。这样的时间对你来说足够好吗?你看到我下面提出的解决方案了吗? - undefined

2

正如我在上面评论的那样,我强烈反对在这里使用计时器。

有些情况下,您不希望立即采取行动,因为客户端仍在执行某些操作(移动、调整大小等)。这些操作通常需要鼠标捕获。因此,我的建议是:

当窗口被激活时,向其发布一些命令消息。如果它被关闭,它将永远不会来处理该命令。

在处理该消息时,检查是否正在捕获鼠标,如果是,则设置一个标志;当您收到WM_CAPTURECHANGED消息并且它被设置时,请重新发布相同的消息给自己。

以下是代码:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static bool bWaitForCapture = false;
    switch (message)
    {
    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case WM_APP+1:
            if ((bWaitForCapture = ::GetCapture() == hWnd) != true)
                ::OutputDebugString(L"WM_COMMAND : WM_APP+1\n");
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_ACTIVATE:
        if (wParam != WA_INACTIVE)
            ::PostMessage(hWnd, WM_COMMAND, WM_APP + 1, 0);
        break;
    case WM_CAPTURECHANGED:
        if (bWaitForCapture)
            ::PostMessage(hWnd, WM_COMMAND, WM_APP + 1, 0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

我看到使用计时器可能存在的问题,实际上你回答的第一部分非常好(使用PostMessage一次将命令消息放到队列的末尾)。 然而整个“bWaitForCapture”逻辑似乎根本有问题:它在窗口移动后不执行操作,并且在其他情况下陷入无限循环(不断接收WM_CAPTURECHANGED),这导致了许多重绘问题。有没有办法修复这个问题? - undefined
这段代码对我来说运行良好。bWaitForCapture 是静态的,只针对该 WndProc,仅在接收到 WM_APP+1 命令时设置/重置,具体取决于您的窗口是否此时正在捕获(即 - 拖动或重新调整大小)。当捕获自然更改时,会出现 WM_CAPTURECHANGED,如果您正在捕获,我只会重新发布 WM_APP + 1 命令。你是否碰巧在调试器下测试,并在 WM_CAPTURECHANGED 上设置了断点?那将是一个问题,因为调试器会强制中断您的捕获,导致您需要重新捕获等等。 - undefined
在这一点上,我认为你的代码“应该”原则上可以工作,因此我会接受它作为答案,因为它比使用计时器更优雅。 然而,我仍然无法让它工作,因为似乎当我以某种方式重新发布WM_ACTIVATE消息时(与计时器相同的问题),会出现一些问题,而不是立即处理它(我打开一个模态对话框询问用户是否要重新加载,但在那时鼠标光标开始闪烁...)。 - undefined
这是一个打字错误,还是你重新发布了WM_ACTIVATE消息? - undefined
我觉得我们在激活方面做得过头了:当你关闭那个模态对话框时,会收到WM_ACTIVATE消息,导致进入一个循环。如果使用WM_ACTIVATEAPP而不是WM_ACTIVATE,它就能正常工作。 - undefined

0

好问题!

我认为没有办法延迟处理这些消息,它们在逻辑上以正确的顺序出现,但正如你的问题所述,你无法预测未来。

然而,你可以改变处理这些消息的方式,也许可以使用定时器。你需要想象一系列可能发生的事件。

一种方法是创建一个类来处理这个问题。它处理WM_ACTIVATE / WM_ACTIVATEAPP。一旦收到此消息,启动一个定时器。如果收到WM_MOVE,处理它并重新设置定时器。

如果收到WM_CLOSE,运行激活代码,然后调用默认窗口过程或显式调用DestroyWindow

如果定时器到期(半秒钟?你可能需要调试确切的阈值),那么你可以执行激活代码。

这就是我脑海中的大意,如果我没有解释得很清楚,对不起。


一个计时器是最好的解决方案,但也是非常笨拙的。也许,或许,对于消息使用来说,这是唯一的解决方案。在这种情况下,我强烈推荐使用非模态对话框的解决方案。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接