C++ 专门的QValidator和QML UI更改

C++ 专门的QValidator和QML UI更改,c++,validation,qml,qt5,qtquick2,C++,Validation,Qml,Qt5,Qtquick2,我正在学习Qt5.5和QML 该框架功能强大,有时可以通过多种方式完成一件事。我认为一些可能比其他更有效,我想了解何时以及为什么使用一个而不是另一个。 我想要一个能解释所做选择的答案。当我使用新代码时,C++ 11和C++ 14语法可以在C++方面使用。 要解决的问题是: 我有一个文本字段链接到一个按钮,可以弹出一个文件对话框。我希望文本字段中的文本在无效时为红色,否则保持不变(我将其设置为绿色,因为我不知道如何获得“默认”颜色)。“代码>文本字段< /代码>的值将在C++侧使用,并且在应用程序

我正在学习Qt5.5和QML

该框架功能强大,有时可以通过多种方式完成一件事。我认为一些可能比其他更有效,我想了解何时以及为什么使用一个而不是另一个。
我想要一个能解释所做选择的答案。当我使用新代码时,C++ 11和C++ 14语法可以在C++方面使用。 要解决的问题是:
我有一个
文本字段
链接到一个按钮,可以弹出一个
文件对话框
。我希望
文本字段中的文本在无效时为
红色
,否则保持不变(我将其设置为
绿色
,因为我不知道如何获得“默认”颜色)。“代码>文本字段< /代码>的值将在C++侧使用,并且在应用程序退出时将继续执行。

我已经使用自定义的

QValidator
编码了一个版本,QML端的一些属性,使用
onTextChanged:
onValidatorChanged:
修改了文本的颜色。根据C++中设置的QML(验证器)中的属性(<代码>有效< /代码>)设置文本颜色。要设置属性,C++必须按名称查找调用方(<代码>文本字段< /COD>命名为代码> DeReTrayToWave/Cuth>),因为我还没有找到一种方法将对象本身作为参数传递。 以下是
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;
}
controller
MyClass
和validator
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

    // ...
}
我不满足于:

  • 已更改
    onvalidator中的污垢:
    以确保使用正确的颜色初始化UI
  • 按名称树搜索以查找调用者(看起来无效;可能无效)
  • C++中几个实例与QML
  • 部分的意大利面编码
我可以想出其他5种解决方案:

  • 摆脱自定义验证器,只保留
    onTextChanged:
    ,因为我们无法摆脱来自QML端的信号。大多数事情都是在
    MyClass
  • 修补Qt以实现除
    行为
    之外的其他内容的属性值写入拦截器(请参阅)
  • 注册一个C++类型以附加到QML对象。(见附件)
  • 注册一个类型并将其用作控制器和数据结构(类似bean),以便随后传递到模型(请参阅)
  • 手动使用信号,就像我在
    signalDirectoryChanged
好吧,正如你所看到的,做事情的方式过于随意,令人困惑,所以森派的建议是值得赞赏的


完整的源代码可用。

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

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

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

TextField {
    id: directoryToSave
    placeholderText: qsTr("placeHolder")
    validator: IntValidator { }
}
<>代码与C++命令语法编写的<代码>验证器< /代码>没有什么不同。如前所述,除了一生。鉴于此,在C++中实现验证器是错误的:验证器是 TeXField的一部分,并且应该有一个与之一致的生存期。在这种特殊情况下,这是最好的方法。生成的代码更易于阅读和维护

现在,这个案子很特别。
validator
属性接受从
validator
派生的对象(请参见声明和一些用法,以及)。因此,我们可以定义
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
#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;
}
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();
}
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"
        }
    }
}
现在,您可以在
main
中注册新类型,如下所示:

#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;
}
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();
}
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"
        }
    }
}
正如您所看到的,使用变得更加简单。C++代码更干净,QML代码更干净。您不需要在自己周围传递数据。这里我们使用与
TextField
非常相同的
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\u属性
元素定义:

  • 一个
    QString
    textToCheck
  • bool
    属性作为
    valid
    公开给QML
当绑定到
first.text
时,当
TextField
文本更改时,将设置并重置该属性。更改时,您可以检查文本,例如使用相同的代码,并更新
valid
属性。看见 有关
Q_属性
更新的详细信息,请回答或访问上面的注册链接。我把这个方法的实现交给你们,就像exerci一样