首页 文章

专门的QValidator和QML UI更改

提问于
浏览
1

我正在学习Qt 5.5和QML .

框架很强大,有时候有很多方法可以做一件事 . 我认为有些可能比其他人更有效率,我想知道何时以及为何使用一个而不是另一个 .
我'd like an answer that can explain the choices made. As I'm使用新代码,如果在C端有用,可以使用C 11和C 14语法 .

要解决的问题是:
我有一个 TextField 链接到一个可以弹出 FileDialog 的按钮 . 我希望 TextField 中的文本在无效时为 red ,否则保持不变(我将其设置为 green ,因为我不知道如何获得"default"颜色) . TextField 的值将在C端使用,并在应用程序退出时保持不变 .

我使用自定义 QValidator 编写了一个版本,QML方面的一些属性,使用 onTextChanged:onValidatorChanged: 来修改文本的颜色 . 根据从C侧(在验证器中)设置的QML中的属性( valid )设置文本颜色 . 要设置属性,C必须按名称查找调用者( TextField ,名为 directoryToSave ),因为我还没有找到将对象本身作为参数传递的方法 .

以下是 MainForm.ui.qml 中包含的QML代码:

TextField {
        property bool valid: false

        id: directoryToSave
        objectName: 'directoryToSave'
        Layout.fillWidth:true
        placeholderText: qsTr("Enter a directory path to save to the peer")
        validator: directoryToSaveValidator
        onTextChanged: if (valid) textColor = 'green'; else textColor = 'red';
        onValidatorChanged:
        {
            directoryToSave.validator.attachedObject = directoryToSave.objectName;
            // forces validation
            var oldText = text;
            text = text+' ';
            text = oldText;
        }
    }

自定义验证码:

class QDirectoryValidator : public QValidator
{
    Q_OBJECT
    Q_PROPERTY(QVariant attachedObject READ attachedObject WRITE setAttachedObject NOTIFY attachedObjectChanged)

private:
    QVariant m_attachedObject;

public:
    explicit QDirectoryValidator(QObject* parent = 0);
    virtual State validate(QString& input, int& pos) const;

    QVariant attachedObject() const;
    void setAttachedObject(const QVariant &attachedObject);

signals:
    void attachedObjectChanged();
};

与这些定义相关:

QVariant QDirectoryValidator::attachedObject() const
{
    return m_attachedObject;
}

void QDirectoryValidator::setAttachedObject(const QVariant &attachedObject)
{
    if (attachedObject != m_attachedObject)
    {
        m_attachedObject = attachedObject;
        emit attachedObjectChanged();
    }
}

QValidator::State QDirectoryValidator::validate(QString& input, int& pos) const
{
    QString attachedObjectName = m_attachedObject.toString();
    QObject *rootObject = ((LAACApplication *) qApp)->engine().rootObjects().first();
    QObject *qmlObject = rootObject ? rootObject->findChild<QObject*>(attachedObjectName) : 0;

    // Either the directory exists, then it is _valid_
    // or the directory does not exist (maybe the string is an invalid directory name, or whatever), and then it is _invalid_

    QDir dir(input);
    bool isAcceptable = (dir.exists());

    if (qmlObject) qmlObject->setProperty("valid", isAcceptable);

    return isAcceptable ? Acceptable : Intermediate;
}

m_attachedObjectQVariant 因为我希望最初引用QML实例而不是它的名称 .

由于验证器仅关注验证,因此它不包含有关其验证的数据的任何状态 .
因为我必须得到 TextField 的值才能在应用程序中执行某些操作,所以我已经构建了另一个类以在更改时保存该值: MyClass . 我认为它是我的控制器 . 目前,我将数据直接存储在应用程序对象中,可以看作是我的模型 . 这将在未来发生变化 .

class MyClass : public QObject
{
    Q_OBJECT
public:
    MyClass() {}

public slots:
    void cppSlot(const QString &string) {
       ((LAACApplication *) qApp)->setLocalDataDirectory(string);
    }
};

控制器 MyClass 和验证器 QDirectoryValidator 的实例是在应用程序初始化期间使用以下代码创建的:

MyClass * myClass = new MyClass;
QObject::connect(rootObject, SIGNAL(signalDirectoryChanged(QString)),
              myClass, SLOT(cppSlot(QString)));
//delete myClass;


QValidator* validator = new QDirectoryValidator();
QVariant variant;
variant.setValue(validator);
rootObject->setProperty("directoryToSaveValidator", variant);

//delete 仅用于发现删除实例时发生的情况 .

