我在夏理当农码 - CSDN: dio夹心小面包

『QT』窗口 - 深入剖析 QDialog 对话框机制与内存管理

  |   0 评论   |   6 浏览

1 对话框基本概念

对话框是一个弹窗, 通常用于进行一些与与用户间的=="短平快"==操作;

典型的为是当对一个已编辑文件进行退出操作时, 将会弹出弹窗询问是否进行保存;

Qt中, 通常表示QDialog来表示一个对话框;

针对已有的项目, 也可以创建一些类, 继承自对应的QDialog以完成自定义的对话框, 同时QT也提供了一些内置的对话框, 以静态函数的形式, 方便快速使用对话框以避免自定义对话框;

常见的内置对话框有:

  • QFiledialog (文件对话框)
  • QColorDialog (颜色对话框)
  • QFontDialog (字体对话框)
  • QInputDialog (输入对话框)
  • QMessageBox (消息框)

2 创建简单的对话框

可以在QtCreator中创建一个简单的对话框;

从运行结果来看, 实际上对话框与QWidget类似;

本质上是因为, QDialog对象继承自QWidget类;

QWidget的基础上添加了两个属性, 分别为sizeGripEnablemodal;

其中sizeGripEnable表示右下角的可拖动放大缩小, modal属性则是为是否为非模态(模态/非模态将在下文进行介绍)对话框;


3 对话框的内存管理问题

该话题开始之前, 我们需要设计一个小程序, 即在QMainWindow中存在一个PushButton, 当按下Button后, 将会弹出一个弹窗;

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

void MainWindow::on_pushButton_clicked()
{
    QDialog* dialog = new QDialog(this);
    dialog->setWindowTitle("弹窗标题");
    dialog->resize(200, 100);
    dialog->show();
}

运行结果为:

那么这里将存在一个问题, 在上面的代码中, 我们把这个Dialog的父对象设置为QMainWindow, 但是在关闭过程中, 内存是否会进行释放? 即真正意义上的关闭;

我们需要了解一下, 当一个窗口的内存被释放(析构)后, 称这个窗口被关闭;

而实际在Qt中, 若是这个窗口是一个子窗口而不是主窗口, 当按下关闭键后并不会释放内存, 这意味着并不会真正的释放它的内存, 而是对其进行隐藏;

为了验证这一点, 我们构建一个新的Dialog, 继承自QDialog类, 来实现一个自己的Dialog, 判断其是否会调用析构函数, 完成内存释放;

通过新建class继承QDialog类设计一个myDialog;

/*mydialog.h*/
class myDialog : public QDialog
{
    Q_OBJECT
public:
    myDialog(QWidget *parent = nullptr);
    ~myDialog();
};
/*mydialog.cpp*/
myDialog::myDialog(QWidget *parent)
    : QDialog(parent)
{
}
myDialog::~myDialog()
{
    qDebug()<<"~myDialog()";
}
/*mainwindow.cpp*/
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

void MainWindow::on_pushButton_clicked()
{
    myDialog* dialog = new myDialog(this);
    dialog->setWindowTitle("弹窗标题");
    dialog->resize(200, 100);
    dialog->show();
}

从结果看到, 当点击右上角关闭按钮时, 析构函数并没有被调用, 因为其实际上是被隐藏而不是被真正意义上的关闭, 因此最后将QMainWindow关闭后, 由于所创建的myDialog对象在其对象树上, QMainWindow被关闭时将会逐个释放对象树中子树的内存, 导致所创建的三个myDialog在同一时间被释放;

因此需要控制其内存释放( 在拥有对象树属性的Qt中通常不允许在析构中通过delete this来释放内存以避免对象树在析构中产生差错);

通常可以通过设置 setAttribute()并传入 Qt::WA_DeleteOnClose作为参数, 表示设置当关闭右上角关闭按键时, 彻底关闭这个窗口并释放内存;

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

void MainWindow::on_pushButton_clicked()
{
    myDialog* dialog = new myDialog(this);
    dialog->setWindowTitle("弹窗标题");
    dialog->resize(200, 100);
    dialog->setAttribute(Qt::WA_DeleteOnClose);
    dialog->show();
}

从结果来看, 析构函数被调用, 说明当窗口被关闭时, 将释放内存;

除此之外, 也可以通过在Dialog中设置一个按钮, 通过信号槽的形式来关闭/释放对应的Dialog(这里同样要设置setAttribute来设置WA_DeleteOnClose);

