专门的QValidator和QML UI更改

SR_*_*SR_ 1 c++ validation qml qt5 qtquick2

我正在学习Qt 5.5和QML.

框架很强大,有时候有很多方法可以做一件事.我认为有些可能比其他人更有效率,我想知道何时以及为何使用一个而不是另一个.
我想要一个可以解释所做出的选择的答案.当我使用新代码时,如果在C++端有用,可以使用C++ 11和C++ 14语法.

要解决的问题是:
我已经TextField链接到一个可以弹出的按钮FileDialog.我想在文本TextFieldred当它是无效的,并保持不变,否则(我把它设置为green,因为我不知道如何让"默认"的颜色).它的值TextField将在C++端使用,并在应用程序退出时保持不变.

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

以下是包含在以下内容中的QML代码MainForm.ui.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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

自定义验证码:

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();
};
Run Code Online (Sandbox Code Playgroud)

与这些定义相关:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

在应用程序初始化期间使用以下代码创建控制器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);
Run Code Online (Sandbox Code Playgroud)

//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)

    }
Run Code Online (Sandbox Code Playgroud)

最后,MainForm.ui.qml胶水:

Item {

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

    // ...
}
Run Code Online (Sandbox Code Playgroud)

我不满意:

  • 在污垢onValidatorChanged:,以确保使用正确的颜色来初始化UI
  • byname树搜索找到调用者(看起来效率低下;可能不是)
  • 在几个C++实例和部分QML中的意大利面条编码

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

  • 摆脱自定义验证器,并保持只是onTextChanged:因为我们无法摆脱QML方面的信号.大多数事情都是完成的MyClass
  • 修补Qt以实现属性值写入拦截器的其他内容Behavior(参见此处)
  • 注册C++类型以附加到QML对象.(见这里)
  • 注册一个类型并将其用作控制器和数据结构(类似bean),然后传递给模型(参见这里)
  • 正如我已经做的那样手动使用信号 signalDirectoryChanged

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

这里提供完整的源代码.

BaC*_*Zzo 5

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

AFAIK没有讨论应用程序结构的中心位置.实际上,在QML中也没有关于UI结构的建议(例如参见讨论).也就是说,我们可以在QML应用程序中找出一些常见的模式和选择,我们将在下面进一步讨论.

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

TextField {
    id: directoryToSave
    placeholderText: qsTr("placeHolder")
    validator: IntValidator { }
}
Run Code Online (Sandbox Code Playgroud)

QLineEdit与使用Validator普通命令式C++语法编写的没有什么不同.正如说的那样,除了一生.鉴于此,在普通C++中实现验证器是错误的:验证器是其中的一部分,TextField并且应该具有与之一致的生命周期.在这种特定情况下,注册新类型是最好的方法.生成的代码更易于阅读,更易于维护.

现在,这个案子很特别.该validator属性接受从派生的对象Validator(见申报这里和一些用法在这里,这里这里).因此,我们不是简单地定义一个Object派生类型,而是定义一个QValidator派生类型并使用它来代替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
Run Code Online (Sandbox Code Playgroud)

实现文件是这样的:

#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;
}
Run Code Online (Sandbox Code Playgroud)

现在您可以在此处注册新类型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();
}
Run Code Online (Sandbox Code Playgroud)

你的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"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,使用变得更加直接.C++代码更清晰,但QML代码更清晰.您不需要自己传递数据.这里我们使用相同的acceptableInput,TextField因为它是由它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"
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 一个QString暴露于QML作为textToCheck
  • 关于QML bool暴露的财产valid

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

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

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

qmlRegisterSingletonType是喜欢的功能.这也是"快速预测"应用程序中使用的方法,即Digia展示应用程序.查看main和相关ApplicationInfo类型.

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

Connections类型可用于连接上下文属性和寄存器类型(显然也是单例)发出的信号.而信号,Q_INVOKABLE功能和SLOTs时,可以直接从QML称为触发其他C++等部分所讨论的时隙这里.

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