文件/文本输入框

一、改造原因

使用 QInputDialog, QFileDialog 时,最常用的就是它们自带的几个 static 函数。优点是参数明确,调用方便,但与此同时带来的缺点就是灵活性不足。

情景1

当前页面有定时更新状态的控件,如波形图、OpenGL 模型等;

执行 QFileDialog::getOpenFileNamestatic 方法时,被弹出的对话框遮挡的页面中,定时更新状态的控件都被阻塞,对话框弹出期间不再更新。

被阻塞的原因是对话框采用 exec 方法执行,阻塞其他界面等待用户输入完成。

情景2

想要对 QInputDialog, QFileDialog 界面进行定制,如在弹出对话框输入框中弹出内置软键盘。

一般弹出内置软键盘需要注册控件事件过滤器,而直接执行 static 方法弹出的对话框,都是新 new 出来的,然后就直接 exec 运行了,根本没有机会去注册其输入框控件。

情景3

还是界面定制问题,QFileDialog 有个参数 DontUseNativeDialog 默认为 false,该参数控制 QFileDialog 界面采用的是系统文件对话框还是 Qt 控件自定义的对话框。

在采用系统对话框时,弹出的是操作系统的文件资源管理器,我们无法定制界面的 qss 样式。所以我们要手动将其设为 truesetOption(DontUseNativeDialog, true);

二、改造方法

综上所诉,我们主要有两个需求在调用方便的 static 函数中无法被满足。

  1. 对话框本身属性需要定制,如不使用操作系统界面,界面加载后对其控件进行设置。
  2. 对话框运行方式需要更改,不能阻塞其他界面更新。

从需求角度出发,解决方法就是:

提前定义一个成员变量,在构造的时候就进行注册,后续要弹出对话框时使用该成员变量进行操作。但这个方法调用起来很不方便,直接调用 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);
...

9条评论

  1. I think everything typed was very logical. However, what about
    this? suppose you were to write a awesome post title? I ain’t
    saying your information isn’t solid, however suppose you added
    a title that grabbed people’s attention? I mean 文件/文本输入框 – 龙少的小窝 is kinda plain. You
    could look at Yahoo’s front page and note how they create post titles to grab people interested.
    You might add a related video or a related picture or two
    to grab readers interested about what you’ve got to say.
    Just my opinion, it might make your posts a little livelier.

  2. Hey, I think your site might be having browser compatibility issues. When I look at your blog in Firefox, it looks fine but when opening in Internet Explorer, it has some overlapping. I just wanted to give you a quick heads up! Other then that, excellent blog!

  3. Oh my goodness! Awesome article dude! Thanks, However
    I am having issues with your RSS. I don’t understand the reason why I am unable to join it.
    Is there anyone else having identical RSS problems? Anyone who knows the solution will you kindly respond?
    Thanks!!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注