LINUX.ORG.RU

Забавное поведение при отображении QAbstractItemModel на QSortFilterProxyModel

 , ,


0

1

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

Есть модель дерева ListsRegisterModel, реализующая QAbstractItemModel. Есть QSortFilterProxyModel, к которой подключаю источником модель ListsRegisterModel. Вывожу ListsRegisterModel и прокси-модель в различные QTreeView.

И тут начинается странное. У меня пропали ветки второго уровня во втором отображении, хотя судя по виду, rowCount() отрабатывает правильно.

Скриншот

Код:

    ListsRegisterModel *model = new ListsRegisterModel(this);

    QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
    proxyModel->setSourceModel(model);


    QTreeView *view = new QTreeView(this);
    view->setModel(model);

    QTreeView *view2 = new QTreeView(this);
    view2->setModel(proxyModel);

class ListsRegisterModel : public QAbstractItemModel
{
    Q_OBJECT
public:
    explicit ListsRegisterModel(QObject *parent = 0);
    ~ListsRegisterModel();

    QVariant data(const QModelIndex &index, int role) const;
    Qt::ItemFlags flags(const QModelIndex &index) const;
    QVariant headerData(int section, Qt::Orientation orientation,
                        int role = Qt::DisplayRole) const;
    QModelIndex index(int row, int column,
                      const QModelIndex &parent = QModelIndex()) const;
    QModelIndex parent(const QModelIndex &index) const;
    int rowCount(const QModelIndex &parent = QModelIndex()) const;
    int columnCount(const QModelIndex &parent = QModelIndex()) const;
    bool setData(const QModelIndex &index, const QVariant &value,
                 int role = Qt::CheckStateRole);

private:
    enum Columns
    {
        RamificationColumn,
        ListNameColumn = RamificationColumn,
        ListSubscriberColumn
    };

    TreeItem *rootItem;

    /**
     * @brief parseListsFile разбираем структуру файла с описаниями списков
     * @param path путь к файлу с регистром списков
     * @return QMultiMap<имя_списка, адрес получателя>
     */
    QMultiMap<QString, QString> parseListsFile(QString path);

    /**
     * @brief setupModelData первоначальная настройка модели
     * @param lists multiMap с регистром списков
     * @param parent корневой элемент дерева
     */
    void setupModelData(QMultiMap<QString, QString> lists, TreeItem *parent);
    TreeItem *getItem(const QModelIndex &index) const;
};

#include "listsregistermodel.h"
#include <QtCore>
#include "../treeItem/treeitem.h"

ListsRegisterModel::ListsRegisterModel(QObject *parent) :
    QAbstractItemModel(parent)
{
    QList<QVariant> rootData;
    rootData << tr("Имя") << tr("Абоненты");
    rootItem = new TreeItem(rootData);

    QMultiMap<QString, QString> listsReg = parseListsFile("etc/lists.reg");
    setupModelData(listsReg, rootItem);
}


void ListsRegisterModel::setupModelData(
        QMultiMap<QString, QString> lists, TreeItem *parent)
{
    TreeItem *root = parent;
    QList<TreeItem*> parents;
    parents << root;

    foreach (QString listName, lists.uniqueKeys()) {
        root->appendChild(new TreeItem(listName, root));
        if (root->childCount() > 0) {
            parents << root->child(root->childCount() - 1);
        }
        foreach (QString subscriber, lists.values(listName)) {
            parents.last()->appendChild(
                        new TreeItem(subscriber, parents.last()));
        }
    }
}


QMultiMap<QString, QString> ListsRegisterModel::parseListsFile(QString path)
{
    QMultiMap<QString, QString> listsRegister;
    QRegExp rx("^(\\d{4})\\s+\\{(.+)\\}$");

    QFile file(path);
    if (!file.exists()) {
        qWarning() << "Warning: file " << file.fileName() << " is not exist";
    }

    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qWarning() << "Warning: file " << file.fileName() << " didn't' open";
    }

    while (!file.atEnd()) {
        // поиск регулярного выражения в следующей строке
        rx.indexIn(file.readLine().trimmed());
        // выбор абонентов выбранного списка
        QStringList subscribers =
                rx.cap(2).simplified().split(",", QString::KeepEmptyParts);
        // заполнение MultiMap контейнера значениями <имя списка, абонент>
        foreach (QString addr, subscribers) {
            listsRegister.insertMulti(rx.cap(1), addr);
        }
    }

    file.close();

    return listsRegister;
}


ListsRegisterModel::~ListsRegisterModel()
{
    delete rootItem;
}


QVariant ListsRegisterModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    TreeItem *item = getItem(index);

    switch (role) {
    case Qt::DisplayRole:
        if (item->parent() == rootItem) {
            return item->data(index.column());
        }
        else {
            qDebug() << item->data(index.column() - 1);
            return item->data(index.column() - 1);
        }
        break;
    case Qt::CheckStateRole:
        if (index.column() == RamificationColumn
                && item->parent() == rootItem) {
            return QVariant(item->checkState());
        }
        break;
    default:
        return QVariant();
        break;
    }
    return QVariant();
}


TreeItem *ListsRegisterModel::getItem(const QModelIndex &index) const
{
    if (index.isValid()) {
        TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
        if (item) return item;
    }
    return rootItem;
}


Qt::ItemFlags ListsRegisterModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return 0;

    const Qt::ItemFlags f = QAbstractItemModel::flags(index);

    TreeItem * item = getItem(index);

    if (index.column() == RamificationColumn && item->parent() == rootItem) {
        return  f | Qt::ItemIsUserCheckable;
    }
    return f;
}


QVariant ListsRegisterModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
        return rootItem->data(section);

    return QVariant();
}


