2016-05-30 6 views
1

У меня есть QAbstractListModel, связанный с ListView в QML, но у меня возникла проблема с обновлением представления из C++. Это с Qt 5.6 mingw, QtQuick 2.6 и QtQuick.Controls 1.5.QAbstractListModel dataChanged сигнал не обновляется ListView (QML)

Настройка: В 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) 

ответ

1

Измените делегатом на что-то вроде этого:

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 при нажатии. Если вы обрабатываете клик каким-либо другим способом, например, покрывая CheckboxMouseArea, вы опустите нарушение привязки и все будет работать.

+0

Я только что протестировал этот код, и он работает! Спасибо за ответ и объяснение. Это было очень проницательно. –

+0

@FrankLaritz Добро пожаловать :) –

0

Другой вариант - восстановить привязку.

CheckBox { 
    onClicked: { 
     valueRole = checked; 
     checked = Qt.binding(function() { return valueRole; }); 
    } 
} 

Преимущество этого подхода заключается в том, что он также работает, если взаимодействие с пользователем не является простым щелчком мыши, то есть, если взаимодействие с пользователем не может просто быть перехвачены с MouseArea.

Смежные вопросы