2

很长一段时间我一直想知道模态对话框是如何实现的。

让我以Qt为例。(几乎所有的 GUI 工具包都有这种机制)

在主事件循环中,调用了一个插槽,并在此插槽中打开了一个模式对话框。在对话框关闭之前,插槽不会将控制权返回给主事件循环。所以我认为主事件循环被阻塞并变得无响应。显然这不是真的,因为当您打开模式对话框时,后台主窗口仍在工作,例如重新绘制其 UI 或继续显示曲线或某些图形。它只是变成不接受任何用户输入。

我做了一个实验。我没有在插槽中打开模式对话框,而是在那里启动一个新线程,并等待线程在该插槽中完成。这肯定阻塞了主事件循环。

模态对话框到底是如何实现的?它如何保持主事件循环畅通但同时阻塞调用槽?

4

4 回答 4

3

只需要一个事件循环,并且在出现模式对话框时它不会阻塞。虽然,我想,不同的工具包可能会以不同的方式处理这个问题。您需要查阅文档才能确定。然而,从概念上讲,它们都以相同的方式工作。

每个事件都有一个事件发生的来源。当一个模态对话框出现时,事件循环要么忽略要么重定向所有来自对话框之外的事件。它真的没有魔法。一般来说,它就像事件循环代码中的 if 语句,上面写着“if (modal_is_shown() and !event_is_in_modal_window()) {ignore_and_wait_for_next_event()}”。当然,逻辑有点复杂,但这就是它的要点。

于 2013-02-04T13:49:53.630 回答
2

如果您正在寻找示例,这里是另一个示例:

在 Tk 中,只有一个事件循环。模态行为(不必是对话框,也可以是工具提示、文本框等)通过使主窗口忽略鼠标和键盘事件来简单地实现。由于事件循环仍在运行,所有其他事件(如重绘等)仍然可以得到服务。

Tk 通过[grab]函数实现这一点。调用grabUI 对象使其成为唯一能够响应键盘和鼠标事件的对象。基本上阻止了所有其他对象。这不会与事件循环混淆。它只是暂时禁用事件处理程序,直到释放抓取。

应该注意的是,运行 X 的类 Unix 操作系统也grab内置在窗口系统中。因此,它不一定仅由 UI 工具包库实现,有时也是操作系统的内置功能。同样,这是通过简单地阻止/禁用事件而不是实例化单独的事件循环来实现的。我相信这也曾经是 OSX 之前的旧 MacOS 的情况。虽然不确定OSX或Windows。尽管模态通常由操作系统本身实现,但像 Qt 和 Tk 这样的工具包通常会实现自己的机制来标准化不同平台上的行为。

所以结论是,没有必要阻塞主事件循环来实现模态。您只需要阻止事件和/或事件处理程序。

于 2012-11-16T04:22:17.063 回答
1

通常,这种类型的模式对话框是通过运行它自己的消息循环而不是应用程序的消息循环来实现的。即使在模态操作期间,定向到主窗口的消息(例如计时器或绘制消息)仍将被传递。

在某些情况下,您可能必须小心不要递归地重复执行相同的操作。例如,如果您在计时器消息上触发一个模式对话框并结合了一些持久标志,您将需要确保您不会在计时器消息触发时重复显示相同的对话框。

于 2012-11-16T03:59:49.483 回答
0

https://stackoverflow.com/users/893/greg-hewgill的答案是正确的。

不过看了他和https://stackoverflow.com/users/188326/solotim的后续讨论,感觉还是有进一步澄清的空间,用散文和一些伪代码的方式。

我将使用事实清单处理散文部分:

  • 在模态活动完成之前,主消息循环不会运行
  • 但是,在模态活动运行时仍会传递事件
  • 这是因为模态活动中有一个嵌套的事件循环。

到目前为止,我只是重复了格雷格的回答,为了连续性,请多多包涵。下面是我希望提供更多有用信息的地方。

  • 嵌套事件循环是 GUI 工具包的一部分,因此,它知道与存在的每个窗口相关的回调函数
  • 当嵌套事件循环引发事件(例如指向主窗口的重绘事件)时,它会调用与该事件关联的回调函数。请注意,此处的“回调”可能指的是面向对象系统中表示窗口的类的方法。
  • 回调函数执行所需的操作(例如,重新绘制),并立即返回嵌套消息循环(模态活动中的那个)

最后但同样重要的是,这里的伪代码希望能进一步说明,使用虚构的“GuiToolkit”:

void GuiToolkit::RunModal( ModalWindow *m )
{
    // main event loop
    while( !GuiToolkit::IsFinished() && m->IsOpen() )
    {
          GuiToolkit::ProcessEvent(); // this will call
                                      // MainWindow::OnRepaint
                                      // as needed, via the virtual
                                      // method of the base class
                                      // NonModalWindow::OnRepaint
    }
}

class AboutDialog: public ModalWindow
{
}

class MainWindow: public NonModalWindow
{
    virtual void OnRepaint()
    {
        ...
    }
    virtual void OnAboutBox()
    {
         AboutDialog about;
         GuiToolkit::RunModal(&about); // blocks here!!
    }
}

main()
{
    MainWindow window;
    GuiToolkit::Register( &window ) // GuiToolkit knows how to 
                                    // invoke methods of window

    // main event loop
    while( !GuiToolkit::IsFinished() )
    {
          GuiToolkit::ProcessEvent(); // this will call
                                      // MainWindow::OnAboutBox
                                      // at some point
    }
}
于 2017-03-28T08:13:39.927 回答