crossplatform.ru

Здравствуйте, гость ( Вход | Регистрация )

 
Ответить в данную темуНачать новую тему
> Как правильно создать 3-х уровневую модель дерева?
Rocky
  опции профиля:
сообщение 14.12.2010, 11:42
Сообщение #1


Старейший участник
****

Группа: Участник
Сообщений: 530
Регистрация: 22.12.2008
Из: Санкт-Петербург
Пользователь №: 463

Спасибо сказали: 22 раз(а)




Репутация:   7  


Вроде бы внимательно изучил документацию, примеры simple и edit tree model. Начал сам делать и совсем запутался... Помогите распутаться плиз... Хочу сделать 3-х уровневую модель дерева. В примерах есть класс TreeItem который содержит указатель на родителя и список чайлдов. Но этот родитель и элементы списка сами являются TreeItem. С этим вроде бы ясно... А как развернуть эту структуру? Чтобы данные как бы отделить друг от друга?
Например превратить ее в 4 класса:
1. Класс самой модели.
2. Класс с данными верхенго уровня (содержит список данных и список чайлдов 3)
3. Класс с данными среднего уровня (содержит список данных и список чайлдов 4 и указатель на парента 2)
4. Класс с данными нижнего уровня (содержит список данных и указатель на парента 3)
А как при таком раскладе написать тела виртуальных функций QAbstractItemModel (index и parent и rowCount)?