main.qml 将事物联系在一起:

ApplicationWindow {
    id: thisIsTheMainWindow
    objectName: "thisIsTheMainWindow"

    // ...
    property alias directoryToSaveText: mainForm.directoryToSaveText
    property var directoryToSaveValidator: null

    signal signalDirectoryChanged(string msg)

    // ...

    FileDialog {
        id: fileDialog
        title: "Please choose a directory"
        folder: shortcuts.home
        selectFolder: true

        onAccepted: {
            var url = fileDialog.fileUrls[0]
            mainForm.directoryToSaveText = url.slice(8)
        }
        onRejected: {
            //console.log("Canceled")
        }
        Component.onCompleted: visible = false
    }
    onDirectoryToSaveTextChanged: thisIsTheMainWindow.signalDirectoryChanged(directoryToSaveText)

    }

最后,MainForm.ui.qml胶水:

Item {

    // ...
    property alias directoryToSavePlaceholderText: directoryToSave.placeholderText
    property alias directoryToSaveText: directoryToSave.text

    // ...
}

我不满意:

onValidatorChanged: 中的

  • 污垢确保使用正确的颜色初始化UI

  • byname树搜索找到调用者(看起来效率低下;可能不是)
    在C的几个实例和QML的部分实例中,

  • 类似意大利面条的编码

