Feb. 3, 2020, 5:47 a.m.

QTableView и список значений в ячейке

QTableView, QList, QVector

Всем доброго времени суток!

Подскажите, пожалуйста, как лучше сделать:
1. Имеется модель данных, посторенная с помощью QAbstractTableView. Сама таблица отражает объект в плоском виде.
2. В некоторых колонках нужно отобразить список значений, потому что в самом объекте присутствуют QList/QVector, которые в свою очередь тоже содержат некоторые свойства.

Вопрос у меня возникает следующий, как сделать так, чтобы можно было записывать в модель данных объект, который имеет в своем составе массив, как свойство. Если бы это была простая БД, то особых проблем бы не было, я просто нормализовал бы данные, связал связями и получил обычный Select через первичные и внешние ключи.
Но тут у меня объект программный и для него нужна модель данных. Модель я построил. Но вопрос остался с ячейками, где должны храниться массивы. Как тут быть?

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.
10

Добрый день,
Покажите модель данных и класс объекта, который отвечает за репрезентацию данных. Обычно достаточно иметь поле вектора, а в модель превращать список/вектор в строку требуемых значений и отдавать его для Display роли.

Вот это модель данных:

QVariant FeedThruTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role != Qt::DisplayRole){
        return QVariant();
    }

    if(orientation == Qt::Vertical){
        return section + 1;
    }
    switch(section){
    case HOLEIDENT:
        return tr("Hole Identification");
    case FUNCTIONALITEMNUMBER:
        return tr("Functional Item Number Identifier");
    case FUNCTIONALITEMTYPE:
        return tr("Functional Item Type");
    case IDFUNCTIONALITEMREF:
        return tr("Identifier");
    case INSTALLATIONIDENT:
        return tr("Installation Identifier");
    case APPLICREFID:
        return tr("Applicability Information");
    case CHANGETYPE:
        return tr("Change Type");
    case CHANGEMARK:
        return tr("Change Mark");
    case REASONFORUPDATEREFIDS:
        return tr("Reason For Update Ref Ids");
    case SECURITYCLASSIFICATION:
        return tr("Security Classification");
    case COMMERCIALCLASSIFICATION:
        return tr("Commercial Classification");
    case CAVEAT:
        return tr("Caveat");
    case AUTHORITYNAME:
        return tr("Authority Name");
    case AUTHORITYDOCUMENT:
        return tr("Authority Document");
    case CONTEXTIDENT:
        return tr("Context Identification");
    case MANUFACTURERCODEVALUE:
        return tr("CAGE Code");
    case ITEMORIGINATOR:
        return tr("Functional Item Originator");
    case NAMEVALUE:
        return tr("Functional Item Name");
    case CHANGETYPENAME:
        return tr("Change Type");
    case CHANGEMARKNAME:
        return tr("Change Mark");
    case REASONFORUPDATEREFIDSNAME:
        return tr("Reason For Update Ref Ids");
    case SHORTNAMEVALUE:
        return tr("Alternate Name");
    case DMREF: // Вот это поле объявлено как QVector<dmRef> *vectorDmRef
        return tr("References to data modules");
    case PMREF: // Вот это поле объявлено как QVector<pmRef> *vectorPmRef
        return tr("References to publications");
    case EXTERNALPUBREF: // Вот это поле как QVector<externalPubRef> *vectorExternalPubRef
        return tr("References to external documents");
    }
    return QVariant();
}

Сам объект описан вот так:

    // Attributes of the tag <funcitonalItemRef>
    QMap<QString, QString> *functionalItemRefAttrMap = nullptr;
    // Tag <name>
    Name *nameObject = nullptr;
    // Tag <shortName>
    QString shortNameValue;
    // Tag <refs>
    Refs *refsObject = nullptr;

Объект Refs:

    // The tag <dmRef>
    QVector<DmRef> *dmRefVector = nullptr;
    // The tag <pmRef>
    // Здесь пока ничего нет, но по аналогии с dmRef
    // The tag <externalPubRef>
    // Здесь пока ничего нет, но по аналогии с dmRef

Или я что-то неправильно понял?

Немного не догоняю, вот этот вектор dmRefVector должен как-то мапиться во все эти enum?

