Restore TextInput after invalid input
-
I have a TextInput whose text property is bound to a model role. On successful input, the model role is updated, while invalid inputs are rejected and the model is not updated. In the latter case, I tried setting the text property to its default "--", but of course this broke the binding with the model. I've gotten around this by using a Binding {} to restore the binding after text is assigned to manually, but these seems rather convoluted.
Is there a cleaner way to programmatically reset the TextInput so that it reverts to whatever is in the model?
-
For Qt 6.10 (and later presumably): Changing Model Data
For earlier versions, the delegate can call the model's
setData(), which can emitdataChanged()even if the change attempt is rejected. This works for me with Qt 6.9:
QML:ListView { model: m delegate: TextInput { text: display onEditingFinished: m.setData(m.index(index, 0), text) } }C++
bool setData(const QModelIndex &index, const QVariant &value, int role) override { auto updated = (role == Qt::ItemDataRole::EditRole) ? value.toString().toUpper() : value; if (role == Qt::ItemDataRole::EditRole && updated.toString().contains('!')) { emit dataChanged(index, index, { Qt::ItemDataRole::DisplayRole }); return false; } return QStandardItemModel::setData(index, updated, role); }Another option is the Qt Widgets model-view pattern, with a separate editor that is displayed when the delegate instance is activated. On completion of editing, the editor calls
setData(). The delegate itself remains bound to model data, and is unaware of intermediate or invalid edits. Exchange more code in the delegate for less code in the model. -
onEditingFinished: m.setData(m.index(index, 0), text)No need to call
setDataeither in 6.9, you can do it withonEditingFinished: model.display = text. -
onEditingFinished: m.setData(m.index(index, 0), text)No need to call
setDataeither in 6.9, you can do it withonEditingFinished: model.display = text.@GrecKo said in Restore TextInput after invalid input:
onEditingFinished: m.setData(m.index(index, 0), text)No need to call
setDataeither in 6.9, you can do it withonEditingFinished: model.display = text.Indeed. Simplifying further:
ListView { anchors.fill: parent model: m delegate: TextInput { text: display onEditingFinished: display = text } }bool setData(const QModelIndex &index, const QVariant &value, int role) override { auto updated = (role == Qt::ItemDataRole::DisplayRole) ? value.toString().toUpper() : value; if (role == Qt::ItemDataRole::DisplayRole && updated.toString().contains('!')) { return false; } return QStandardItemModel::setData(index, updated, role); } -
uh that doesn't modify back the
textof the TextInput when the setData is refused unless I'm mistaken. -
uh that doesn't modify back the
textof the TextInput when the setData is refused unless I'm mistaken. -
note that you could still return false.
-
@GrecKo Indeed, which is why I've had to resort to always emitting dataChanged and never returning false.
@ECEC said in Restore TextInput after invalid input:
@GrecKo Indeed, which is why I've had to resort to always emitting dataChanged and never returning false.
I was surprised, but it does return the TextInput to the model's value. Full code:
Main.qml
import QtQuick Window { visible: true required property QtObject m ListView { anchors.fill: parent model: m delegate: TextInput { text: display onEditingFinished: display = text } } }main.cpp:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QStandardItemModel> class Model : public QStandardItemModel { public: bool setData(const QModelIndex &index, const QVariant &value, int role) override { auto updated = (role == Qt::ItemDataRole::DisplayRole) ? value.toString().toUpper() : value; if (role == Qt::ItemDataRole::DisplayRole && updated.toString().contains('!')) { return false; } return QStandardItemModel::setData(index, updated, role); } }; int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); Model model; model.appendRow(new QStandardItem("something")); QQmlApplicationEngine engine; engine.setInitialProperties({ {"m", QVariant::fromValue(&model)} }); engine.loadFromModule("untitled1", "Main"); return app.exec(); }