QDIALOG window level modal

The content of this article mainly comes from New Ways of Using QDialog in QtQuarterly30, which introduces the use of QDialog::open() (this function was introduced in Qt 4.5) instead of the traditional exec() to implement a window-level modal dialog box. The so-called modal dialog box means that the dialog box blocks the user's interaction with the parent window until the dialog box is closed. In Mac OS X, it is called Sheet. There are many details involved here, which deserve our attention.

Dialogs and modals

There is a description in the Qt documentation: "A dialog box is a top-level window used for short-term tasks and simple interactions. QDialog can be modal or non-modal."

For modal dialog boxes, code like the following is traditionally used:

MyQDialogSubclass dialog;
// 初始化操作
if (dialog.exec() == QDialog::Accept) {
    // 获取返回值等
}

This code first creates an object of the QDialog subclass and then calls the exec() function. The exec() function will prevent the code from continuing to run until it returns, and finally determines what operations to perform based on the dialog box return value. This modality is called "application-level modality". At this level of modality, user input (mouse and keyboard) can only be dispatched to the modal dialog box and cannot be received by other windows.

The other is a non-modal dialog box. We use the show() function to open a non-modal dialog box. Note that modeless dialog boxes do not block the user from interacting with other windows in the program. Of course, if we want to perform some operations based on its return value, we also have another method.

There is also a type of interaction called "window-level modality" or "document-level modality". This modal only blocks interaction with its parent window, not with other windows in the application. For example, if each document in a program is opened in an independent window, if an application-level modal dialog box is used to save, all document windows will be blocked; if a window-level modal dialog box is used, only the window that opens it will be blocked. That window (that is, its parent window) is blocked. This situation shows that sometimes window-level modality is more convenient than blocking the entire program at once. Of course, using this modality requires that the dialog box must have a parent window.

Starting from Qt 4.1, QWidget introduces a new property windowModality, which is used to set the type of modality of the window. When a window is created, windowModality is set to Qt::NonModal, so it is non-modal by default. After calling QDialog::exec(), windowModality will be set to Qt::ApplicationModal, and when exec() returns, it will be set back to Qt::NonModal. If we do not modify the value of windowModality ourselves, we can simply think that its value is set by show() and exec():

  • QDialog::show()=>Qt::NonModal
  • QDialog::exec()=>Qt::ApplicationModal

Note that the above mapping relationship does not include the value Qt::WindowModal. In other words, if we want to set the window-level modality, we must manually set the windowModality and then call show() or exec(). This is certainly possible, but it's not the same as calling a simple function.

Sheet in Mac OS X

Let's start with the Max OS X Sheet. Above is an example of a Sheet. In Apple's Human Interface Guidelines, Sheet is described as follows: "Sheet is a modal dialog box associated with a specific document or window, ensuring that the user does not lose information about the window to which the sheet is associated. Sheet It can also be used to allow users to complete some small tasks before the sheet disappears, without the feeling that the system has been 'hijacked' by the application." If we study this description carefully, we will find that the sheet is actually a special The window-level modal dialog box is special because the sheet allows the user to clearly see which window it is blocking.

In the Qt 4.0 version, window-level modal dialog boxes are also supported, but they require developers to do more operations:

void MainWindow::maybeSave()
{
    if (!messageBox) {
        messageBox = new QMessageBox(tr("SDI"),
            tr("The document has been modified.\n"
                "Do you want to save your changes?"),
            QMessageBox::Warning,
            QMessageBox::Yes | QMessageBox::Default,
            QMessageBox::No,
            QMessageBox::Cancel | QMessageBox::Escape,
            this, Qt::Sheet);
        messageBox->setButtonText(QMessageBox::Yes,
            isUntitled ? tr("Save...") : tr("Save"));
        messageBox->setButtonText(QMessageBox::No,
            tr("Don’t Save"));
        connect(messageBox, SIGNAL(finished(int)),
                this, SLOT(finishClose(int)));
    }
    messageBox->show();
}

Here, we create a Qt::Sheet type dialog box and connect its closing signal to a slot in the program for post-closure processing. For developers on Mac OS However, this code still correctly creates a window-level modal dialog box: after the dialog box is displayed, the function needs to return immediately and cannot block. The processing of the dialog box return value is completed in the slot.

Next, let's explain why this code needs to return immediately after the sheet is displayed and cannot block. The typical way to block a function and continue to dispatch events is to create a local QEventLoop object and then exit the event loop when the window is closed. In comparison, a bad implementation would be:

