1. 程式人生 > >Qt 之 模態、非模態、半模態視窗的介紹及 實現QDialog的exec()方法

Qt 之 模態、非模態、半模態視窗的介紹及 實現QDialog的exec()方法

版權宣告:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結和本宣告。
本文連結:https://blog.csdn.net/GoForwardToStep/article/details/53667566
一、簡述
先簡單介紹一下模態與非模態對話方塊。
模態對話方塊
簡單一點講就是在彈出模態對話方塊時,除了該對話方塊整個應用程式視窗都無法接受使用者響應,處於等待狀態,直到模態對話方塊被關閉。這時一般需要點選對話方塊中的確定或者取消等按鈕關閉該對話方塊,程式得到對話方塊的返回值(即點選了確定還是取消),並根據返回值進行相應的操作,之後將操作權返回給使用者。這個時候使用者可以點選或者拖動程式其他視窗。

說白了就相當於阻塞同一應用程式中其它可視視窗的輸入的對話方塊,使用者必須完成這個對話方塊中的互動操作並且關閉了它之後才能訪問應用程式中的其它視窗。

其實模態對話方塊的作用就是得到使用者選擇的結果,根據結果來進行下面的操作。

非模態對話方塊
又叫做無模式對話方塊,即彈出非模態對話方塊時,使用者仍然可以對其他視窗進行操作,不會因為這個對話方塊未關閉就不能操作其他視窗。

半模態對話方塊
半模態對話方塊區別於模態與非模態對話方塊,或者說是介於兩者之間,也就是說半模態對話方塊會阻塞視窗的響應,但是不會影響後續程式碼的執行。

Qt中的模態&非模態&半模態
QWidget
QWidget提供了setWindowModality()方法設定視窗半模態or非模態;

Qt::NonModal The window is not modal and does not block input to other windows.
非模態對話方塊

Qt::WindowModal The window is modal to a single window hierarchy and blocks input to its parent window, all grandparent windows, and all siblings of its parent and grandparent windows.
視窗級模態對話方塊,即只會阻塞父視窗、父視窗的父視窗及兄弟視窗。(半模態對話方塊)

Qt::ApplicationModal The window is modal to the application and blocks input to all windows.
應用程式級模態對話方塊,即會阻塞整個應用程式的所有視窗。(半模態對話方塊)

Qt助手中的show()方法——非模態對話方塊


Qt助手中的介紹很簡單,就是顯示視窗以及他的子視窗。

Qt助手中的setWindowModality()方法


setWindowModality()方法可以設定視窗是否是模態視窗,從上圖中我們可以看到Qt::WindowModality的預設值為Qt::NonModal,也就是非模態視窗。

所以,如果沒有設定Qt::WindowModality屬性值,我們每次用show()方法顯示出的視窗都是非模態視窗。

QDialog
我們知道QWidget是大部分 控制元件的父類,也就是說QWidget是控制元件的始祖類,處於最上層,而QDialog也繼承自QWidget。

在Qt助手中我們發現在QDialog除了繼承QWidget的show()方法外,多了兩個方法用來顯示視窗,分別是open() 和 exec()方法。

Qt助手中的open()方法——半模態對話方塊


可以看到使用open()方法顯示出的對話方塊為視窗級模態對話方塊,並且立即返回,這樣open()方法後的程式碼將會繼續執行。open()方法就相當於如下程式碼。