myDialog::myDialog(QWidget *parent)
    : QDialog(parent)
{
    QPushButton* button = new QPushButton("Close Dialog");
    QVBoxLayout* layout = new QVBoxLayout(this);
    this->setLayout(layout);
    layout->addWidget(button);
    connect(button, &QPushButton::clicked, this, &myDialog::closeDialog);
}

myDialog::~myDialog()
{
    qDebug()<<"~myDialog()";
}

void myDialog::closeDialog()
{
    // delete this; // 不推荐(可能破坏对象树)
    this->close(); // 推荐(但需要设置 setAttribute(WA_DeleteOnClose))
}

运行结果为:


4 自定义对话框

自定义对话框有两种方式, 一种为UI配合代码的方式, 另一种为纯代码的方式;


4.1 纯代码自定义对话框

通过纯代码的自定义对话框的方式在QtCreator中的步骤为如下:

  1. 文件->新建文件

  2. 选择C++ Class

  3. 命名新类名/继承自哪个类以及勾选所需选项

完成后将会自动生成对应的头文件以及源文件;

当文件创建完毕后, 则可以对对应的对话框内设计内容, 类似的设计方式已在 =="3 对话框的内存管理问题"== 中有示例, 在此不进行赘述;


4.2 UI与代码结合自定义对话框

相同的步骤, 但所选项不同, 以下图为例:

  1. 选择Qt -> QtWidget Designer Form Class

  2. 选择所需界面(此处为QDialog的派生类, 因此此处选择Dialog Without Buttons)

  3. 设置类名

  4. 最终结果

    最终可以看到, 选择结束后自动生成了.ui, .h, .cpp文件;

具体的使用与正常的Widget无异, 此处不赘述;

同时所有需要自定义的控件都可以使用这种方式设置;


5 对话框的模态与非模态

对话框也分为模态与非模态;

  • 模态

    弹出对话框时, 父窗口无法操作, 必须等待对话框结束;

  • 非模态

    弹出对话框时, 不影响父窗口操作;

通常情况下, 模态对话框通常需要用户作出重大决策, 如当某个文件编辑后未保存, 在关闭时或许会弹出模块对话框提示"是否保存所编辑内容";

Qt中, 我们可以通过QDialog::setModal(bool)来设置对应的模态状态, 当以show进行窗口显示时, 默认为非模态;

创建一个QMainWindow, 该QMainWindow中包含一个按钮, 通过点击按钮来弹出一个对话框;

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

void MainWindow::on_pushButton_clicked()
{
    myDialog* dialog = new myDialog(this);
    dialog->setAttribute(Qt::WA_DeleteOnClose);
    dialog->show();
}

运行结果为:

可以看到, 在非模态的状态下, 当出现弹窗时, 父窗口仍进行点击;

此处调用setModal(true)再次运行程序;

void MainWindow::on_pushButton_clicked()
{
    myDialog* dialog = new myDialog(this);
    dialog->setModal(true);
    dialog->setAttribute(Qt::WA_DeleteOnClose);
    dialog->show();
}

运行结果为:

setModal设置为true时, 为模态弹窗, 此时用户必须使弹窗结束, 否则父窗口将阻塞;

在此前, 我们提到, 当使用show()展示窗口时将会出现这样的情况, 是因为本质上使用show()展示窗口时, 默认采用非模态的形式来展示, 若是需要模态窗口展示, 可以将show()替换为exec();

void MainWindow::on_pushButton_clicked()
{
    myDialog* dialog = new myDialog(this);
    dialog->setAttribute(Qt::WA_DeleteOnClose);
    dialog->exec();
}

运行结果与上图一致;


6 内置对话框

除了用户可以使用QDialog或者自定义对话框以外, Qt也提供了一系列的内置对话框以提供更便捷的开发;

常用的内置对话框有如下:

  • QMessageBox - 消息对话框
  • QFileDialog - 文件对话框
  • QInputDialog - 输入对话框
  • QFontDialog - 字体对话框
  • QPrintDialog - 打印对话框
  • QProgressDialog - 进度条对话框

6.1 QMessageBox 消息对话框

QMessageBox消息对话框是Qt所提供的用于展示不同级别的消息内容的对话框;

通常情况下, 消息对话框是模态对话框, 当对话框弹出时, 用户必须在对话框内进行决策, 否则无法进行下一步操作;