3-й день сижу и ниче не получается (

Или этот подход неверен?

Сама задача такая. Есть список имен. Каждому элементу из этого списка соответсвует список других имен. Каждому элементу из последнего списка соответсвует набор данных. Как это все представить для модели? ((

Спасибо если кто-нибудь что-нибудь подскажет...

Сообщение отредактировал Rocky - 14.12.2010, 11:47
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
Алексей1153
  опции профиля:
сообщение 14.12.2010, 12:40
Сообщение #2


фрилансер
******

Группа: Участник
Сообщений: 2941
Регистрация: 19.6.2010
Из: Обливион
Пользователь №: 1822

Спасибо сказали: 215 раз(а)




Репутация:   34  


Rocky, на форуме много раз упоминалось про борьбу с деревом - попробуй начать с поиска
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
Rocky
  опции профиля:
сообщение 14.12.2010, 13:09
Сообщение #3


Старейший участник
****

Группа: Участник
Сообщений: 530
Регистрация: 22.12.2008
Из: Санкт-Петербург
Пользователь №: 463

Спасибо сказали: 22 раз(а)




Репутация:   7  


Нашел пару тем
http://www.forum.crossplatform.ru/index.ph...&#entry6407
http://www.forum.crossplatform.ru/index.ph...ic=5436&hl=

Т.е. что, получается что не нужно наследоваться от QAbstractItemModel а тупо заполнять дерево вручную?

Так а если нужно обрабатывать нажатие мыши то как тогда с таким подходом?... Получается, что не годится... Имхо проще через QTreeWidget сделать.. но тогда тормоза будут если много данных ((
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
Rocky
  опции профиля:
сообщение 14.12.2010, 13:54
Сообщение #4


Старейший участник
****

Группа: Участник
Сообщений: 530
Регистрация: 22.12.2008
Из: Санкт-Петербург
Пользователь №: 463

Спасибо сказали: 22 раз(а)




Репутация:   7  


И если нужна отрисовка элементов своя...
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
kuzulis
  опции профиля:
сообщение 14.12.2010, 13:58
Сообщение #5


Активный участник
***

Группа: Участник
Сообщений: 393
Регистрация: 29.6.2009
Пользователь №: 862

Спасибо сказали: 36 раз(а)




Репутация:   7  


Цитата
Т.е. что, получается что не нужно наследоваться от QAbstractItemModel а тупо заполнять дерево вручную?

Нужно наследоваться.
Просто суть такова, что источником данных для модели выступает класс TreeItem.
Т.е. каждый TreeItem содержит список столбцов(собственно - данных), а также список строк(список указателей на дочерние итемы).
т.е. источник данных типа TreeItem специально имеет древовидную структуру для того чтобы легко его можно было адаптировать к QAbstractItemModel-ли.
Так вот, переопределяя в модели виртуальные методы, мы тем самым, можем для любого итема источника данных показать любые его данные какие хотим.
К примеру, итем TreeItem имеет список данных из 3-х столбцов, но мы хотим показать только два из них, поэтому, переопределяя виртуальный метод в модели, мы можем подсовывать в него нужные нам столбцы TreeItem .

Вместо списка данных TreeItem типа QList<QVariant>, можно испоотзовать любые структуры данных. QList<QVariant> в примере (ИМХО) приведен чисто чтобы показать как оно работает, т.е. общий случай. Тем более, что в QList<QVariant> можно впихнуть любые данные, поэтому такое решение универсально.

Цитата
Так а если нужно обрабатывать нажатие мыши то как тогда с таким подходом?

Нажатие мыши обрабатываются в TreeView, при этом, ты получаешь готовый текущий QModelIndex, и можеш, зная его, изменить в нем данные с помощью методов модели типа setData() и т.п.
т.е. в данном случае, подразумеваем, что данные в источнике ты меняешь сверху вниз, т.е:
TreeView->Model->DataSource.

А вот если у тебя данные в TreeItem кто-то меняет извне (например какой-то алгоритм), то тут нужно придумать какой-то механизм чтобы уведомить модель о том, что данные в данном итеме TreeItem изменились. т.е. этим механизмом будет являться твоё собственное API.

Как-то так...
В общем, весь этот подход MVC какой-то не особо универсальный. ИМХО.

ЗЫ: Я сам долго анализировал и втыкал что есть что. Вот, довтыкался :).
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
Rocky
  опции профиля:
сообщение 14.12.2010, 14:27
Сообщение #6


Старейший участник
****

Группа: Участник
Сообщений: 530
Регистрация: 22.12.2008
Из: Санкт-Петербург
Пользователь №: 463

Спасибо сказали: 22 раз(а)




Репутация:   7  


Цитата(kuzulis @ 14.12.2010, 14:58) *
Вместо списка данных TreeItem типа QList<QVariant>, можно испоотзовать любые структуры данных. QList<QVariant> в примере (ИМХО) приведен чисто чтобы показать как оно работает, т.е. общий случай. Тем более, что в QList<QVariant> можно впихнуть любые данные, поэтому такое решение универсально.

Да, любые... но если нужна иерархическая структура данных? Так вот мне совершенно не понятно, что возвращать из функций класса-наследника от QAbstractItemModel index/parent/rowCount. Вот например:
Раскрывающийся текст
//ResultTableItem.h
class CResultTableChild
{
    QString m_sName1;
    QString m_sName2;
    QString m_sIntAn;
    QString m_sIntCm;
    QString m_sIntRel;
    QString m_sConc;

public:
    QVariant data(int nColumn) const;
    bool setData(int nColumn, const QVariant &oData);
};

class CResultTableParent
{
    QList<CResultTableChild*> childItems;

public:
    CResultTableChild *child(int number);
    int childCount() const;
    bool insertChild(int position);
};

class CResultTableItem
{
    QList<CResultTableParent*> childItems;
    CResultTableParent *parentItem;

public:
    CResultTableItem(CResultTableParent *parent = 0);
    ~CResultTableItem();

    CResultTableParent *child(int number);
    int childCount() const;    
    bool insertChild(int position);
    CResultTableParent *parent();
    int childNumber() const;

};


Раскрывающийся текст
//ResultTableModel.h
class CResultTableItem;
class CResultTableParent;
class CResultTableModel : public QAbstractItemModel
{
    Q_OBJECT

    //CResultTableItem *getItem(const QModelIndex &index) const;
    CResultTableParent *rootItem;

public:
    CResultTableModel(QObject *parent = 0);
    virtual ~CResultTableModel();

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


Раскрывающийся текст
//ResultTableModel.cpp
#include <QtGui>

#include "./ResultTableItem.h"
#include "./ResultTableModel.h"

//---------------------------------------------------------------------------------------------------------//
CResultTableModel::CResultTableModel(QObject *parent) : QAbstractItemModel(parent)
{
    rootItem = new CResultTableParent();

    rootItem->insertChild(0);
    rootItem->child(0)->setData(0, "Ni1_Fe1");
    rootItem->child(0)->setData(1, "");
    rootItem->child(0)->setData(2, "");

//    CResultTableChild *pChild1 = rootItem->child(0);
//    pChild1->insertChild(0);
//    pChild1->child(0)->setData(1, "bvdr");
//    pChild1->child(0)->setData(2, "");
//    pChild1->child(0)->setData(3, "");

//    CResultTableChild *pChild2 = pChild1->child(0);
//    pChild2->insertChild(0);
//    pChild2->child(0)->setData(2, "10");
//    pChild2->child(0)->setData(3, "20");
}
//---------------------------------------------------------------------------------------------------------//
CResultTableModel::~CResultTableModel()
{
    delete rootItem;
}
//---------------------------------------------------------------------------------------------------------//
int CResultTableModel::columnCount(const QModelIndex & /* parent */) const
{
    return 6;
}
//---------------------------------------------------------------------------------------------------------//
QVariant CResultTableModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || role != Qt::DisplayRole) return QVariant();

    CResultTableItem *item = getItem(index);

    return item->data(index.column());
}
//---------------------------------------------------------------------------------------------------------//
Qt::ItemFlags CResultTableModel::flags(const QModelIndex &index) const
{
    if (!index.isValid()) return 0;
    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
//---------------------------------------------------------------------------------------------------------//
//CResultTableItem *CResultTableModel::getItem(const QModelIndex &index) const
//{
///    if (index.isValid())
//    {
//        CResultTableItem *item = static_cast<CResultTableItem*>(index.internalPointer());
  //      if (item) return item;
//    }
//    return rootItem;
//}
//---------------------------------------------------------------------------------------------------------//
QVariant CResultTableModel::headerData(int nSection, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
    {
        switch (nSection)
        {
        case 0: return "Analitical pair";
        case 1: return "Spectr name";
        case 2: return "Int A";
        case 3: return "Int C";
        case 4: return "Int rel";
        case 5: return "C, %";;
        }
    }
    return QVariant();
}
//---------------------------------------------------------------------------------------------------------//
bool CResultTableModel::insertRows(int position, int /*rows*/, const QModelIndex &parent)
{
//    CResultTableItem *parentItem = getItem(parent);
//    bool success;

//    beginInsertRows(parent, position, position);
//    success = parentItem->insertChild(position);
//    endInsertRows();

   // return success;
}
//---------------------------------------------------------------------------------------------------------//
QModelIndex CResultTableModel::index(int row, int column, const QModelIndex &parent) const
{
//    if (parent.isValid() && parent.column() != 0)  return QModelIndex();

//    CResultTableParent *parentItem = getItem(parent);

    //CResultTableChild *childItem = parentItem->child(row);
    //if (childItem) return createIndex(row, column, childItem);

    //return QModelIndex();
}
//---------------------------------------------------------------------------------------------------------//
QModelIndex CResultTableModel::parent(const QModelIndex &index) const
{
//    if (!index.isValid()) return QModelIndex();

//    CResultTableItem *childItem = getItem(index);
//    CResultTableParent *parentItem = childItem->parent();

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

//   return createIndex(parentItem->childNumber(), 0, parentItem);
}
//---------------------------------------------------------------------------------------------------------//
int CResultTableModel::rowCount(const QModelIndex &parent) const
{
    //CResultTableItem *parentItem = getItem(parent);

   // return parentItem->childCount();
}
//---------------------------------------------------------------------------------------------------------//
bool CResultTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    //if (role != Qt::DisplayRole) return false;

    //CResultTableItem *item = getItem(index);
    //return item->setData(index.column(), value);
}
//---------------------------------------------------------------------------------------------------------//

Вобщем, вообще ничего не понятно, кроме функций columnCount, flags, headerData....

Я уже начал сомневаться, можно ли построить иерархическое дерево через модель?

Их примеры (edit tree model) можно адаптировать конечно... Т.е. вместо списка QVariant сделать 6 переменных - на 6 столбцов. Но тогда будет лишнее дублирование данных. Потому что, например, строка 1 таблицы-дерева должна содержать только одно имя (Имя_1) - в 1-м столбце. Строка 2 - должна содеражть только Имя_2 - другую переменную (во 2-м столбце). Соответсвенно остальные столбцы - (4 штуки) - переменные - должны быть пустыми... А память под них во-первых выделяется, а во-вторых это только запутывает, т.к. они не используются... Короче бред какой-то..... В случае модели таблицы - списка все полностью понятно... А тут какая-то фигня... (

Еще более непонятно, что писать в функции insertRows(...).
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
kuzulis
  опции профиля:
сообщение 14.12.2010, 15:36
Сообщение #7


Активный участник
***

Группа: Участник
Сообщений: 393
Регистрация: 29.6.2009
Пользователь №: 862

Спасибо сказали: 36 раз(а)




Репутация:   7  


Я тож долго соображал (до сих пор соображаю еще).
Вот, может пригодится это обсуждение: http://www.prog.org.ru/topic_15908_0.html

Самым простым решением (что я надумал), является создание базового класса Итем, от которого будут наследоваться уже реальные итемы источника данных, которые идентифицируются по типу.

т.е. мысленно делим итемы нашей структуры данных на типы, например:
корневой итем rootItem,
итем 1-го уровня вложенности oneLevelItem,
итем 2-го уровня вложенности twoLevelItem,
....
итем n-го уровня вложенности nLevelItem,

т.е. базовый класс имеет вид:
class BaseNode
{
public:
    enum NodeType {
        RootNodeType,
        OneLevelType,
        TwoLevelType,
    };

    BaseNode(BaseNode *parent = 0);
    virtual ~BaseNode();

    // Control Interface
    void appendChild(BaseNode *child);
    void removeChild(BaseNode *child);
    void removeAllChild();

    //
    BaseNode *child(int row);
    int childCount() const;
    virtual int columnCount() const = 0;
    virtual QVariant data(int column) const = 0;
    int row() const;
    BaseNode *parent();
    NodeType type() const;

protected:

    QList<BaseNode *> childNodes;
    NodeType nodeType;
private:
    BaseNode *parentNode;
};


т.е. всё аналогично тому, что в примерах с TreeItem, но! с одним условием, что добавили enum NodeType (тип итема/узла),
плюс сделали методы:
    virtual int columnCount() const = 0;
    virtual QVariant data(int column) const = 0;

чисто абстрактными (т.е. реализовывать их будем в других классах-наследниках от базового),
плюс убрали список QList<QVariant> с данными итема, т.к. каждый тип итема будет содержать разные типы/кол-во данных (столбцов).

Теперь реализуем те классы-итемы, типы которых у нас будут в нашей структуре данных, пример:

корневой узел.
class RootNode : public BaseNode
{
public:
    class RootData
    {
    public:
        QString title;
        QString summary;
    };
    RootNode(BaseNode *parent = 0);
    void setRootData(const RootData &data);

    // From BaseNode
    int columnCount() const;
    QVariant data(int column) const;

private:
    RootData data;
};

------------------------------------------------
RootNode::RootNode(BaseNode *parent)
        : BaseNode(parent)
{
    this->nodeType = BaseNode::RootNodeType;

    RootNode::RootData rd;
    rd.title = "Title";
    rd.summary = "Summary";
    this->setRootData(rd);
}

void RootNode::setRootData(const RootData &data)
{
    this->data= data;
}

// From BaseNode
int RootNode::columnCount() const
{
    return 2;
}

QVariant RootNode::data(int column) const
{
    switch (column) {
    case 0: return QVariant(this->data.title);
    case 1: return QVariant(this->data.summary);
    default:;
    }
    return QVariant();
}

Видим, что корневой узел имеет данные типа RootData, которые содержат всего два столбца title и summary, т.е. это то что будет отображено в TreeView как заголовки (header). При этом, знаем, что сам корневой узел не отображается в TreeView !!!

Далее, реализуем другой итем 1-го уровня:
class OneLevelNode : public BaseNode
{
public:
    class OneLevelData
    {
    public:
        int id;
        QString name;
        QString description;
    };
    OneLevelNode (const QString &name, BaseNode *parent = 0);
    void setOneLevelData(const OneLevelData &data);

    // From BaseNode
    int columnCount() const;
    QVariant data(int column) const;

private:
    OneLevelData data;
};

------------------------------
OneLevelNode::OneLevelNode(const QString &name, BaseNode *parent)
        : BaseNode(parent)
{
    this->nodeType = BaseNode::OneLevelNodeType;
    this->data.name = name;
}

void OneLevelNode::setOneLevelDataData(const OneLevelData &data)
{
    this->options = data;
}

// From BaseNode
int OneLevelNode::columnCount() const
{
    return 3;
}

QVariant OneLevelNode::data(int column) const
{
    switch (column) {
    case 0: return QVariant(this->data.id);
    case 1: return QVariant(this->data.name);
    case 2: return QVariant(this->data.description);
    default:;
    }
    return QVariant();
}


Как видим, этот класс содержит данные OneLevelData из трёх полей.
Далее, реализуем все остальные классы-итемы, которые у нас будут в дереве.

Теперь, в модели делаем так:
class CoreTreeModel : public QAbstractItemModel
{
    Q_OBJECT

public:
    CoreTreeModel(RootNode *rootNode, QObject *parent = 0);
    ~CoreTreeModel();

    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;

private:
    RootNode *rootNode;
};


При этом, считаем, что дерево данных rootNode полученное в модель уже существует (создано и заполнено данными/итемами заранее).
(Считаем так для простоты).

Теперь, нужно переопределить виртуальные методы модели так, чтобы она "рисовала" нам итемы данных так как мы хотим:
....
QVariant CoreTreeModel::data(const QModelIndex &index, int role) const
{
    if (this->rootNode && index.isValid() && (Qt::DisplayRole == role)) {
        const BaseNode *node = static_cast<BaseNode *>(index.internalPointer());
        if (node) {
            int column = 0;
            switch (node->type()) {
            case BaseNode::ProjectNodeType:
                column = index.column();
                if (0 == column)
                    return node->data(++column);
            default:;
            }
        }
    }
    return QVariant();
}
....


т.е. вся фишка в
const BaseNode *node = static_cast<BaseNode *>(index.internalPointer());
...
switch (node->type()) {
...
}
...


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

И по аналогии переопределяем другие методы модели!

Надеюсь, суть ясна? ;)

ЗЫ: старался донести как мог идею (один из вариантов решения проблем похожего рода).



Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
wiz29
  опции профиля:
сообщение 14.12.2010, 15:46
Сообщение #8


Старейший участник
****

Группа: Участник
Сообщений: 600
Регистрация: 7.7.2010
Из: Санкт-Петербург
Пользователь №: 1866

Спасибо сказали: 94 раз(а)




Репутация:   12  


по поводу рисования смотри в сторону QItemDelegate
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
Rocky
  опции профиля:
сообщение 14.12.2010, 16:06
Сообщение #9


Старейший участник
****

Группа: Участник
Сообщений: 530
Регистрация: 22.12.2008
Из: Санкт-Петербург
Пользователь №: 463

Спасибо сказали: 22 раз(а)




Репутация:   7  


kuzulis, все, идею понял, супер!... Только не мог бы ты подсказать насчет кода функций index/parent/rowCount плиз?)
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
kuzulis
  опции профиля:
сообщение 14.12.2010, 16:15
Сообщение #10


Активный участник
***

Группа: Участник
Сообщений: 393
Регистрация: 29.6.2009
Пользователь №: 862

Спасибо сказали: 36 раз(а)




Репутация:   7  


Цитата(Rocky @ 14.12.2010, 16:06) *
kuzulis, все, идею понял, супер!... Только не мог бы ты подсказать насчет кода функций index/parent/rowCount плиз?)


Вот, думаю разберешься:
QModelIndex CoreTreeModel::index(int row, int column, const QModelIndex &parent) const
{
    if (this->rootNode && this->hasIndex(row, column, parent)) {
        BaseNode *parentNode = (parent.isValid()) ?
                               (static_cast<BaseNode *>(parent.internalPointer())) : (this->rootNode);
        if (parentNode) {
            BaseNode *childNode = parentNode->child(row);
            if (childNode)
                return createIndex(row, column, childNode);
        }
    }
    return QModelIndex();
}

QModelIndex CoreTreeModel::parent(const QModelIndex &index) const
{
    if (this->rootNode && index.isValid()) {
        BaseNode *childNode = static_cast<BaseNode *>(index.internalPointer());
        if (childNode) {
            BaseNode *parentNode = childNode->parent();
            if (parentNode != this->rootNode)
                return this->createIndex(parentNode->row(), 0, parentNode);
        }
    }
    return QModelIndex();
}

int CoreTreeModel::rowCount(const QModelIndex &parent) const
{
    if (this->rootNode && (parent.column() <= 0)) {
        BaseNode *parentNode = (parent.isValid()) ?
                           (static_cast<BaseNode *>(parent.internalPointer())) : (this->rootNode);
        return parentNode->childCount();
    }
    return 0;
}


Как то так
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение

Быстрый ответОтветить в данную темуНачать новую тему
Теги
Нет тегов для показа


4 чел. читают эту тему (гостей: 4, скрытых пользователей: 0)
Пользователей: 0




RSS Текстовая версия Сейчас: 25.11.2024, 3:19