Alterações especializadas na QValidator e na interface QML
Estou aprendendo Qt 5.5 e QML.
A estrutura é poderosa e, às vezes, existem muitas maneiras de fazer uma coisa. Eu acho que alguns são provavelmente mais eficientes que outros, eEu gostaria de entender quando e por que usar um ao invés de outro.
Eu gostaria de uma resposta que possa explicar as escolhas feitas. Como estou trabalhando com um novo código, a sintaxe C ++ 11 e C ++ 14 pode ser usada se útil no lado C ++.
Problema a resolver é:
eu tenho umaTextField
vinculado a um botão que pode abrir umFileDialog
. Eu quero o texto noTextField
ser estarred
quando é inválido e permaneceu inalterado de outra forma (eu o defino comogreen
porque não sei como obter a cor "padrão"). O valor doTextField
deve ser usado no lado do C ++ e persiste quando o aplicativo é encerrado.
Eu codifiquei uma versão usando um personalizadoQValidator
, algumas propriedades no lado da QML, um usoonTextChanged:
eonValidatorChanged:
para modificar a cor do texto. A cor do texto é definida de acordo com uma propriedade (valid
) no QML configurado no lado do C ++ (no validador). Para definir a propriedade, o C ++ precisa localizar pelo nome o chamador (TextField
nomeadodirectoryToSave
) porque ainda não encontrei uma maneira de passar o próprio objeto como argumento.
Aqui está o código QML contido emMainForm.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;
}
}
O código do validador customizado:
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();
};
Associado a estas definições:
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
é umQVariant
porque eu queria que a instância QML fosse referenciada em vez de seu nome, inicialmente.
Como o validador está preocupado apenas com a validação, ele não contém nenhum estado sobre os dados que valida.
Como devo obter o valor doTextField
para fazer algo no aplicativo, criei outra classe para salvar o valor quando ele mudar:MyClass
. Eu vejo isso como meucontrolador. Atualmente, eu armazeno dados diretamente no objeto do aplicativo, que podem ser vistos como meusmodelo. Isso vai mudar no futuro.
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass() {}
public slots:
void cppSlot(const QString &string) {
((LAACApplication *) qApp)->setLocalDataDirectory(string);
}
};
As instâncias do controladorMyClass
e validadorQDirectoryValidator
são criados durante a inicialização do aplicativo com o seguinte código:
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);
o//delete
serve apenas para descobrir o que acontece quando a instância é excluída ou não.
omain.qml
amarra as coisas:
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)
}
E, finalmente, a cola MainForm.ui.qml:
Item {
// ...
property alias directoryToSavePlaceholderText: directoryToSave.placeholderText
property alias directoryToSaveText: directoryToSave.text
// ...
}
Não estou satisfeito por ter:
sujeira emonValidatorChanged:
certifique-se de inicializar a interface do usuário com a cor corretaárvore de nomes pesquisando para encontrar o chamador (parece ineficiente; pode não ser)codificação do tipo espaguete entre várias instâncias do C ++ e partes da QMLEu posso pensar em 5 outras soluções:
livrar-se do validador personalizado e manter apenasonTextChanged:
porque não podemos nos livrar da sinalização do lado da QML. A maioria das coisas é feita emMyClass
remendar Qt para implementar uminterceptor de gravação do valor da propriedade para algo mais do queBehavior
(Vejoaqui)registrando um tipo C ++ para anexar ao objeto QML. (Vejoaqui)registrar um tipo e usá-lo como um controlador e uma estrutura de dados (semelhante a um bean), para passar para o modelo posteriormente (consulteaqui)usando sinais manualmente como eu já faço comsignalDirectoryChanged
Bem, como você vê, as maneiras pletóricas de fazer as coisas são confusas, então os conselhos da senpai são apreciados.
Código fonte completo disponívelaqui.