一、改造原因
使用
QInputDialog, QFileDialog
时,最常用的就是它们自带的几个static
函数。优点是参数明确,调用方便,但与此同时带来的缺点就是灵活性不足。
情景1
当前页面有定时更新状态的控件,如波形图、
OpenGL
模型等;执行
QFileDialog::getOpenFileName
等static
方法时,被弹出的对话框遮挡的页面中,定时更新状态的控件都被阻塞,对话框弹出期间不再更新。被阻塞的原因是对话框采用
exec
方法执行,阻塞其他界面等待用户输入完成。
情景2
想要对
QInputDialog, QFileDialog
界面进行定制,如在弹出对话框输入框中弹出内置软键盘。一般弹出内置软键盘需要注册控件事件过滤器,而直接执行 static 方法弹出的对话框,都是新
new
出来的,然后就直接exec
运行了,根本没有机会去注册其输入框控件。
情景3
还是界面定制问题,
QFileDialog
有个参数DontUseNativeDialog
默认为false
,该参数控制QFileDialog
界面采用的是系统文件对话框还是Qt
控件自定义的对话框。
在采用系统对话框时,弹出的是操作系统的文件资源管理器,我们无法定制界面的
qss
样式。所以我们要手动将其设为true
。setOption(DontUseNativeDialog, true);
二、改造方法
综上所诉,我们主要有两个需求在调用方便的
static
函数中无法被满足。
- 对话框本身属性需要定制,如不使用操作系统界面,界面加载后对其控件进行设置。
- 对话框运行方式需要更改,不能阻塞其他界面更新。
从需求角度出发,解决方法就是:
提前定义一个成员变量,在构造的时候就进行注册,后续要弹出对话框时使用该成员变量进行操作。但这个方法调用起来很不方便,直接调用
static
方法才一条语句。一般需要加载文件或者获取输入等交互界面很多,如果每一个界面都这么处理,结构和代码量上也不符合我们预期。
三、改造代码
于是,新的问题就是代码上的封装与优化。对于这种问题我们就很熟悉了,直接继承控件重写
static
方法。由于不是虚函数,无法直接重写,个人将新方法函数名做了更改,代码如下:
1. QLsFileDialog
qls_file_dialog.h
#ifndef QLSFILEDIALOG_H
#define QLSFILEDIALOG_H
#include <functional>
#include <QFileDialog>
class QLsFileDialog : public QFileDialog
{
Q_OBJECT
public:
QLsFileDialog(QWidget *parent = nullptr, const QString &caption = QString(),
const QString &dir = QString(),
const QString &filter = QString());
// O: 指 fdlg->open(); 形式异步执行
static QString getOpenFileNameO(std::function<void()> run,
QWidget *parent = nullptr,
const QString &caption = QString(),
const QString &dir = QString(),
const QString &filter = QString(),
QString *selectedFilter = nullptr,
Options options = Options());
static QUrl getOpenFileUrlO(
std::function<void()> run, QWidget *parent = nullptr,
const QString &caption = QString(), const QUrl &dir = QUrl(),
const QString &filter = QString(), QString *selectedFilter = nullptr,
Options options = Options(),
const QStringList &supportedSchemes = QStringList());
static QString getSaveFileNameO(std::function<void()> run,
QWidget *parent = nullptr,
const QString &caption = QString(),
const QString &dir = QString(),
const QString &filter = QString(),
QString *selectedFilter = nullptr,
Options options = Options());
static QUrl getSaveFileUrlO(
std::function<void()> run, QWidget *parent = nullptr,
const QString &caption = QString(), const QUrl &dir = QUrl(),
const QString &filter = QString(), QString *selectedFilter = nullptr,
Options options = Options(),
const QStringList &supportedSchemes = QStringList());
};
#endif // QLSFILEDIALOG_H
qls_file_dialog.cpp
#include "ui_wrapper/qls_file_dialog.h"
#include <QApplication>
#include <QLineEdit>
QLsFileDialog::QLsFileDialog(QWidget *parent, const QString &caption,
const QString &dir, const QString &filter)
: QFileDialog(parent, caption, dir, filter)
{
}
QString QLsFileDialog::getOpenFileNameO(std::function<void()> run,
QWidget *parent, const QString &caption,
const QString &dir,
const QString &filter,
QString *selectedFilter,
Options options)
{
const QStringList schemes = QStringList(QStringLiteral("file"));
const QUrl selectedUrl =
getOpenFileUrlO(run, parent, caption, QUrl::fromLocalFile(dir), filter,
selectedFilter, options, schemes);
return selectedUrl.toLocalFile();
}
QUrl QLsFileDialog::getOpenFileUrlO(std::function<void()> run, QWidget *parent,
const QString &caption, const QUrl &dir,
const QString &filter,
QString *selectedFilter, Options options,
const QStringList &supportedSchemes)
{
QUrl selectedUrl;
QEventLoop loop;
QSharedPointer<QLsFileDialog> fdlg(
new QLsFileDialog(parent, caption, dir.toLocalFile(), filter));
fdlg->setFileMode(ExistingFile);
fdlg->setOptions(options);
fdlg->setSupportedSchemes(supportedSchemes);
fdlg->setOption(DontUseNativeDialog, true); // 不使用本机对话框
fdlg->findChild<QLineEdit *>()->setReadOnly(true); // 输入框只读
if (selectedFilter && !selectedFilter->isEmpty()) {
fdlg->selectNameFilter(*selectedFilter);
}
fdlg->connect(fdlg.get(), &QFileDialog::accepted, [&] {
if (selectedFilter) {
*selectedFilter = fdlg->selectedNameFilter();
}
selectedUrl = fdlg->selectedUrls().value(0);
});
fdlg->connect(fdlg.get(), &QFileDialog::finished,
[&](int) { loop.exit(); });
// 打开
fdlg->open();
// 异步操作
if (run) {
run();
}
// 非阻塞等待
loop.exec(QEventLoop::DialogExec);
return selectedUrl;
}
QString QLsFileDialog::getSaveFileNameO(std::function<void()> run,
QWidget *parent, const QString &caption,
const QString &dir,
const QString &filter,
QString *selectedFilter,
Options options)
{
const QStringList schemes = QStringList(QStringLiteral("file"));
const QUrl selectedUrl =
getSaveFileUrlO(run, parent, caption, QUrl::fromLocalFile(dir), filter,
selectedFilter, options, schemes);
return selectedUrl.toLocalFile();
}
QUrl QLsFileDialog::getSaveFileUrlO(std::function<void()> run, QWidget *parent,
const QString &caption, const QUrl &dir,
const QString &filter,
QString *selectedFilter, Options options,
const QStringList &supportedSchemes)
{
QUrl selectedUrl;
QEventLoop loop;
QSharedPointer<QLsFileDialog> fdlg(
new QLsFileDialog(parent, caption, dir.toLocalFile(), filter));
fdlg->setFileMode(AnyFile);
fdlg->setOptions(options);
fdlg->setSupportedSchemes(supportedSchemes);
fdlg->setAcceptMode(AcceptSave); // Save
fdlg->setOption(DontUseNativeDialog, true); // 不使用本机对话框
if (selectedFilter && !selectedFilter->isEmpty()) {
fdlg->selectNameFilter(*selectedFilter);
}
fdlg->connect(fdlg.get(), &QFileDialog::accepted, [&] {
if (selectedFilter) {
*selectedFilter = fdlg->selectedNameFilter();
}
selectedUrl = fdlg->selectedUrls().value(0);
});
fdlg->connect(fdlg.get(), &QFileDialog::finished,
[&](int) { loop.exit(); });
// 打开
fdlg->open();
// 异步操作
if (run) {
run();
}
// 非阻塞等待
loop.exec(QEventLoop::DialogExec);
return selectedUrl;
}
调用举例:
...
QString file_name = QLsFileDialog::getOpenFileNameO(
[=] {}, this, tr("choose script(*.lua):"), ".",
tr("script(*.lua);; all(*)"));
...
...
QString file_name = QLsFileDialog::getSaveFileNameO(
[=] {
// 注册键盘
RegisterInputWidget(
findChild<QLsFileDialog *>()->findChild<QLineEdit *>(),
KeyboardType::StandardType);
},
this, tr("choose script(*.lua):"), ".",
tr("script(*.lua);; all(*.*)"));
...
2. QLsInputDialog
qls_input_dialog.h
#ifndef QLSINPUTDIALOG_H
#define QLSINPUTDIALOG_H
#include <QInputDialog>
class QLsInputDialog : public QInputDialog
{
Q_OBJECT
public:
QLsInputDialog(QWidget *parent = nullptr,
Qt::WindowFlags flags = Qt::WindowFlags());
// O: 指 idlg->open(); 形式异步执行
static QString getTextO(
std::function<void()> run, QWidget *parent, const QString &title,
const QString &label, QLineEdit::EchoMode echo = QLineEdit::Normal,
const QString &text = QString(), bool *ok = nullptr,
Qt::WindowFlags flags = Qt::WindowFlags(),
Qt::InputMethodHints inputMethodHints = Qt::ImhNone);
};
#endif // QLSINPUTDIALOG_H
qls_input_dialog.cpp
#include "ui_wrapper/qls_input_dialog.h"
#include <QApplication>
QLsInputDialog::QLsInputDialog(QWidget *parent, Qt::WindowFlags flags)
: QInputDialog(parent, flags)
{
}
QString QLsInputDialog::getTextO(std::function<void()> run, QWidget *parent,
const QString &title, const QString &label,
QLineEdit::EchoMode echo, const QString &text,
bool *ok, Qt::WindowFlags flags,
Qt::InputMethodHints inputMethodHints)
{
QString textValue;
QEventLoop loop;
QSharedPointer<QLsInputDialog> idlg(new QLsInputDialog(parent, flags));
idlg->setWindowTitle(title);
idlg->setLabelText(label);
idlg->setTextEchoMode(echo);
idlg->setTextValue(text);
idlg->setInputMethodHints(inputMethodHints);
idlg->connect(idlg.get(), &QInputDialog::accepted,
[&] { textValue = idlg->textValue(); });
idlg->connect(idlg.get(), &QInputDialog::finished, [&](int result) {
if (ok) {
*ok = (result == QDialog::Accepted);
}
loop.exit();
});
// 打开
idlg->open();
// 异步操作
if (run) {
run();
}
// 非阻塞等待
loop.exec(QEventLoop::DialogExec);
return textValue;
}
调用举例:
...
QString str_op_mode_pwd = QLsInputDialog::getTextO(
[=] {
RegisterInputWidget(
findChild<QInputDialog *>()->findChild<QLineEdit *>(),
KeyboardType::StandardType);
},
this, tr("Operational Mode Password"), tr("Enter Password"),
QLineEdit::Password);
...