Может тогда лучше QMap ? А потом уже возвращать и устанавливать данные в зависимости от этого enum?

Евгений, добрый день.

Я планировал для элемента dmRef получать агрегатное значение и его отображать. Иными словами в модели будет храниться его подготовленное значение. Если представлять его в каком-то виде, то я предполагал взять QListView для этих целей. А зачем QMap ?
Но, сейчас у меня возник еще один вопрос, а как эти данные готовить. С однозначными(атомарными) значениями все понятно, а тут? Я могу показать весь класс для этой модели, если нужно.

Евгений, у меня с обычными таблицами проблем вообще никаких не возникло, разобрался как это работает и в путь. Но тут получается, что объект, который я кладу в модель данных - он составной и в его составе могут быть другие обеъкты и/или массивы/списки/словари. А вот как в этом случае строить модель данных для меня не совсем понятно.
Я тут поразмыслил и мне пришла в голову мысль, что нужно как-то модернизировать эти объекты-"матрешки". Например, вместо объекта, который входит в состав хранить не сам объект, а его идентификатор-ключ. В объекте, который будет включаться ввести дополнительное свойство, идентификатор. Вот только придется следить за уникальностью этих идентификаторов программно при создании.
Вот только одного я не учел. В файл мне нельзя класть эти идентификаторы иначе я не пройду валидацию. Как вариант придется ломать формат и вводить комментарии или элементы, которые не влияют на валидацию.

Класс объекта, который вы храните в модели должен иметь нужные методы для возврата и установки значений, а логику по возврату значений из "составных" кусков инкапсулируйте внутрь класса. И там уже внутри разбиайтесь что там есть, а чего нет. И в зависимости от типа данных и составных кусков сможете возвращать информацию в удобовармом для модели виде.

Для управления уникальными указателями попробуйте использовать shared_ptr

Евгений, спасибо.
Буду пробовать.

Евгений, добрый день.

Я понял о чем Вы меня спрашивали, когда речь шла про вектор. Нет, этот вектор должен отображаться только в одну ячейку. Получается, что с точки зрения БД - мое значение не атомарно, а представляет собой набор значений, в свою очередь эти значения представляют собой QMap.

Решил пойти сначала. Создал тестовый проект.
Тестовая форма
Здесь отображаются данные в верхней части, это данные основного объекта, а в нижней части отображаются данные объекта, который входит в состав основного объекта. Этим объектом является QVector<составной_объект>.
Классы, которые описывают основной и составной объект достаточно просты:
Составной объект class Contact и его описание

#ifndef CONTACT_H
#define CONTACT_H

#include <QMap>

class Contact
{
public:
    Contact();
    ~Contact(){}

    QMap<QString, QString> *getMap() const;
    void setMap(QMap<QString, QString> *value);
private:
    QMap<QString, QString> *map = nullptr;
};

#endif // CONTACT_H
#include "contact.h"
Contact::Contact() {}

QMap<QString, QString> *Contact::getMap() const {return map;}

void Contact::setMap(QMap<QString, QString> *value) {map = value;}

Основной объект class ConnectionList и его описание

#ifndef CONNECTIONLIST_H
#define CONNECTIONLIST_H

#include "contact.h"

class ConnectionList
{
public:
    ConnectionList();
    ~ConnectionList();

    QString getAttrValue() const;
    void setAttrValue(const QString &value);

    QVector<Contact> *getContactList() const;
    void setContactList(QVector<Contact> *value);

private:
    QString attrValue;
    QVector<Contact> *contactList = nullptr;
};

#endif // CONNECTIONLIST_H
#include "connectionlist.h"

ConnectionList::ConnectionList(){}

QString ConnectionList::getAttrValue() const {return attrValue;}

void ConnectionList::setAttrValue(const QString &value) {attrValue = value;}

QVector<Contact> *ConnectionList::getContactList() const {return contactList;}

void ConnectionList::setContactList(QVector<Contact> *value) {contactList = value;}

Модель данных для отображения таблицы "Contact List" и описание:

#ifndef CONTACTMODEL_H
#define CONTACTMODEL_H

#include "contact.h"

#include <QAbstractTableModel>

