SR_*_*SR_ 1 c++ validation qml qt5 qtquick2
我正在学习Qt 5.5和QML.
框架很强大,有时候有很多方法可以做一件事.我认为有些可能比其他人更有效率,我想知道何时以及为何使用一个而不是另一个.
我想要一个可以解释所做出的选择的答案.当我使用新代码时,如果在C++端有用,可以使用C++ 11和C++ 14语法.
要解决的问题是:
我已经TextField链接到一个可以弹出的按钮FileDialog.我想在文本TextField是red当它是无效的,并保持不变,否则(我把它设置为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_attachedObject是QVariant因为我希望最初引用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我可以想到其他5种解决方案:
onTextChanged:因为我们无法摆脱QML方面的信号.大多数事情都是完成的MyClassBehavior(参见此处)signalDirectoryChanged那么,正如你所看到的那样,做事的多种方式令人困惑,所以很感谢senpai的建议.
这里提供完整的源代码.
我不认为一个答案可以解决您的所有问题,但我仍然认为有关应用程序结构的一些指导可以帮助您前进.
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作为textToCheckbool暴露的财产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 应该能够启用大多数用例.