我有一个QAbstractListModel连接到QML中的ListView,但我遇到了从C ++更新视图的问题。这是Qt 5.6 mingw,QtQuick 2.6和QtQuick.Controls 1.5。
设定: ListView使用带有属性的自定义复选框委托来存储模型中的值。当用户单击代理时,委托更新模型。在我的QML中,我还有一个切换按钮,它调用模型中的一个插槽,切换模型中的数据,并为所有行发出dataChanged()信号(将所有复选框设置为不检查或检查)。
问题: 切换按钮工作正常,直到用户与任何复选框委托进行交互。用户执行此操作后,dataChanged()信号不再更新该特定委托。我还验证了我的模型的data()函数在用户交互之前被调用所有行,然后只在用户交互后用户没有单击的行上调用它。这让我相信幕后某处视图选择不更新某些行,但我无法弄清楚原因。
可能的解决方案: 无论用户交互如何,在模型中发出layoutChanged()都会更新切换按钮的视图,但这会导致整个视图被重绘并且相对较慢。
此问题是使用复选框创建的,但它适用于任何类型的用户交互。以下是重新创建问题所需的所有代码。
main.qml
import QtQuick 2.6
import QtQuick.Controls 1.5
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
ColumnLayout {
anchors.fill: parent
spacing: 10
Button {
Layout.preferredHeight: 100
Layout.preferredWidth: 100
text: "Test!"
onClicked: {
console.log("attempting to refresh qml")
testModel.refresh()
testModel.print()
}
}
ScrollView {
Layout.fillHeight: true
Layout.fillWidth: true
ListView {
id: view
anchors.fill: parent
spacing: 5
model: testModel
delegate: Rectangle {
height: 50
width: 100
color: "lightgray"
CheckBox {
id: checkBox
anchors.fill: parent
checked: valueRole
onClicked: {
valueRole = checked
}
}
}
}
}
}
}
TestModel.cpp
#include "testmodel.h"
TestModel::TestModel(QObject *parent) : QAbstractListModel(parent) {
roleVector << TaskRoles::valueRole;
testValue = false;
}
TestModel::~TestModel() {}
void TestModel::setup(const QList<bool> &inputList) {
// Clear view
removeRows(0, valueList.length());
// Update value list
valueList = inputList;
// Add rows
insertRows(0, valueList.length());
}
// Emits data changed for entire model
void TestModel::refresh() {
qDebug() << "attempting to refresh c++";
// Toggle all values in model
for (int i=0; i < valueList.length(); i++) {
valueList[i] = testValue;
}
// Toggle test value
testValue = !testValue;
// Update view
// this works but is slow
// layoutAboutToBeChanged();
// layoutChanged();
// this doesn't work if the user clicked the checkbox already
dataChanged(createIndex(0, 0), createIndex(rowCount()-1, 0), roleVector);
}
void TestModel::print() {
qDebug() << "Model:" << valueList;
}
QHash<int, QByteArray> TestModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[valueRole] = "valueRole";
return roles;
}
int TestModel::rowCount(const QModelIndex & /*parent*/) const {
return valueList.length();
}
QVariant TestModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/) const {
return QVariant();
}
Qt::ItemFlags TestModel::flags(const QModelIndex & /*index*/) const {
return static_cast<Qt::ItemFlags>(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEditable);
}
QVariant TestModel::data(const QModelIndex &index, int role) const {
// qDebug() << QString("Get Data - Row: %1, Col: %2, Role: %3").arg(index.row()).arg(index.column()).arg(role);
int row = index.row();
if (row >= 0 && row < valueList.length()) {
switch(role) {
case valueRole:
qDebug() << QString("Get Data - Row: %1, Col: %2, Role: %3").arg(index.row()).arg(index.column()).arg(role);
return valueList.at(row);
default:
return QVariant();
}
}
return QVariant();
}
bool TestModel::setData(const QModelIndex &index, const QVariant &value, int role) {
qDebug() << QString("Set Data - Row: %1, Col: %2, Role: %3").arg(index.row()).arg(index.column()).arg(role);
int row = index.row();
if (row >= 0 && row < valueList.length()) {
switch(role) {
case valueRole:
valueList[row] = value.toBool();
break;
default:
break;
}
dataChanged(index, index, QVector<int>() << role);
print();
}
return true;
}
bool TestModel::insertRows(int row, int count, const QModelIndex & /*parent*/) {
// Check bounds
if (row < 0 || count < 0) {
return false;
}
if (count == 0) {
return true;
}
if (row > rowCount()) {
row = rowCount();
}
beginInsertRows(QModelIndex(), row, row+count-1);
endInsertRows();
return true;
}
bool TestModel::removeRows(int row, int count, const QModelIndex & /*parent*/) {
// Check bounds
if (row < 0 || count < 0 || rowCount() <= 0) {
return false;
}
if (count == 0) {
return true;
}
if (row >= rowCount()) {
row = rowCount() - 1;
}
beginRemoveRows(QModelIndex(), row, row+count-1);
endRemoveRows();
return true;
}
TestModel.h
#ifndef TESTMODEL_H
#define TESTMODEL_H
#include <QAbstractListModel>
#include <QDebug>
#include <QVector>
class TestModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit TestModel(QObject *parent = 0);
~TestModel();
// Roles
enum TaskRoles {
valueRole = Qt::UserRole + 1,
};
// Row / Column Functions
int rowCount(const QModelIndex &parent = QModelIndex()) const ;
// Header / Flag Functions
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
// Model Get / Set Functions
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
// Row Insertion / Deletion Functions
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
protected:
// Value List
QList<bool> valueList;
QVector<int> roleVector;
public slots:
QHash<int, QByteArray> roleNames() const;
void setup(const QList<bool> &inputList);
void refresh();
void print();
};
#endif // TESTMODEL_H
的main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QQmlContext>
#include "testmodel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
TestModel testModel;
testModel.setup(QList<bool>() << true << false << true << false << true);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
engine.rootContext()->setContextProperty("testModel", &testModel);
return app.exec();
}
示例调试输出(注意在单击委托中的复选框后,从5次获取数据调用到4次获取数据调用的方式):
qml: attempting to refresh qml
attempting to refresh c++
"Get Data - Row: 0, Col: 0, Role: 257"
"Get Data - Row: 1, Col: 0, Role: 257"
"Get Data - Row: 2, Col: 0, Role: 257"
"Get Data - Row: 3, Col: 0, Role: 257"
"Get Data - Row: 4, Col: 0, Role: 257"
Model: (false, false, false, false, false)
qml: clicked checkbox
"Set Data - Row: 0, Col: 0, Role: 257"
Model: (true, false, false, false, false)
qml: attempting to refresh qml
attempting to refresh c++
"Get Data - Row: 1, Col: 0, Role: 257"
"Get Data - Row: 2, Col: 0, Role: 257"
"Get Data - Row: 3, Col: 0, Role: 257"
"Get Data - Row: 4, Col: 0, Role: 257"
Model: (true, true, true, true, true)
qml: attempting to refresh qml
attempting to refresh c++
"Get Data - Row: 1, Col: 0, Role: 257"
"Get Data - Row: 2, Col: 0, Role: 257"
"Get Data - Row: 3, Col: 0, Role: 257"
"Get Data - Row: 4, Col: 0, Role: 257"
Model: (false, false, false, false, false)
答案 0 :(得分:2)
将您的委托修改为以下内容:
delegate: Rectangle {
height: 50
width: 100
color: "lightgray"
CheckBox {
id: checkBox
anchors.fill: parent
checked: valueRole
// onClicked: {
// valueRole = checked
// }
}
MouseArea {
anchors.fill: parent
onClicked: {
valueRole = !checkBox.checked
}
}
}
Checkbox
的值绑定到valueRole
,但它只是在单击时用bool
值覆盖此绑定。如果您以其他方式处理点击,例如通过用Checkbox
覆盖MouseArea
,您将省略破坏绑定,一切都将正常工作。
答案 1 :(得分:1)
另一种选择是恢复绑定。
CheckBox {
onClicked: {
valueRole = checked;
checked = Qt.binding(function() { return valueRole; });
}
}
这种方法的优点是,如果用户交互不是简单的点击,它也可以工作,即如果用户交互不能简单地用MouseArea拦截。