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, and I'd like to understand when and why using one rather than another.
I'd like an answer that can explain the choices made. As I'm working with new code, C++ 11 and C++ 14 syntax can be used if useful on the C++ side.
To problem to solve is :
I've got a TextField
linked to a button that can pop a FileDialog
. I want the text in the TextField
to be red
when it is invalid, and left unchanged otherwise (I set it to green
because I don't know how to get the "default" colour). The value of the TextField
is to be used in the C++ side, and is persisted when application exits.
I've coded a version using a custom QValidator
, some properties on the QML side, an using onTextChanged:
and onValidatorChanged:
to modify the colour of the text. The text colour is set according to a property (valid
) in the QML that is set from the C++ side (in the validator). To set the property, the C++ has to find by name the caller (TextField
named directoryToSave
) because I've not yet found a way to pass the object itself as argument.
Here is the QML code contained in 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;
}
}
The custom validator code :
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();
};
Associated with these definitions :
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
is a QVariant
because I wanted the QML instance to be referenced instead of its name, initially.
As the validator is only concerned about validation it does not contain any state about the data it validates.
As I must get the value of the TextField
in order to do something in the application, I've built another class to save the value when it changes : MyClass
. I see it as my controller. Currently, I store data directly in the application object, that can be seen as my model. That will change in the future.
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass() {}
public slots:
void cppSlot(const QString &string) {
((LAACApplication *) qApp)->setLocalDataDirectory(string);
}
};
The instances of controller MyClass
and validator QDirectoryValidator
are created during application initialization with the following code :
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);
The //delete
only serves the purpose to discover what happens when the instance is deleted or not.
The main.qml
ties things together :
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)
}
And, at last, the MainForm.ui.qml glue :
Item {
// ...
property alias directoryToSavePlaceholderText: directoryToSave.placeholderText
property alias directoryToSaveText: directoryToSave.text
// ...
}
I'm not satisfied having :
- dirt in
onValidatorChanged:
to be sure to initialize the UI with the correct colour - byname tree searching to find the caller (looks unefficient ; may not be)
- spaghetti-like coding among several instances of C++ and parts of QML
I can think of 5 other solutions :
- getting rid of the custom validator, and keeping only
onTextChanged:
because we cannot get rid of signaling from QML side. Most things are done inMyClass
- patching Qt in order to implement a property value write interceptor for something else than
Behavior
(see here) - registering a C++ type to attach to the QML Object. (see here)
- registering a type and using it as both a controller and a data structure (bean-like), to pass onto the model afterwards (see here)
- using signals manually as I already do with
signalDirectoryChanged
Well, as you see, the plethoric ways of doing things is confusing, so senpai advice is appreciated.
Complete source code available here.
See Question&Answers more detail:os