void MainWindow::on_pushButton_clicked()
{
    QMessageBox* msgbox = new QMessageBox(this);
    msgbox->setWindowTitle("Title for QMessageBox"); // 设置标题
    msgbox->setText("Text for QMessageBox"); // 设置文本
    msgbox->setIcon(QMessageBox::Information); // 设置图标
    msgbox->setStandardButtons(QMessageBox::Ok|QMessageBox::Save); // 设置按钮
    msgbox->exec(); // 模态窗口展示
}
  • 设置标题

    调用setWindowTitle(QString)来设置对应的弹窗标题;

  • 设置文本内容

    调用setText(QString)来设置文本内容;

  • 设置Icon图标

    QMessageBox支持设置图标, 可以设置自定义的图标;

    除此之外, QMessageBox内置了一些图标以供用户快速使用;

    enum Icon {
            // keep this in sync with QMessageDialogOptions::StandardIcon
            NoIcon = 0, // 没有图标
            Information = 1, // 常规
            Warning = 2, // 警告
            Critical = 3, // 严重
            Question = 4 // 问题
        };
        Q_ENUM(Icon)
    

    内置的图标通常来为消息进行级别划分, 可以直接传入枚举值来设置对应的图标;

  • 设置按钮

    QMessageBox中, 可通过setStandardButtons()来添加按钮;

    enum StandardButton {
            // keep this in sync with QDialogButtonBox::StandardButton and QPlatformDialogHelper::StandardButton
            NoButton           = 0x00000000,
            Ok                 = 0x00000400,
            Save               = 0x00000800,
            SaveAll            = 0x00001000,
            Open               = 0x00002000,
            Yes                = 0x00004000,
            YesToAll           = 0x00008000,
            No                 = 0x00010000,
            NoToAll            = 0x00020000,
            Abort              = 0x00040000,
            Retry              = 0x00080000,
            Ignore             = 0x00100000,
            Close              = 0x00200000,
            Cancel             = 0x00400000,
            Discard            = 0x00800000,
            Help               = 0x01000000,
            Apply              = 0x02000000,
            Reset              = 0x04000000,
            RestoreDefaults    = 0x08000000,
    
            FirstButton        = Ok,                // internal
            LastButton         = RestoreDefaults,   // internal
    
            YesAll             = YesToAll,          // obsolete
            NoAll              = NoToAll,           // obsolete
    
            Default            = 0x00000100,        // obsolete
            Escape             = 0x00000200,        // obsolete
            FlagMask           = 0x00000300,        // obsolete
            ButtonMask         = ~FlagMask          // obsolete
        };
        Q_ENUM(StandardButton)
    

    该函数的参数只有一个, 但是可以传入多个按钮, 本质是通过位运算的方式进行添加;

  • 上述代码的运行结果为

除此之外, QMessageBox还派生了一系列的子类, 并设计了对应的静态成员函数, 可直接通过静态成员函数来设置需要的QMessageBox, 常见的有:

  • QMessageBox::Information

    用于报告正常运行信息;

  • QMessageBox::Question

    用于正常操作过程中的提问;

  • QMessageBox::Warning

    用于报告非关键错误;

  • QMessageBox::Critical

    用于报告严重错误;

示例:

void MainWindow::on_pushButton_clicked()
{
    QMessageBox::critical(this, "CriticalTitle", "Critical", QMessageBox::Ok|QMessageBox::Cancel);
}

运行结果为:


6.1.1 通过按钮判断执行逻辑

可见, 由于并不能制定槽函数, 因此无法以以往的方式对按钮进行区分操作, 即判断哪个按钮被按下;

而在exec()函数中, 将会返回一个返回值, 这个返回值通常为按钮按下的值;

可通过该值来判断所按下的按钮;

除此之外, 静态成员函数QMessageBox::xxx也将返回一个返回值, 同样可以用来判断所按下的值;

void MainWindow::on_pushButton_clicked()
{
    int res = QMessageBox::critical(this, "CriticalTitle", "Critical", QMessageBox::Ok|QMessageBox::Cancel);
    switch (res) {
    case QMessageBox::Ok:
        qDebug()<<"QMessageBox::Ok";
        break;
    case QMessageBox::Cancel:
        qDebug()<<"QMessageBox::Cancel";
        break;
    default:
        qDebug()<<"No Button Clicked!";
    }
}

运行结果为:


6.2 QColorDialog 颜色对话框

颜色对话框允许用户选择颜色, 同样继承于QDialog;

同样的, QColorDialog为了方便使用, 生成了对应的静态函数, 可直接通过QColorDialog::getColor()来打开颜色对话框;

