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

个人博客地址

|----------------------------------------|
| 个人博客: 花开富贵 |

文章目录

  • 个人博客地址
    • [1 对话框基本概念](#1 对话框基本概念)
    • [2 创建简单的对话框](#2 创建简单的对话框)
    • [3 对话框的内存管理问题](#3 对话框的内存管理问题)
    • [4 自定义对话框](#4 自定义对话框)
      • [4.1 纯代码自定义对话框](#4.1 纯代码自定义对话框)
      • [4.2 UI与代码结合自定义对话框](#4.2 UI与代码结合自定义对话框)
    • [5 对话框的模态与非模态](#5 对话框的模态与非模态)
    • [6 内置对话框](#6 内置对话框)
      • [6.1 QMessageBox 消息对话框](#6.1 QMessageBox 消息对话框)
        • [6.1.1 通过按钮判断执行逻辑](#6.1.1 通过按钮判断执行逻辑)
      • [6.2 QColorDialog 颜色对话框](#6.2 QColorDialog 颜色对话框)
        • [6.2.1 QColorDialog 返回值](#6.2.1 QColorDialog 返回值)
      • [6.3 QFileDialog 文件对话框](#6.3 QFileDialog 文件对话框)
      • [6.5 QFontDialog 字体对话框](#6.5 QFontDialog 字体对话框)
      • [6.6 QInputDialog 输入对话框](#6.6 QInputDialog 输入对话框)

1 对话框基本概念

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

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

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

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

常见的内置对话框有:

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

2 创建简单的对话框

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

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

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

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

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


3 对话框的内存管理问题

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

cpp 复制代码
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;

cpp 复制代码
/*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作为参数, 表示设置当关闭右上角关闭按键时, 彻底关闭这个窗口并释放内存;

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->setAttribute(Qt::WA_DeleteOnClose);
    dialog->show();
}

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

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

cpp 复制代码
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中包含一个按钮, 通过点击按钮来弹出一个对话框;

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->setAttribute(Qt::WA_DeleteOnClose);
    dialog->show();
}

运行结果为:

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

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

cpp 复制代码
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();

cpp 复制代码
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所提供的用于展示不同级别的消息内容的对话框;

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

cpp 复制代码
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内置了一些图标以供用户快速使用;

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

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

  • 设置按钮

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

    cpp 复制代码
    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

    用于报告严重错误;

示例:

cpp 复制代码
void MainWindow::on_pushButton_clicked()
{
    QMessageBox::critical(this, "CriticalTitle", "Critical", QMessageBox::Ok|QMessageBox::Cancel);
}

运行结果为:


6.1.1 通过按钮判断执行逻辑

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

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

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

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

cpp 复制代码
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()来打开颜色对话框;

cpp 复制代码
void MainWindow::on_pushButton_clicked()
{
    QColorDialog::getColor();
}

运行结果为:


6.2.1 QColorDialog 返回值

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

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

cpp 复制代码
void MainWindow::on_pushButton_clicked()
{
    QColor res = QColorDialog::getColor();
    qDebug()<<res;
}

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

bash 复制代码
QColor(ARGB 1, 0.254902, 0.329412, 1)

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

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

cpp 复制代码
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的派生类, 其能够打开文件对话框, 并且支持打开文件与保存文件的对话框;

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

代码为:

cpp 复制代码
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;
}

运行结果为:

(图片过大, CSDN无法显示, 建议移步Here!!!)

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


6.5 QFontDialog 字体对话框

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

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

cpp 复制代码
    static QFont getFont(bool *ok, QWidget *parent = nullptr);

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

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

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

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

运行结果为:

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

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

运行结果为:

(图片过大, CSDN无法显示, 建议移步Here!!!)


6.6 QInputDialog 输入对话框

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

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

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

代码为:

cpp 复制代码
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;
}

运行结果为:

(图片过大, CSDN无法显示, 建议移步Here!!!)

相关推荐
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner1 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz6 天前
QML Hello World 入门示例
qt
xcyxiner9 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner10 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner10 天前
DicomViewer (添加模型类)3
qt
xcyxiner11 天前
DicomViewer (目录调整) 2
qt
xcyxiner11 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00613 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术13 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript