Qt QInputDialog等自带弹窗样式修改

在使用 QT 开发客户端的过程中,QT 自带的弹窗类实现的功能很简洁,例如 QMessageBox 作为提醒报错等功能弹窗, QInputDialog 作为 int, string 等需要用户定义值的获取,使用起来很方便,不用自己重新造轮子,但是在实际开发客户端的过程中,为了保证软件整体样式风格一致,需要对这些弹窗的样式也进行统一,这篇文章简单介绍一下这些类修改样式的一些方法。

这篇文章主要以 QInputDialog 为例,其他弹窗都可以使用类似的方法设置样式

非静态函数调用时的样式设置方法

最基本方式

在使用 QInputDialog 获取一个字符串的时候,需要对界面的样式进行调整,可以使用 setStyleSheet() 来设置,简单的代码如下(这里选择了一个简单的 TextInput 类型的 QInputDialog ):

QInputDialog dialog{this, Qt::WindowCloseButtonHint};
dialog.setWindowTitle(tr("创建文件夹"));
dialog.setInputMode(QInputDialog::InputMode::TextInput);
dialog.setTextEchoMode(QLineEdit::Normal);
dialog.setLabelText(tr("请输入文件夹名称:"));
dialog.setOkButtonText(QObject::tr("确定"));
dialog.setCancelButtonText(QObject::tr("取消"));
dialog.setFixedSize(350,250);
dialog.setTextValue("");
// 设置了 QPushButtuon 的三态, QLineEdit 的样式, QLable 和 整体的背景色
QString style = "QPushButton{background:rgb(26,179,148); color:rgb(255,255,255); border-radius:3px; min-height:30px; min-width:60px; font:13px \"Microsoft YaHei\";}"
                "QPushButton:hover{background:rgb(24,166,137);}"
                "QPushButton:pressed{background:rgb(32,75,148);}"
                "QLineEdit{border:2px solid rgb(229,230,231);padding:4px;padding-left:10px;border-radius:3px;color:rgb(105,105,105);font:13px \"Microsoft YaHei\";}"
                "QLineEdit:focus{border:2px solid rgb(26,179,148);}"
                "QLineEdit:disabled{background-color:rgb(238,238,238);}"
                "QLabel{color:rgb(85,85,85); font:12px \"Microsoft YaHei\"; font-weight:bold;}"
                "QInputDialog{background-color:rgb(255,255,255); }";
dialog.setStyleSheet(style);

最后得到的样式图大概如下:

old

现在我们有2个需求:

  • 确定取消 这2个按钮设计成不同的样式
  • QLable, QLineEidt 以及 QPushButtion 之间布局需要调整,比如间距,占比等

关于第一个需求,我们知道在使用样式表的时候,设置指定对象的样式是可以通过类名加对象名来区分
例如现在有2个 QPushButton , 一个叫 ptn_ok, 另一个是 ptn_cancel, 我们可以通过 QPushButton#ptn_ok 指定其样式
但是现在的问题,QInputDialog 并没有提供接口直接获取确定取消按键

第二个需求中,同样也不能通过 QInputDialog 提供的接口直接拿到 QVBoxLayout 对象,来调整布局

通过 findChild() 方法设置

接下来先简单说一下解决思路

对于 QInputDialog 对象 dialog 使用 findChild() 函数获取指定对象,然后对得到的对象单独设置样式

  • QInputDialog 中的按钮是在 QDialogButtonBox 类中, 之后会有一篇关于 QInputDialog 源码分析,主要学习一下这个类的实现逻辑
  • QInputDialog 中的布局是在 QVBoxLayout 类中

代码如下:

QInputDialog dialog{this, Qt::WindowCloseButtonHint};
dialog.setWindowTitle(tr("创建文件夹"));
dialog.setInputMode(QInputDialog::InputMode::TextInput);
dialog.setTextEchoMode(QLineEdit::Normal);
dialog.setLabelText(tr("请输入文件夹名称:"));
dialog.setOkButtonText(QObject::tr("确定"));
dialog.setCancelButtonText(QObject::tr("取消"));
dialog.setFixedSize(350,250);
dialog.setTextValue("");

if (QDialogButtonBox *btn_box =  dialog.findChild<QDialogButtonBox*>() ){
    QString ok_style = "QPushButton{background:rgb(26,179,148); color:rgb(255,255,255); border-radius:3px; min-height:30px; min-width:60px; font:13px \"Microsoft YaHei\";}"
                       "QPushButton:hover{background:rgb(24,166,137);}"
                       "QPushButton:pressed{background:rgb(32,75,148);}";
    btn_box->button(QDialogButtonBox::Ok)->setStyleSheet(ok_style);

    QString cancel_style = "QPushButton{background:rgb(237,85,101); color:rgb(255,255,255); border-radius:3px; min-height:30px; min-width:60px; font:13px \"Microsoft YaHei\";}"
                           "QPushButton:hover{background:rgb(236,71,88);}"
                           "QPushButton:pressed{background:rgb(171,71,37);}";
    btn_box->button(QDialogButtonBox::Cancel)->setStyleSheet(cancel_style);
}