class ContactModel : public QAbstractTableModel
{
    Q_OBJECT

public:
    ContactModel(QObject *parent = nullptr);

    void appendContact(Contact *item);
    void resetModel();
    QVector<Contact> getContactList();

private:
    enum Cols{
        CONTACTIDENT = 0,
        CONTACTFUNCTION,
        CONTACTTYPE,
        CONNECTEDFLAG,
        CONTACTPARTNUMBER,
        WIREINSTALLATION,
        COLUMNCOUNT
    };

    typedef QHash<Cols, QVariant> ContactData;
    typedef QList<ContactData> ContactGroup;
    ContactGroup m_contactGroup;


    // QAbstractItemModel interface
public:
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;

    // QAbstractItemModel interface
public:
    QVariant headerData(int section, Qt::Orientation orientation, int role) const;

    // QAbstractItemModel interface
public:
    Qt::ItemFlags flags(const QModelIndex &index) const;
};

#endif // CONTACTMODEL_H

#include "contactmodel.h"

ContactModel::ContactModel(QObject *parent)
    : QAbstractTableModel(parent)
{
}

QVariant ContactModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role != Qt::DisplayRole){
        return QVariant();
    }

    if(orientation == Qt::Vertical){
        return section + 1;
    }
    switch(section){
    case CONTACTIDENT:
        return tr("CONTACTIDENT");
    case CONTACTFUNCTION:
        return tr("CONTACTFUNCTION");
    case CONTACTTYPE:
        return tr("CONTACTTYPE");
    case CONNECTEDFLAG:
        return tr("CONNECTEDFLAG");
    case CONTACTPARTNUMBER:
        return tr("CONTACTPARTNUMBER");
    case WIREINSTALLATION:
        return tr("WIREINSTALLATION");
    }
    return QVariant();
}

int ContactModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return m_contactGroup.count();

}

int ContactModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return COLUMNCOUNT;
}


void ContactModel::appendContact(Contact *item)
{    ContactData contactItem;
     if(item->getMap()->contains("1")){
         contactItem[CONTACTIDENT] = item->getMap()->value(item->getMap()->key("1"));
     }
     if(item->getMap()->contains("2")){
         contactItem[CONTACTFUNCTION] = item->getMap()->value(item->getMap()->key("2"));
     }
     if(item->getMap()->contains("3")){
         contactItem[CONTACTTYPE] = item->getMap()->value(item->getMap()->key("3"));
     }
     if(item->getMap()->contains("4")){
         contactItem[CONNECTEDFLAG] = item->getMap()->value(item->getMap()->key("4"));
     }
     if(item->getMap()->contains("5")){
         contactItem[CONTACTPARTNUMBER] = item->getMap()->value(item->getMap()->key("5"));
     }
     if(item->getMap()->contains("6")){
         contactItem[WIREINSTALLATION] = item->getMap()->value(item->getMap()->key("6"));
     }
     int row = m_contactGroup.count();
     beginInsertRows(QModelIndex(), row, row);
     m_contactGroup.append(contactItem);
     endInsertRows();
}


Qt::ItemFlags ContactModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags flags = QAbstractTableModel::flags(index);
    if(index.isValid()){
        flags |= Qt::ItemIsEditable;
    }

    return flags;
}


QVariant ContactModel::data(const QModelIndex &index, int role) const
{
    if(!index.isValid() ||
            m_contactGroup.count() <= index.row() ||
            (role != Qt::DisplayRole && role != Qt::EditRole)){
        return QVariant();
    }
    return m_contactGroup[index.row()][Cols(index.column())];
}

Модель данных для отображения верхней таблицы:

#ifndef CONNECTIONLISTMODEL_H
#define CONNECTIONLISTMODEL_H

#include "connectionlist.h"

#include <QAbstractTableModel>
#include <QObject>

class ConnectionListModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    ConnectionListModel(QObject *parent = nullptr);

    void appendConnection(ConnectionList *item);
    void resetModel();
    QVector<ConnectionList> getConnectionList();

