Specialized QValidator and QML UI changes

后端 未结 1 2048
鱼传尺愫
鱼传尺愫 2020-12-16 08:19

I\'m learning Qt 5.5 and QML.

The framework is powerful, and there are sometimes many ways to do one thing. I think that some are probably more efficient than others

相关标签:
1条回答
  • 2020-12-16 08:46

    I don't think a single answer can address all your questions but I still think that some guidelines about application structure can help getting you going.

    AFAIK there's no central place that discuss application structure. Actually, there's also no recommendation about UI structure in QML (see this discussion for instance). That said, we can identify some common patterns and choices in a QML application which we are going to discuss further below.

    Before getting there I would like to stress an important aspect. QML is not so distant from C++. QML is basically an object tree of QObject-derived objects whose lifetime is controlled by a QMLEngine instance. In this sense a piece of code like

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

    it's not that different from a QLineEdit with a Validator written in plain imperative C++ syntax. Apart from the lifetime, as said. Given that, implementing your validator in plain C++ is wrong: the validator is part of the TextField and should have a lifetime consistent with it. In this specific case registering a new type is the best way to go. The resulting code is easier to read and easier to maintain.

    Now, this case is particular. The validator property accepts objects that derives from Validator (see declaration here and some usages here, here and here). Hence, instead of simply define a Object-derived type, we can define a QValidator-derived type and use it in place of IntValidator (or the other QML validation types).

    Our DirectoryValidator header file looks like this:

    #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
    

    The implementation file is like this:

    #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;
    }
    

    Now you can register the new type in your main like this:

    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();
    }
    

    and your QML code could be rewritten like this:

    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 {             // external declaration
            id: dirVal
        }
    
        Column {
            anchors.fill: parent
            
            TextField {
                id: first
                validator: dirVal
                textColor: acceptableInput ? "black" : "red"
            }
    
            TextField {
                validator: DirValidator { }      // declaration inline
                textColor: acceptableInput ? "black" : "red"
            }
    
            TextField {
                validator: DirValidator { }      // declaration inline
                textColor: acceptableInput ? "black" : "red"
            }
        }
    }
    

    As you can see the usage become more straightforward. The C++ code is cleaner but also the QML code is cleaner. You don't need to pass data around yourself. Here we use the very same acceptableInput of TextField since it is set by the Validator associated with it.

    The same effect could have been obtained by registering another type which is not derived from Validator - losing the association with acceptableInput. Look at the following code:

    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"
        }
    }
    

    Here ValidationType could be defined with two Q_PROPERTY elements:

    • a QString exposed to QML as textToCheck
    • a bool property exposed as valid to QML

    When bound to first.text the property is set and reset when the TextField text changes. On change you can check the text, for instance with your very same code, and update valid property. See this answer or the registration link above for details about Q_PROPERTY updates. I leave the implementation of this approach to you, as exercise.

    Finally, when it comes to services-like/global objects/types, using non instanciable / singleton types could be the right approach. I would let the documentation talk for me in this case:

    A QObject singleton type can be interacted with in a manner similar to any other QObject or instantiated type, except that only one (engine constructed and owned) instance will exist, and it must be referenced by type name rather than id. Q_PROPERTYs of QObject singleton types may be bound to, and Q_INVOKABLE functions of QObject module APIs may be used in signal handler expressions. This makes singleton types an ideal way to implement styling or theming, and they can also be used instead of ".pragma library" script imports to store global state or to provide global functionality.

    qmlRegisterSingletonType is the function to prefer. It's the approach used also in the "Quick Forecast" app i.e. the Digia showcase app. See the main and the related ApplicationInfo type.

    Also context properties are particularly useful. Since they are added to the root context (see the link) they are available in all the QML files and can be used also as global objects. Classes to access DBs, classes to access web services or similar are eligible to be added as context properties. Another useful case is related to models: a C++ model, like an AbstractListModel can be registered as a context property and used as the model of a view, e.g. a ListView. See the example available here.

    The Connections type can be used to connect signals emitted by both context properties and register types (obviously also the singleton one). Whereas signals, Q_INVOKABLE functions and SLOTs can be directly called from QML to trigger other C++ slots like partially discussed here.

    Summing up, using objectName and accessing QML from C++ is possible and feasible but is usually discouraged (see the warning here). Also, when necessary and possible, QML/C++ integration is favoured via dedicated properties, see for instance the mediaObject property of QML Camera type. Using (singleton) registered types, context properties and connecting them to QML via the Connections type, Q_INVOKABLE, SLOTs and SIGNALs should enable the most of the use cases.

    0 讨论(0)
提交回复
热议问题