如何实现模态对话框?

时间:2012-11-16 03:50:34

标签: user-interface

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

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

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

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

毕竟如何实施模态对话框?它如何保持主事件循环解除阻塞,但同时阻止了呼叫槽?

4 个答案:

答案 0 :(得分:3)

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

每个事件都有事件发生的来源。当出现模式对话框时,事件循环会忽略或重定向在对话框外部生成的所有事件。这真的没有魔力。一般来说,它就像事件循环代码中的if语句,表示“if(modal_is_shown()和!event_is_in_modal_window()){ignore_and_wait_for_next_event()}”。当然,逻辑有点复杂,但这就是它的要点。

答案 1 :(得分:2)

如果您正在寻找另一个例子:

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

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

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

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

答案 2 :(得分:1)

通常,通过运行自己的消息循环而不是应用程序的消息循环来实现此类型的模式对话框。即使在模态操作期间,也会传递指向主窗口的消息(例如计时器或绘画消息)。

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

答案 3 :(得分: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
    }
}