我可以想到其他5种解决方案:

  • 摆脱自定义验证器,只保留 onTextChanged: ,因为我们无法摆脱QML方面的信令 . 大多数事情都在 MyClass 完成

  • 修补Qt,以便为 Behavior 以外的其他东西实现属性值写入拦截器(参见here

  • 注册要附加到QML对象的C类型 . (见here

  • 注册一个类型并将其用作控制器和数据结构(类似bean),然后传递给模型(参见here

  • 手动使用信号,因为我已经使用 signalDirectoryChanged

那么,正如你所看到的那样,做事的多种方式令人困惑,所以很感谢senpai的建议 .

完整源代码here .

1 回答

  • 5

    我不认为一个答案可以解决您的所有问题,但我仍然认为有关应用程序结构的一些指导可以帮助您前进 .

    AFAIK那里's no central place that discuss application structure. Actually, there'也没有关于QML中UI结构的建议(例如参见this讨论) . 也就是说,我们可以在QML应用程序中找出一些常见的模式和选择,我们将在下面进一步讨论 .

    在到达那里之前,我想强调一个重要方面 . QML离C不远 . QML基本上是 QObject -派生对象的对象树,其生命周期由 QMLEngine 实例控制 . 在这个意义上,一段代码就像

    TextField {
        id: directoryToSave
        placeholderText: qsTr("placeHolder")
        validator: IntValidator { }
    }
    

    它与使用简单命令式C语法编写的 ValidatorQLineEdit 没有什么不同 . 正如说的那样,除了一生 . 鉴于此,在普通C中实现验证器是错误的:验证器是 TextField 的一部分,并且应该具有与之一致的生命周期 . 在这个具体案例中,registering a new type是最好的方式 . 生成的代码更易于阅读,更易于维护 .

    现在,这个案子很特别 . validator 属性接受派生自 Validator 的对象(参见声明here和一些用法hereherehere) . 因此,我们可以定义一个 QValidator -derived类型,而不是简单地定义 QValidator -derived类型,而是使用它代替 IntValidator (或其他QML验证类型) .

    我们的 DirectoryValidator 头文件如下所示:

    #ifndef DIRECTORYVALIDATOR_H
    #define DIRECTORYVALIDATOR_H
    #include <QValidator>
    #include <QDir>
    
    class DirectoryValidator : public QValidator
    {
        Q_OBJECT
    
    public:
        DirectoryValidator(QObject * parent = 0);
        void fixup(QString & input) const override;
        QLocale locale() const;
        void setLocale(const QLocale & locale);
        State   validate(QString & input, int & pos) const override;
    };    
    #endif
    

    实现文件是这样的:

    #include "directoryvalidator.h"
    
    DirectoryValidator::DirectoryValidator(QObject *parent): QValidator(parent)
    {
        // NOTHING
    }
    
    void DirectoryValidator::fixup(QString & input) const
    {
        // try to fix the string??
        QValidator::fixup(input);
    }
    
    QLocale DirectoryValidator::locale() const
    {
        return QValidator::locale();
    }
    
    void DirectoryValidator::setLocale(const QLocale & locale)
    {
        QValidator::setLocale(locale);
    }
    
    QValidator::State DirectoryValidator::validate(QString & input, int & pos) const
    {
        Q_UNUSED(pos)                   // change cursor position if you like...
        if(QDir(input).exists())
            return Acceptable;
        return Intermediate;
    }
    

    现在您可以在 main 中注册新类型,如下所示:

    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);
    
        QQmlApplicationEngine engine;
        qmlRegisterType<DirectoryValidator>("DirValidator", 1, 0, "DirValidator");
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        return app.exec();
    }
    

    你的QML代码可以像这样重写:

    import QtQuick 2.4
    import QtQuick.Window 2.2
    import QtQuick.Controls 1.4
    import QtQuick.Layouts 1.1
    import DirValidator 1.0       // import the new type
    
    Window {
        id: main
        visible: true
        width: 600
        height: 600
    
        DirValidator {             // use it
            id: dirVal
        }
    
        Column {
            anchors.fill: parent
    
            TextField {
                id: first
                validator: dirVal
                textColor: acceptableInput ? "black" : "red"
            }
    
            TextField {
                validator: dirVal
                textColor: acceptableInput ? "black" : "red"
            }
    
            TextField {
                validator: DirValidator { }      // use it
                textColor: acceptableInput ? "black" : "red"
            }
        }
    }
    

    如您所见,使用变得更加直接 . C代码更干净,但QML代码更清晰 . 您不需要自己传递数据 . 这里我们使用的 acceptableInputTextField 完全相同,因为它是由与之关联的 Validator 设置的 .

    通过注册另一种不是从 Validator 派生的类型可以获得相同的效果 - 失去与 acceptableInput 的关联 . 看下面的代码:

    import QtQuick 2.4
    import QtQuick.Window 2.2
    import QtQuick.Controls 1.4
    import ValidationType 1.0
    
    Window {
        id: main
        visible: true
        width: 600
        height: 600
    
        ValidationType {
            id: validator
            textToCheck: first.text
        }
    
        TextField {
            id: first
            validator: dirVal
            textColor: validator.valid ? "black" : "red"  // "valid" used in place of "acceptableInput"
        }
    }
    

    这里 ValidationType 可以用两个 Q_PROPERTY 元素定义:

    • a QString 暴露于QML为 textToCheck

    • bool 属性公开为 valid 到QML

    绑定到 first.text 时,属性设置并在 TextField 文本更改时重置 . 在更改时,您可以检查文本,例如使用相同的代码,并更新 valid 属性 . 有关 Q_PROPERTY 更新的详细信息,请参阅this answer或上面的注册链接 . 作为练习,我将这种方法的实施留给你 .

    最后,当谈到类似服务/全局对象/类型时,使用非instanciable / singleton类型可能是正确的方法 . 在这种情况下,我会让documentation跟我说话:

    QObject单例类型可以以类似于任何其他QObject或实例化类型的方式进行交互,除了只存在一个(引擎构造和拥有的)实例,并且它必须由类型名称而不是id引用 . 可以绑定QObject单例类型的Q_PROPERTY,并且可以在信号处理程序表达式中使用QObject模块API的Q_INVOKABLE函数 . 这使得单例类型成为实现样式或主题的理想方式,也可以使用它们代替“.pragma library”脚本导入来存储全局状态或提供全局功能 .

    qmlRegisterSingletonType是喜欢的功能 . 这也是"Quick Forecast"应用程序中使用的方法,即Digia展示应用程序 . 请参阅main和相关的ApplicationInfo类型 .

    另外context properties特别有用 . 由于它们被添加到根上下文(请参阅链接),因此它们可用于所有QML文件,也可用作全局对象 . 访问DB的类,访问Web服务的类或类似的类都有资格作为上下文属性添加 . 另一个有用的案例与模型有关:C模型,如 AbstractListModel ,可以注册为上下文属性,并用作视图的模型,例如: a ListView . 请参阅可用的示例here .

    Connections类型可用于连接上下文属性和寄存器类型(显然也是单例)发出的信号 . 而信号, Q_INVOKABLE 函数和 SLOT 可以直接从QML调用,以触发其他C槽,如部分讨论here .

    总结一下,使用 objectName 并从C访问QML是可行且可行的,但通常不鼓励(参见警告here) . 此外,在必要和可能的情况下,QML / C集成通过专用属性受到青睐,例如参见QML Camera 类型的mediaObject属性 . 使用(单例)注册类型,上下文属性并通过 Connections 类型将它们连接到QML, Q_INVOKABLESLOTSIGNAL 应该能够启用大多数用例 .

相关问题