void MainWindow::on_pushButton_clicked()
{
    QColorDialog::getColor();
}

运行结果为:


6.2.1 QColorDialog 返回值

同样的, 这个对话框能返回所选择的颜色属性;

通常返回的是一个QColor类型的对象;

void MainWindow::on_pushButton_clicked()
{
    QColor res = QColorDialog::getColor();
    qDebug()<<res;
}

忽略运行图例, 运行(运行并获取颜色)结果为:

QColor(ARGB 1, 0.254902, 0.329412, 1)

可以看到, 这里的QColor所返回的并不是一个RGB格式的颜色, 而是ARGB格式, 其中A表示alpha不透明度, 1表示完全不透明, 0表示完全透明, 其他的内容即为默认的RGB;

可以通过拼接字符串的形式, 设置对应的StyleSheet:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->setObjectName("QMainWindow_Test"); // 设置对象名
}

void MainWindow::on_pushButton_clicked()
{
    QColor res = QColorDialog::getColor();
    
    /*对应的StyleSheet只作用于QMainWindow_Test中*/
    QString style = QString("#QMainWindow_Test { background-color: rgb(%1, %2, %3); }")
                        .arg(res.red())
                        .arg(res.green())
                        .arg(res.blue());
    this->setStyleSheet(style);
    qDebug()<<res;
}

运行结果为:


6.3 QFileDialog 文件对话框

同样是QDialog的派生类, 其能够打开文件对话框, 并且支持打开文件与保存文件的对话框;

这里以打开文件, 打开多个文件, 保存文件为例;

代码为:

void MainWindow::on_pushButton_clicked()
{
    QString fileName = QFileDialog::getOpenFileName();
    qDebug()<<fileName;
}

void MainWindow::on_pushButton_2_clicked()
{
    QStringList fileNames = QFileDialog::getOpenFileNames();
    qDebug()<<fileNames;
}

void MainWindow::on_pushButton_3_clicked()
{
    QString fileName = QFileDialog::getSaveFileName();
    qDebug()<<fileName;
}

运行结果为:

这个对话框也是后期针对QT的文件操作的一部分;


6.5 QFontDialog 字体对话框

字体对话框能够打开对话框并选择对应的字体;

可直接调用静态成员函数来打开QFOntDialog::getFont(&ok);

static QFont getFont(bool *ok, QWidget *parent = nullptr);

可以看到, 该函数必须的参数有一个bool类型的指针;

该指针可以用于判断该字体对话框最终的按钮点击为OK还是Cancel;

同时该函数将会返回一个QFont类型对象, 而该类型通常用来描述一个具体的字体信息, 可以其进行打印;

void MainWindow::on_pushButton_clicked()
{
    bool ok;
    QFont font = QFontDialog::getFont(&ok, this);
    if(ok) qDebug()<<font;
    else qDebug()<<"Cancel";
}

运行结果为:

在有些控件中, 可以通过setFont来设置对应的字体, 可以配合该对话框使用:

void MainWindow::on_pushButton_clicked()
{
    bool ok;
    QFont font = QFontDialog::getFont(&ok, this);
    if(ok) {
        ui->pushButton->setFont(font);
        qDebug()<<font;
    }
    else qDebug()<<"Cancel";
}

运行结果为:


6.6 QInputDialog 输入对话框

弹出一个对话框, 该对话框将会让用户输入对应的内容;

所输入的内容可以是整数, 浮点数, 或是字符串;

通常直接调用静态成员函数直接使用;

代码为:

void MainWindow::on_pushButton_int_clicked()
{
    int res = QInputDialog::getInt(this, "Get INT", "INT");
    qDebug()<<res;
}

void MainWindow::on_pushButton_float_clicked()
{
    double res = QInputDialog::getDouble(this, "Get DOUBLE", "DOUBLE");
    qDebug()<<res;
}

void MainWindow::on_pushButton_str_clicked()
{
    QString res = QInputDialog::getText(this, "Get STRING", "STRING");
    qDebug()<<res;
}

void MainWindow::on_pushButton_item_clicked()
{
    QString res = QInputDialog::getItem(this, "Get ITEM", "ITEM",{"Hello", "World", "Qt", "C++"});
    qDebug()<<res;
}

运行结果为:


标题:『QT』窗口 - 深入剖析 QDialog 对话框机制与内存管理
作者:orion
地址:http://orionpeng.top/articles/2025/12/01/1764590086884.html

评论

发表评论


取消