QModelIndex ListsRegisterModel::index(int row, int column, const QModelIndex &parent) const
{
    TreeItem * parentItem;

    if (row < 0 || column < 0)
        return QModelIndex();

    if (!parent.isValid())
        parentItem = rootItem;
    else
        parentItem = getItem(parent);

    TreeItem *childItem = parentItem->child(row);

    if (childItem)
        return createIndex(row, column, childItem);

    return QModelIndex();
}


QModelIndex ListsRegisterModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();

    TreeItem *childItem = getItem(index);
    TreeItem *parentItem = childItem->parent();

    if (parentItem == rootItem)
        return QModelIndex();

    return createIndex(parentItem->row(), 0, parentItem);
}


int ListsRegisterModel::rowCount(const QModelIndex &parent) const
{
    TreeItem *parentItem;
    if (parent.column() > 0)
        return 0;

    if (!parent.isValid())
        parentItem = rootItem;
    else
        parentItem = static_cast<TreeItem*>(parent.internalPointer());

    return parentItem->childCount();
}


int ListsRegisterModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return static_cast<TreeItem*>(parent.internalPointer())->columnCount();
    else
        return rootItem->columnCount();
}


bool ListsRegisterModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role != Qt::CheckStateRole)
        return false;

    TreeItem *item = getItem(index);

    Qt::CheckState state
            = (value.toInt() == Qt::Checked) ? Qt::Checked : Qt::Unchecked;
    item->setCheckState(state);

    emit dataChanged(index, index);

    return true;
}

UPD: уменьшил объем исходника

class ListsRegisterModel : public QAbstractItemModel
{
    Q_OBJECT
public:
    explicit ListsRegisterModel(QObject *parent = 0);
    ~ListsRegisterModel();

    QVariant data(const QModelIndex &index, int role) const;
    Qt::ItemFlags flags(const QModelIndex &index) const;
    QVariant headerData(int section, Qt::Orientation orientation,
                        int role = Qt::DisplayRole) const;
    QModelIndex index(int row, int column,
                      const QModelIndex &parent = QModelIndex()) const;
    QModelIndex parent(const QModelIndex &index) const;
    int rowCount(const QModelIndex &parent = QModelIndex()) const;
    int columnCount(const QModelIndex &parent = QModelIndex()) const;
    bool setData(const QModelIndex &index, const QVariant &value,
                 int role = Qt::CheckStateRole);

private:
    enum Columns
    {
        RamificationColumn,
        ListNameColumn = RamificationColumn,
        ListSubscriberColumn
    };

    TreeItem *rootItem;

    QMultiMap<QString, QString> parseListsFile(QString path);
    void setupModelData(QMultiMap<QString, QString> lists, TreeItem *parent);
    TreeItem *getItem(const QModelIndex &index) const;
};



ListsRegisterModel::ListsRegisterModel(QObject *parent) :
    QAbstractItemModel(parent)
{
    QList<QVariant> rootData;
    rootData << tr("List") << tr("Subscribers");
    rootItem = new TreeItem(rootData);

    QMultiMap<QString, QString> listsReg = parseListsFile("etc/lists.reg");
    setupModelData(listsReg, rootItem);
}


QVariant ListsRegisterModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    TreeItem *item = getItem(index);

    switch (role) {
    case Qt::DisplayRole:
        if (item->parent() == rootItem) {
            return item->data(index.column());
        }
        else {
            qDebug() << item->data(index.column() - 1);
            return item->data(index.column() - 1);
        }
        break;
    case Qt::CheckStateRole:
        if (index.column() == RamificationColumn
                && item->parent() == rootItem) {
            return QVariant(item->checkState());
        }
        break;
    default:
        return QVariant();
        break;
    }
    return QVariant();
}




QModelIndex ListsRegisterModel::index(int row, int column, const QModelIndex &parent) const
{
    TreeItem * parentItem;

    if (row < 0 || column < 0)
        return QModelIndex();

    if (!parent.isValid())
        parentItem = rootItem;
    else
        parentItem = getItem(parent);

    TreeItem *childItem = parentItem->child(row);

    if (childItem)
        return createIndex(row, column, childItem);

    return QModelIndex();
}


QModelIndex ListsRegisterModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();

    TreeItem *childItem = getItem(index);
    TreeItem *parentItem = childItem->parent();

    if (parentItem == rootItem)
        return QModelIndex();

    return createIndex(parentItem->row(), 0, parentItem);
}
h0x0d9 ()

Залил исходники: git@bitbucket.org:h0x0d9/myfilterproxymodel.git

h0x0d9 ()
Ответ на: комментарий от h0x0d9

Ох чёрт, как же много...

Вечером или завтра повнимательнее посмотрю, навскидку - проверь parent'ов у QModelIndex'ов.

P.S.

enum Columns
    {
        RamificationColumn,
        ListNameColumn = RamificationColumn,
        ListSubscriberColumn
    };

ListNameColumn = RamificationColumn - зачем??

Adonai ★★★ ()
Последнее исправление: Adonai (всего исправлений: 1)

Исправь: int ListsRegisterModel::columnCount(const QModelIndex &parent) const { return 2; }

Прямой вызов модели возвращает значение для 2-ой колонки, поэтому показывается (но нормально не выделяется - попробуй выбрать строку в tree-view). А прокси просто не запрашивает значение для 2-ой колонки, т.к. кол-во колонок == 1.

Чтобы скомпилить проект добавил в mainwindow.cpp: #include <QHBoxLayout> #include <QTreeView>

tnodir ()
Ответ на: комментарий от Adonai

Adonai, спасибо за отклик.

ListNameColumn = RamificationColumn - зачем??

Синононимы, чтобы разделить логически название колонки и номер колонки с ветвлением.

h0x0d9 ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.