QML Соединения не работают во вновь вставленной строке? - PullRequest
0 голосов
/ 28 мая 2018

В качестве примера я взял официальный пример из документации Qt и добавил несколько строк, чтобы продемонстрировать проблему:

model.h:

#include <QAbstractListModel>
#include <QStringList>

//![0]
class Animal
{
public:
    Animal(const QString &type, const QString &size);
//![0]

    QString type() const;
    QString size() const;

private:
    QString m_type;
    QString m_size;
//![1]
};

class AnimalModel : public QAbstractListModel
{
    Q_OBJECT

    Q_PROPERTY(int myProperty READ myProperty NOTIFY myPropertyChanged)

signals:
    void myPropertyChanged();

public:
    enum AnimalRoles {
        TypeRole = Qt::UserRole + 1,
        SizeRole
    };

    AnimalModel(QObject *parent = 0);
//![1]

    void addAnimal(const Animal &animal);

    int rowCount(const QModelIndex & parent = QModelIndex()) const;

    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;

    // my code starts
    virtual bool insertRows(int position, int rows,
                            const QModelIndex &index = QModelIndex()) override;
    virtual Qt::ItemFlags flags(const QModelIndex &index) const override {
        return QAbstractListModel::flags(index) | Qt::ItemIsEditable;
    }
    int myProperty() const {
        return m_myProperty;
    }
    int m_myProperty;

public slots:
    void addAnimal();
        // my code ends

protected:
    QHash<int, QByteArray> roleNames() const;
private:
    QList<Animal> m_animals;
//![2]
};
//![2]

model.cpp:

#include "model.h"

Animal::Animal(const QString &type, const QString &size)
    : m_type(type), m_size(size)
{
}

QString Animal::type() const
{
    return m_type;
}

QString Animal::size() const
{
    return m_size;
}

AnimalModel::AnimalModel(QObject *parent)
    : QAbstractListModel(parent)
    , m_myProperty(0)
{
}

void AnimalModel::addAnimal(const Animal &animal)
{
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    m_animals << animal;
    endInsertRows();
}

int AnimalModel::rowCount(const QModelIndex & parent) const {
    Q_UNUSED(parent);
    return m_animals.count();
}

QVariant AnimalModel::data(const QModelIndex & index, int role) const {
    if (index.row() < 0 || index.row() >= m_animals.count())
        return QVariant();

    const Animal &animal = m_animals[index.row()];
    if (role == TypeRole)
        return animal.type();
    else if (role == SizeRole)
        return animal.size();
    return QVariant();
}

bool AnimalModel::insertRows(int position, int rows, const QModelIndex &index)
{
    beginInsertRows(index, position, position + rows - 1);

    for (int row = 0; row < rows; ++row) {
        m_animals.insert(position, Animal("new type", "new animal"));
    }

    endInsertRows();

    return true;
}

void AnimalModel::addAnimal()
{
    insertRow(0);

    m_myProperty = m_animals.count();
    emit myPropertyChanged();
}

//![0]
QHash<int, QByteArray> AnimalModel::roleNames() const {
    QHash<int, QByteArray> roles;
    roles[TypeRole] = "type";
    roles[SizeRole] = "size";
    return roles;
}
//![0]

и, наконец, view.qml:

import QtQuick 2.0
import QtQuick.Controls 2.2

//![0]
Column {
    ListView {
        id: list
        width: 200; height: 250

        model: myModel
        delegate: Text {
            text: index + ": Animal: " + type + ", " + size + ", " + list.model.myProperty

            Connections {
                target: myModel
                onMyPropertyChanged: {
                    console.log(index + ", " + list.model.myProperty)
                }
            }
        }
    }
    Button {
        onClicked: {
            myModel.addAnimal()
        }
    }
}

//![0]

Довольно просто.После нажатия кнопки и вызова addAnimal() я бы ожидал увидеть вывод консоли следующим образом:

qml: 0, 4
qml: 1, 4
qml: 2, 4
qml: 3, 4

, но вместо этого я вижу это:

qml: 1, 4
qml: 2, 4
qml: 3, 4

Однако сигнал, безусловно, испускается (и получено), потому что пользовательский интерфейс показывает не только недавно добавленный элемент с обновленным размером списка животных, но также обновлены все остальные строки.Так почему новая строка не получила onMyPropertyChanged?

Это ошибка Qt?Я использую Qt 5.9.5.

animal list size == 4

1 Ответ

0 голосов
/ 28 мая 2018

Когда вы используете insertRow(0), вы вставляете в позицию 0, поэтому предыдущий элемент 0 теперь будет тем, который имеет соединение и получит сигнал.

Таким образом, последний вставленный элемент не будет иметьсоединение, когда нажата нажатая, поэтому он не будет уведомлен.


Для иллюстрации я объясню это шаг за шагом:

  • В первой вставке естьнет элементов, поэтому никто не получает сигнал.

  • Во второй вставке предыдущий элемент 0 будет текущим 1, и у него будет соединение, поэтому он будет уведомлен.

  • В третьей вставке предыдущий элемент 0 будет текущим 1, а предыдущий будет текущим 2, поэтому 1, 2 имеют соединение и будут уведомлены.

В заключение, создание делегата происходит после выдачи сигнала myPropertyChanged, поэтому вставленный делегат не будет уведомлен, остальные будут, а поскольку ваша вставка всегда находится в первомЭто никогда не будет напечатано qml: 0, n

Для графического понимания предположим, что есть делегаты, и, поскольку они уже существуют, они получат уведомление:

                                            0           0  (+)
0 (+)                                       1  (+)      1  (+)
1 (+)                                       2  (+)      2  (+)
2 (+)                                       3  (+)      3  (+)
... --> clicked --> myPropertyChanged -->  ...     -->  4  (+)
n (+)                                      n+1 (+)     n+1 (+)

(+): указывает, что естьis connection


Более подробное объяснение:

Для ясности правила цикла событий следующие: последовательные задачи выполняются с приоритетом, вызовысигналы поступают, если они не являются срочными.

Давайте проанализируем ваш код более подробно:

  1. insertRow (0);
  2. m_myProperty = m_animals.count ();
  3. emit myPropertyChanged ();

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

Сразу после завершения шага 3,задачи по сигналу выполняются, поэтому вы создаете - создаете делегат и соединение, поэтому теперь вы отдаете приоритет сигналу myPropertyChanged и вызываете существующие соединения минус последнее, так как оно не существовало, когда сигнал испускался.


В заключение будут вызываться только те слоты, которые существуют, когда сигнал испускает сигнал, новые соединения, которые создаются сразу после выдачи сигнала, пока вызов слота не будет вызван.

...