private:
    enum Columns{
        CONNECTION = 0,
        CONTACTLIST,
        COLUMNCOUNT
    };

    typedef QHash<Columns, QVariant> ConnectionData;
    typedef  QMap<QString, QVariant> ContactData;
    typedef  QVariantList ContactList;
    typedef QList<ConnectionData> ConnectionGroup;
    ConnectionGroup m_connectionGroup;
    ContactData m_contactMap;
    ContactList m_contactList;

    // QAbstractItemModel interface
public:
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const;
    Qt::ItemFlags flags(const QModelIndex &index) const;
};

#endif // CONNECTIONLISTMODEL_H

#include "connectionlistmodel.h"

ConnectionListModel::ConnectionListModel(QObject *parent)
    : QAbstractTableModel(parent)
{

}

void ConnectionListModel::appendConnection(ConnectionList *item)
{
    ConnectionData connectionItem;
    if(!item->getAttrValue().isEmpty()){
        connectionItem[CONNECTION] = item->getAttrValue();
    }
    if(item->getContactList() != nullptr){
        m_contactMap.insert(item->getContactList()->at(0).getMap()->key("1"), item->getContactList()->at(0).getMap()->value("1"));
        m_contactMap.insert(item->getContactList()->at(0).getMap()->key("2"), item->getContactList()->at(0).getMap()->value("2"));
        m_contactList.append(m_contactMap);
        m_contactList.append(m_contactMap);
        connectionItem[CONTACTLIST] = QVariant::fromValue(m_contactList);
    }

    int row = m_connectionGroup.count();
    beginInsertRows(QModelIndex(), row, row);
    m_connectionGroup.append(connectionItem);
    endInsertRows();

}


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

int ConnectionListModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return COLUMNCOUNT;
}

QVariant ConnectionListModel::data(const QModelIndex &index, int role) const
{
    if(!index.isValid() ||
            m_connectionGroup.count() <= index.row() ||
            (role != Qt::DisplayRole && role != Qt::EditRole)){
        return QVariant();
    }
    return m_connectionGroup[index.row()][Columns(index.column())];
}

QVariant ConnectionListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role != Qt::DisplayRole){
        return QVariant();
    }

    if(orientation == Qt::Vertical){
        return section + 1;
    }
    switch(section){
    case CONNECTION:
        return tr("CONNECTION");
    case CONTACTLIST:
        return tr("CONTACTLIST");
    }
    return QVariant();
}

Qt::ItemFlags ConnectionListModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags flags = QAbstractTableModel::flags(index);
    if(index.isValid()){
        flags |= Qt::ItemIsEditable;
    }

    return flags;
}

Логика работы формы:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include "contactmodel.h"
#include "connectionlist.h"
#include "connectionlistmodel.h"

#include <QMainWindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    ContactModel *_cModel = nullptr;
    ConnectionListModel *_connListModel = nullptr;

};

#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    _cModel = new ContactModel();
    ui->contactTable->setModel(_cModel);
    ui->contactTable->horizontalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents);

    _connListModel = new ConnectionListModel();
    ui->connectionTable->setModel(_connListModel);
    ui->connectionTable->horizontalHeader()->resizeSections(QHeaderView::ResizeMode::ResizeToContents);

    Contact *cont1 = new Contact();
    Contact *cont2 = new Contact();
    ConnectionList *newConnectionList = new ConnectionList();

    auto attrMap = new QMap<QString, QString>();
    attrMap->insert("1", "1");
    attrMap->insert("2", "2");
    attrMap->insert("3", "3");
    attrMap->insert("4", "4");
    attrMap->insert("5", "5");
    attrMap->insert("6", "6");
    cont1->setMap(attrMap);
    cont2->setMap(attrMap);
    _cModel->appendContact(cont1);
    _cModel->appendContact(cont2);

    newConnectionList->setAttrValue("direct");
    QVector<Contact> *contactList = new QVector<Contact>();
    contactList->append(*cont1);
    contactList->append(*cont2);
    newConnectionList->setContactList(contactList);
    _connListModel->appendConnection(newConnectionList);

}

MainWindow::~MainWindow()
{
    delete ui;
}