// 不要这么实现 Sheet!
void MainWindow::maybeSave()
{
    if (!messageBox) {
        messageBox = new QMessageBox(tr("SDI"),
            tr("The document has been modified.\n"
                "Do you want to save your changes?"),
            QMessageBox::Warning,
            QMessageBox::Yes | QMessageBox::Default,
            QMessageBox::No,
            QMessageBox::Cancel | QMessageBox::Escape,
            this, Qt::Sheet);
        messageBox->setButtonText(QMessageBox::Yes,
            isUntitled ? tr("Save...") : tr("Save"));
        messageBox->setButtonText(QMessageBox::No,
            tr("Don’t Save"));
    }
    QEventLoop eventLoop;
    connect(messageBox, SIGNAL(closed()),
            &eventLoop, SLOT(quit()));
    messageBox->show();
    eventLoop.exec();
    finishClose(messageBox->result());
}

This code will only work in some cases, not all. Consider below that the user has two unsaved documents. If you use the above code, if the user clicks the close button, a sheet will pop up; when the user clicks the close button of another document, the window of this document will also pop up a sheet. The user then returns to the first window and clicks the "Don't Save" button of the first sheet. The sheet disappears, but the window is not closed because it is blocked there by the second sheet's event loop. Obviously, this is not what we expected.