void showWindow()
{
    QWidget* pWindow = new QWidget();

    QWidget* childWindow = new QWidget(pWindow);
    childWindow->setWindowModality(Qt::WindowModal);
    childWindow->show();

    // 上面三行程式碼相當於下面兩行程式碼; 
    //QDialog* childDialog = new QDialog(pWindow);
    //childDialog->open();

    // 下面的程式碼可以執行;
    qDebug() << "這是一個半模態視窗";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Qt助手中的exec()方法——模態對話方塊


可以看到使用exec()方法顯示出的對話方塊為模態對話方塊,同時會阻塞之前視窗的響應直到使用者關閉這個對話方塊,並且返回DialogCode(包括Accepted和Rejected兩個值)結果。

看紅色劃線部分,如果沒有設定Qt::WindowModality屬性值,使用exec()方法顯示出的對話方塊預設為應用程式級模態對話方塊。所有使用exec()方法顯示對話方塊在視窗關閉前會阻塞整個程式所有視窗的響應。同時呼叫exec()方法後的程式碼也不會執行直到對話方塊關閉才會繼續執行。在關閉對話方塊後exec()方法會返回Accepted或者Rejected,一般程式根據返回不同的結果進行相應的操作。

那我們是否可以用以下程式碼來代替QDialog中的exec()方法呢?
void showModalWindow()
{
    QWidget* pWindow = new QWidget();

    QWidget* childWindow = new QWidget(pWindow);
    childWindow->setWindowModality(Qt::ApplicationModal);
    childWindow->show();

    // 下面的程式碼可以執行;
    qDebug() << "這是一個模態視窗嗎?";
}
1
2
3
4
5
6
7
8
9
10
11
顯然是不可以的,這裡呼叫完show()方法後立即返回了,並不知道使用者選擇了Accepted還是Rejected。而exec()會阻塞後面程式碼的執行,直到對話方塊關閉,返回結果。

下面用QDialog的exec()方法來顯示一個模態對話方塊。
void showModalWindow()
{
    QWidget* pWindow = new QWidget();

    QDialog* childDialog = new QDialog(pWindow);
    int resutl = childDialog ->exec();
    if (resutl == QDialog::Accepted)
    {
        qDebug() << "You Choose Ok";
    }
    else
    {
        qDebug() << "You Choose Cancel";
    }


    // 在關閉對話方塊之後,下面的程式碼才可以執行;
    qDebug() << "這是一個模態視窗";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
模式對話方塊有自己的事件迴圈。按照我的理解,實際上 exec() 方法是先設定modal屬性為Qt::ApplicationModal,然後呼叫 show() 顯示對話方塊,最後啟用事件迴圈來阻止exec() 方法的結束。直到視窗關閉,得到返回結果(DialogCode),退出事件迴圈,最後exec()方法呼叫結束,exec()方法後的程式碼將繼續執行。

QDialog的exec() 方法的實現 整體上就是按照上方所講的思路進行實現的。關於exec() 方法返回的結果可以通過對介面上的按鈕繫結相應的槽,比如確定按鈕繫結accept()槽,取消按鈕繫結reject()槽,這樣在點選確定或者取消按鈕時exec()方法就會返回Accepted 或者 Rejected,可以根據返回的值做出相應的操作。

下面就直接上程式碼實現exec()方法。

二、程式碼之路
實現QDialog的exec()方法
void MyDialog::init()
{
    connect(ui.pButtonOk, SIGNAL(clicked()), this, SLOT(onOkClicked()));
    connect(ui.pButtonCancel, SIGNAL(clicked()), this, SLOT(onCancelClicked()));
}

int MyDialog::exec()
{
    // 設定為模態;
    this->setWindowModality(Qt::ApplicationModal);
    show();
    // 使用事件迴圈QEventLoop ,不讓exec()方法結束,在使用者選擇確定或者取消後,關閉視窗結束事件迴圈,並返回最後使用者選擇的結果;
    // 根據返回結果得到使用者按下了確定還是取消,採取相應的操作。從而模擬出QDialog類的exec()方法;
    m_eventLoop = new QEventLoop(this);
    m_eventLoop->exec();

    return m_chooseResult;
}

void MyDialog::onOkClicked()
{
    m_chooseResult = Accepted;
    close();
}

void MyDialog::onCancelClicked()
{
    m_chooseResult = Rejected;
    close();
}

void MyDialog::closeEvent(QCloseEvent *event)
{
    // 關閉視窗時結束事件迴圈,在exec()方法中返回選擇結果;
    if (m_eventLoop != NULL)
    {
        m_eventLoop->exit();
    }
    event->accept();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
三、 Qt事件迴圈的一些理解(exec、eventloop)
1、事件迴圈一般用exec()函式開啟。QApplicaion::exec()、QMessageBox::exec()都是事件迴圈。其中前者又被稱為主事件迴圈。

事件迴圈首先是一個無限“迴圈”,程式在exec()裡面無限迴圈,能讓跟在exec()後面的程式碼得不到執行機會,直至程式從exec()跳出。從exec()跳出時,事件迴圈即被終止。QEventLoop::quit()能夠終止事件迴圈。

其次,之所以被稱為“事件”迴圈,是因為它能接收事件,並處理之。當事件太多而不能馬上處理完的時候,待處理事件被放在一個“佇列”裡,稱為“事件迴圈佇列”。當事件迴圈處理完一個事件後,就從“事件迴圈佇列”中取出下一個事件處理之。當事件迴圈佇列為空的時候,它和一個啥事也不做的永真迴圈有點類似,但是和永真迴圈不同的是,事件迴圈不會大量佔用CPU資源。

事件迴圈的本質就是以佇列的方式再次分配執行緒時間片。

2、事件迴圈是可以巢狀的,一層套一層,子層的事件迴圈執行exec()的時候,父層事件迴圈就處於中斷狀態;當子層事件迴圈跳出exec()後,父層事件迴圈才能繼續迴圈下去。
另外,子層事件迴圈具有父層事件迴圈的幾乎所有功能。Qt會把事件送到當前生效的那個事件迴圈佇列中去,其中包括Gui的各種事件。所以使用者在主執行緒中執行各種exec()(如QMessageBox::exec(),QEventLoop::exec())的時候,雖然這些exec()打斷了main()中的QApplication::exec(),但是Gui介面仍然能夠正常響應。

3、如果某個子事件迴圈仍然有效,但其父迴圈被強制跳出,此時父迴圈不會立即執行跳出,而是等待子事件迴圈跳出後,父迴圈才會跳出。

摘自 http://blog.chinaunix.net/uid-27685749-id-3847998.html。


關於模態、非模態、半模態視窗的定義也很好理解,其實也就是跟使用者操作過程中進行互動的問題。

同時我們也通過簡單的程式碼來模擬出了QDialog的exec()方法。有問題直接找Qt助手,在這裡基本上便能找到我們需要的答案。所以說遇到一些問題不一定非要立馬到網上找各種資料或者到學習群中詢問問題的解決辦法,多看看幫助問題還是很有好處的。

http://www.kuqin.com/qtdocument/classes.html , 這個網址裡提供了Qt文件的中文翻譯 ,有需要的小夥伴可以看看。
————————————————
版權宣告:本文為CSDN博主「前行中的小豬」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/GoForwardToStep/art