Так вот при запуске проекта я получаю список в объекте m_connectionGroup в ячейке CONTACTLIST. Но данные этой ячейки не отображаются в таблице.
В принципе я добился того, что положил данные объекта в модель данных, но теперь я не понимаю:
1. Как отобразить эти данные в ячейку?
2. Если отобразить данные в ячейку не получится, то как связать верхнюю таблицу с нижней? С тем расчетом, что двигаясь по строкам верхней таблицы менялись значения в нижней.
3. Как в этом случае редактировать(добавлять/удалять) данные из модели?
4. Думал про делегаты, типа в ячейку добавить кнопку, которая бы вызывала дочернюю форму, а там отображались бы данные из списка. Но не знаю, правильное ли решение это будет? Да и с делегатами пока еще не работал.

Еще раз оговорюсь, что с простыми таблицами вроде бы все понятно. Я вроде бы разобрался, как это работает, а вот с таког рода объектами... Темный лес в общем.

Файл проекта приложил. Спасибо всем, кто поможет разобраться в такой задачке.
TestTableView.7z TestTableView.7z

Итак, продолжаю вести что-то вроде рассказа о том, как и что у меня получается. В общем решил задачу с отоображением данных в основной и дочерней таблице. Если в крадце, то я сохранил в ячейку список QVariantList внутрь которого положил QMap . Эту колонку, которая содержит список элементов я не отображаю, но теперь я могу легко взять этот список элементов. Дальше на выбор сторки в повесил построение дочерней модели.
Как все сделаю, то напишу итоговый результат.
Просто я смотрю, много информации по работе с таблицами из БД, а чтобы это делалось программно такой информации практически нет.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Donate

Hello, Dear Users of EVILEG!!!

If the site helped you, then support the development of the site financially, please.

You can do it by following ways:

Thank you, Evgenii Legotckoi

a
Feb. 25, 2020, 5:40 a.m.
ayb

C++ - Test 005. Structures and Classes

  • Result:83points,
  • Rating points4
DZ
Feb. 24, 2020, 2:47 p.m.
Dmitrij Zlobin

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:40points,
  • Rating points-8
p
Feb. 17, 2020, 2:41 p.m.
pstMem

C++ - Тест 003. Условия и циклы

  • Result:85points,
  • Rating points6
Last comments
Feb. 24, 2020, 3:54 a.m.
Evgenij Legotskoj

Добрый день. Там будет url, на который указывает ссылка тега a в пагинаторе, если правильно помню )) Написал этот код и забыл.
B
Feb. 24, 2020, 12:37 a.m.
BahaMeirman

Евгений Здравствуйте! Не могу понять вот эту часть кода: url: jQuery(this).attr('action') наверное здесь должен быть путь к url, тогда 'action' на какой url указывает?
Feb. 17, 2020, 3:22 a.m.
Evgenij Legotskoj

Добрый день. Это кастомный тег, помещается в файл, который находится в каталоге templatetags myapp/ templatetags/ myapp.py
B
Feb. 16, 2020, 1:36 p.m.
BahaMeirman

Добрый вечер! Монжно по подробней о теге get_companion? ссылка не работает.
Now discuss on the forum
a
Feb. 25, 2020, 8:06 a.m.
ayb

Да
Feb. 24, 2020, 12:04 p.m.
Evgenij Legotskoj

Добрый день. Вот пример с дружественного ресурса с движением камеры
Feb. 24, 2020, 6:29 a.m.
Evgenij Legotskoj

Qt не предоставляет функционала по запросу root прав во время выполнения программы. Поэтому нужно использовать платформозависимый функционал, для Linux это будет выглядеть так: #include…
Feb. 24, 2020, 3:47 a.m.
Evgenij Legotskoj

Добрый день. Что означает клиентская область? Это изображение? Вам нужно распознавать символы текста или всё-таки пользователь будет вводить текст с клавиатуры, просто в любом месте "абстр…
VZ
Feb. 21, 2020, 5:19 a.m.
Vladimir Zhitkovsky

void sendImage(int sessid, int type, int dest, int format, QString imgBase64Data){ QNetworkRequest request; request.setUrl(QUrl(ipAddress + "ctlapi/cmd=preparereportimg&sessid=…
EVILEG
About
Services
© EVILEG 2015-2019
Recommend hosting TIMEWEB