Another possible misuse of sheets is to treat them as application-level sheets. Early versions of Qt did allow this (in fact, Qt's static functions do this), but this does not comply with Apple's human-computer interaction specifications. This also creates an area of ​​confusion because other applications don't do this. In other words, the static functions of Qt dialog boxes should not be used as sheet implementations.

QDialog::open()

Taking into account the issues mentioned above, let's compare the implementation of application-level modalities. When windowModality is Qt::NonModal, it can be easily implemented by just calling QDialog::exec(). So, why not provide a similar function that can implement a window-level modal dialog box with just a simple call? The answer is QDialog::open(). QDialog::open() can open a window-level modal dialog box. On Mac OS X it is a sheet. In order to implement the event loop correctly, the function returns immediately after being called. This means that you must use the signal and slot mechanism to handle the returned results. Fortunately, QDialog provides the finished() signal, which will correctly set the return value of the dialog box, similar to accept() and reject(). Of course, you can also directly inherit QDialog and use your own signal. So, for the QMessageBox problem mentioned earlier, we only need to use QMessageBox::open() instead of show() to simply implement a window-level modal dialog box. Even on the Mac OS X platform, we do not need to specify the Qt::Sheet parameter. The open() function is cross-platform, which means it works the way we expect on all platforms. Now, we have a more complete mapping relationship:

  • QDialog::show()=>Qt::NonModal
  • QDialog::exec()=>Qt::ApplicationModal
  • QDialog::open()=>Qt::WindowModal

As a result, choosing a modal type is much simpler than before. Below we will explain another confusing issue.

Static function of subclass

Another purpose of providing the new open() function is to avoid misuse of sheet. This means you don't need to set Qt::Sheet parameters manually. At the same time, if you need to open an application-level modal sheet, you will get an ordinary application-level modal dialog box; this means that if you open a window-level modal dialog box, the system will open it for you. a sheet. This is because in Cocoa, except for sheet, there is no way to open a normal dialog box with window level modality.

The fact that all application-level modal dialog boxes cannot use sheets affects the static functions of all QDialog subclasses, such as QColorDialog, QFontDialog, QFileDialog, QInputDialog, and QMessageBox. Let's consider one of these functions, such as QColorDialog::getColor(). This function will create a QColorDialog dialog box and then display it modally. The return value of each function is the color selected by the user from the dialog box; if the user does not choose, an illegal color value will be returned. The problem with these functions is that they provide application-level modalities, so sheets cannot be used. This may confuse some users of QFileDialog, because under Mac OS X, Qt's QFileDialog is indeed a sheet. This is because under Mac OS X, applications can use QFileDialog as a sheet.

QDialog::open() allows us to easily use three modal types. So, let's see if there are any other functions. Indeed, we can see many overloaded versions of open(), used to implement some functions that required a lot of code to complete in the past. Many subclasses allow us to easily add slots for handling return values ​​to the open() function. These dialog boxes allow for appropriate connections to be made without any processing on our part. Here is a list of these overloaded versions:

  • QFileDialog::open(QObject *receiver, const char *slot);
  • QColorDialog::open(QObject *receiver, const char *slot);
  • QFontDialog::open(QObject *receiver, const char *slot);
  • QPrintDialog::open(QObject *receiver, const char *slot);
  • QPageSetupDialog::open(QObject *rec, const char *slot);
  • QInputDialog::open(QObject *receiver, const char *slot);
  • QProgressDialog::open(QObject *receiver, const char *slot);
  • QPrintPreviewDialog::open(QObject *rec, const char *slot);

These functions are provided so that the most common processing functions can be easily connected. Below we'll look at how these connections are connected:

  • QColorDialog connects the passed slot to the colorSelected(QColor) signal;
  • QFontDialog is connected to the fontSelected(QFont) signal;
  • QFileDialog connects to the filesSelected(QString) or filesSelected(QStringList) signal, depending on its mode
  • QProgressDialog is connected to the canceled() signal.

You can get more detailed information from the Qt documentation. Specifying the response slot in open() can greatly simplify the code. Using this method, you can directly open a native dialog box. The classes that currently provide this function are QFileDialog, QColorDialog, QFontDialog and QPrintDialog.

new forms of interaction

Since we can use open() to open a native dialog box, we should be able to similarly use show() to open a native non-modal dialog box. At first glance, this doesn't do much, but it opens a standard "live feedback" dialog box under Mac OS X. This is fairly simple in Qt.

The static functions provided by QDialog subclasses encourage developers to use a method that stops the user from continuing to work and asks a question (for example, "Which font do you want to use?"). However, to a certain extent, this will affect the user's work or even annoy the user. Imagine that the user wants to select a color using QColorDialog. He needs to open the dialog box, click to select a color, close the dialog box, and then see the effect. If they weren't happy with the color they chose, they would have to redo the job. Obviously, this kind of complicated work is usually annoying. Why not design the dialog box to be displayed there all the time, so that the user can see the effect immediately after selecting the color? One solution is to create a modeless dialog box yourself to achieve the work mentioned above. For example, when it comes to font selection, Qt has a QFontComboBox class that allows font selection in a non-modal way. But it's impossible to achieve everything QFontDialog can do. Using a dialog box is more intuitive. To achieve this purpose, the QFontDialog class provides the QFontDialog::currentFontChanged() signal. We can connect to this signal and then use show() to provide a modeless dialog box. In this way, we have a dialog box that does not disturb the user's operation and can immediately return the response to the window. QColorDialog also has similar methods. This implementation can make the program feel more user-friendly. Let’s take color selection as an example to see how this can be achieved.

class MainWindow
{
    Q_OBJECT
    //...
private:
    // ...
    QColorDialog *globalColorDialog;
    // ...
};
class PaintArea
{
    Q_OBJECT
    //...
public slots:
    void setBrushColor(const QColor &color);
    // ...
};

We cannot use the static function provided by QColorDialog, but maintain a QColorDialog pointer. To do this, we add a pointer to the main window and add the slot function to the component (here it is QPaintAreas::setBrushColor()).

void MainWindow::brushColor()
{
  if (!globalColorDialog) {
    globalColorDialog = new QColorDialog(this);
    globalColorDialog->setCurrentColor(paintArea->brushColor());
    globalColorDialog->setOption(
        QColorDialog::NoButtons, true);
    connect(globalColorDialog,
            SIGNAL(currentColorChanged(QColor)),
            paintArea,
            SLOT(setBrushColor(QColor)));
  }
  globalColorDialog->show();
}

We need to set the selected color to the current brush. Use QColorDialog::NoButtons to avoid OK and Cancel buttons. This is mainly because in this implementation, they don't make much sense, since we want to return the selected color to the brush immediately (the cancel button does not undo the color selection). However, it is worth noting that on some window managers in X11, windows without a close button will behave strangely. Finally, we create signal and slot connections for currentColorChanged() and setBrushColor(). Then call the show() function to display the dialog box. If the dialog is already displayed, simply place it at the top of the window.

The same is true for QFontDialog, there is no difference. We create a font dialog and make signal and slot connections by keeping its pointer.

in conclusion

This article explains some new ways to use QDialog. We focus on the implementation of various modalities, as well as some useful techniques. Some of the functions mentioned here are newly added after Qt 4.5, such as QDialog::open(). These functions are very useful, so we should use them more in our own programs instead of sticking to the old interface. After all, our programs also need to keep pace with the times~

As a benefit for this article, you can receive a Qt development learning package and technical videos for free, including (C++ language basics, introduction to Qt programming, QT signal and slot mechanism, QT interface development-image drawing, QT network, QT database programming, QT project practice , QT embedded development, Quick module, etc.) ↓↓↓↓↓↓See below↓↓Click at the bottom of the article to receive the fee↓↓

Guess you like

Origin blog.csdn.net/hw5230/article/details/132701143