我正在学习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_attachedObject
是 QVariant
因为我希望最初引用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 回答
我不认为一个答案可以解决您的所有问题,但我仍然认为有关应用程序结构的一些指导可以帮助您前进 .
AFAIK那里's no central place that discuss application structure. Actually, there'也没有关于QML中UI结构的建议(例如参见this讨论) . 也就是说,我们可以在QML应用程序中找出一些常见的模式和选择,我们将在下面进一步讨论 .
在到达那里之前,我想强调一个重要方面 . QML离C不远 . QML基本上是
QObject
-派生对象的对象树,其生命周期由QMLEngine
实例控制 . 在这个意义上,一段代码就像它与使用简单命令式C语法编写的
Validator
的QLineEdit
没有什么不同 . 正如说的那样,除了一生 . 鉴于此,在普通C中实现验证器是错误的:验证器是TextField
的一部分,并且应该具有与之一致的生命周期 . 在这个具体案例中,registering a new type是最好的方式 . 生成的代码更易于阅读,更易于维护 .现在,这个案子很特别 .
validator
属性接受派生自Validator
的对象(参见声明here和一些用法here,here和here) . 因此,我们可以定义一个QValidator
-derived类型,而不是简单地定义QValidator
-derived类型,而是使用它代替IntValidator
(或其他QML验证类型) .我们的
DirectoryValidator
头文件如下所示:实现文件是这样的:
现在您可以在
main
中注册新类型,如下所示:你的QML代码可以像这样重写:
如您所见,使用变得更加直接 . C代码更干净,但QML代码更清晰 . 您不需要自己传递数据 . 这里我们使用的
acceptableInput
与TextField
完全相同,因为它是由与之关联的Validator
设置的 .通过注册另一种不是从
Validator
派生的类型可以获得相同的效果 - 失去与acceptableInput
的关联 . 看下面的代码:这里
ValidationType
可以用两个Q_PROPERTY
元素定义:a
QString
暴露于QML为textToCheck
bool
属性公开为valid
到QML绑定到
first.text
时,属性设置并在TextField
文本更改时重置 . 在更改时,您可以检查文本,例如使用相同的代码,并更新valid
属性 . 有关Q_PROPERTY
更新的详细信息,请参阅this answer或上面的注册链接 . 作为练习,我将这种方法的实施留给你 .最后,当谈到类似服务/全局对象/类型时,使用非instanciable / singleton类型可能是正确的方法 . 在这种情况下,我会让documentation跟我说话:
qmlRegisterSingletonType是喜欢的功能 . 这也是"Quick Forecast"应用程序中使用的方法,即Digia展示应用程序 . 请参阅main和相关的ApplicationInfo类型 .
另外context properties特别有用 . 由于它们被添加到根上下文(请参阅链接),因此它们可用于所有QML文件,也可用作全局对象 . 访问DB的类,访问Web服务的类或类似的类都有资格作为上下文属性添加 . 另一个有用的案例与模型有关:C模型,如
AbstractListModel
,可以注册为上下文属性,并用作视图的模型,例如: aListView
. 请参阅可用的示例here .Connections类型可用于连接上下文属性和寄存器类型(显然也是单例)发出的信号 . 而信号,
Q_INVOKABLE
函数和SLOT
可以直接从QML调用,以触发其他C槽,如部分讨论here .总结一下,使用
objectName
并从C访问QML是可行且可行的,但通常不鼓励(参见警告here) . 此外,在必要和可能的情况下,QML / C集成通过专用属性受到青睐,例如参见QMLCamera
类型的mediaObject属性 . 使用(单例)注册类型,上下文属性并通过Connections
类型将它们连接到QML,Q_INVOKABLE
,SLOT
和SIGNAL
应该能够启用大多数用例 .