if (QVBoxLayout * layout = dialog.findChild<QVBoxLayout *>() ) {
    layout->setSpacing(10);
}

QString style = "QLineEdit{border:2px solid rgb(229,230,231);padding:4px;padding-left:10px;border-radius:3px;color:rgb(105,105,105);font:13px \"Microsoft YaHei\";}"
                "QLineEdit:focus{border:2px solid rgb(26,179,148);}"
                "QLineEdit:disabled{background-color:rgb(238,238,238);}"
                "QLabel{color:rgb(85,85,85); font:12px \"Microsoft YaHei\"; font-weight:bold;}"
                "QInputDialog{background-color:rgb(255,255,255); }";
dialog.setStyleSheet(style);

先展示一下最后的结果

new

其实2个样式整体区别并不大,这篇文章主要是想说明一下 findChild() 函数,虽然 QInputDialog 并没有提供 QVBoxLayout确定取消 按钮对象,但是可以通过此函数来查找,关于 findChild() 函数具体用法和细节可以参考以下这篇博客

https://blog.csdn.net/liang19890820/article/details/52118210

静态函数调用时的样式设置方法

以 getText() 函数为例,简单看一下静态函数调用的方法

static QString getText(QWidget *parent, const QString &title, const QString &label,
                       QLineEdit::EchoMode echo = QLineEdit::Normal,
                       const QString &text = QString(), bool *ok = Q_NULLPTR,
                       Qt::WindowFlags flags = Qt::WindowFlags(),
                       Qt::InputMethodHints inputMethodHints = Qt::ImhNone);
参数 描述
QWidget *parent 弹窗的父对象,后面会根据是否设置了父对象分为2中情况说明样式设置的方法
const QString &title 标题栏信息
const QString &label QLabel 上显示的提示信息
QLineEdit::EchoMode echo EchoMode { Normal, NoEcho, Password, PasswordEchoOnEdit } QLineEdit 展示内容的方式
const QString &text QLineEdit 预展示的内容
bool *ok 返回用户操作QInputDialog的结果,确定为true, 取消或关闭为false
Qt::WindowFlags flags 作为 QWidget 的子类,可以设置窗口的一些格式
Qt::InputMethodHints inputMethodHints 输入类控件属性,输入法使用它来检索有关输入法应如何操作的提示,具体用户可以参考官方文档

针对于静态函数,重点不是如何设置样式,而是如何获取 QInputDialog 的对象,设置样式可以使用上面提到的2种方法,下面按照是否设置父对象来分情况说明

设置了父对象

bool ok = false;
// 这里捕获对象是 getText() 函数设置的父对象,我这里的父对象是this,所以捕获了this
QTimer::singleShot(0, [this](){
    QInputDialog *dialog = this->findChild<QInputDialog *>();
    // 我把样式放在了界面的 te_default 控件中,便于调试
    QString style = ui->te_default->toPlainText();
    dialog->setStyleSheet(style);
});
QString value = QInputDialog::getText(this, tr("创建文件夹"), tr("请输入文件夹名称:"),QLineEdit::Normal, "", &ok, Qt::WindowCloseButtonHint );

未设置父对象

bool ok = false;
// 这里可以不捕获this, 但是因为我使用this指针中的ui部分信息,所以需要捕获 this
QTimer::singleShot(0, [this](){
    for(QWidget *widget: QApplication::topLevelWidgets()){
        if(QInputDialog *dialog = qobject_cast<QInputDialog*>(widget)){
            QString style = ui->te_default->toPlainText();
            dialog->setStyleSheet(style);
        }
    }
});
QString value = QInputDialog::getText(nullptr, tr("创建文件夹"), tr("请输入文件夹名称:"),QLineEdit::Normal, "", &ok, Qt::WindowCloseButtonHint );

总结

  • 其实这篇博客真正的收获是对于Qt很多封装好的简易类时,提供的接口是很有限的, 学会使用 findChild() 找到自己要针对操作的对象, 所以除了 QInputDialog 类,其他类也可以使用同样的方法
  • 这个类在真正使用的时候,推荐自己在重新封装,将样式和一些确定不会改变的图标之类的封装好,便于更简单的调用
  • 这个类本身继承自 QDialog,整体的布局,控件都很简单,可以根据自己实际的业务需求重新实现一下,这样灵活度更高
  • 之后会有一篇关于 QInputDialog 的源码分析,太复杂啃起来太累,自己先看看这些简单类的实现逻辑
  • 测试的工程地址: https://github.com/catcheroftime/QInputDialogCss