QTableView, QStandardItemModel and underlying data in a QList



  • Hi Qt fans,

    I am new to QT. I want to use my own objects in my model, and present them in a QTableView. But I cannot find out how to connect underlying data with my model / view (especially vice versa). Maybe I am not that far away, but after weeks of reading about model/view-things, reading here and Stackoverflow, I am totally confused. So I dare to present my solution here, and be curious about yours.

    I am using QStandardItem model, and I want at first an easy solution. If possible, no QAbstractItemModel, reimplementing or so.
    I have objects of my struct "person" stored in a QList. I just want that a person (and its attributes) is represented by a row in that table. Even when you sort all person by an attribute, and then you click on that row, you get data back from the underyling object in the Qlist. Not only item data, I want to get back whole object, or data that is not shown in tableview!
    Persons are organised in an object of class "club". Club is a widget containing my QTableView. A person has three members: an ID, name and family name.

    Here is my struct person:

    // person.h
    struct person 
    {
        int id;
        QString name;
        QString family;
    }
    

    Here is my club:

    //club.cpp
    
    // here is my external list for persons (because needed by other classes)
    QList<person> list_of_people;
    
    // Constructor
    Club::Club(QWidget *parent) : QWidget(parent), uiclub(new Ui::Club)
    {
        club_model = new QStandardItemModel();
        uiclub->tableView(setModel(club_model);
        QStringList header;
        header << "ID" << "Name" << "Family Name";
        club_model->setHorizontalHeaderLabels(header);
    }
    
    // function to create a person and add to list_of_people
    void Club::create_person_from_lineEdits()
    {
       Person &newPerson = *(new Person());
       // fill data from LineEdits...
        newPerson.id = list_of_people.count();
        newPerson.name = uiclub->lineEdit1.text;
        {...}
        list_of_people.append(newPerson);
    }
    
    // Create Items from objects and append as a row to model
    void Club::show_person_in_tableView(Person &obj)
    {
        QStandardItem *item_id = new QStandardItem(obj.id);
        QStandardItem *item_name = new QStandardItem(obj.name);
        QStandardItem *item_family = new QStandardItem(obj.family);
        QList<QStandardItem*> items;
        items.push_back(item_id);
        items.push_back(item_name);
        items.push_back(item_family);
        club_model->appendRow(items);
    }
    // Click, enter or whatever on an item in qtableview
    void Club::on_tableView_activated(const QModelIndex &index)
    {
        qDebug() << "user clicks on index row" << index.row();
        do-something_with_person(const_cast<Person&>(list_of_people.at(index.row())));
    }
    

    Here you see the problem: index qtableview is only the same as index of list_of_people when I do not change sorting!
    But of course I want to sort, and I want to get back the underlying object via ID or so. Here in this example ID is an item, but what if not? Is it possible to include my QList directly into my model?
    Can I solve this with my comfortable QStandardItemModel? Do I have to use my own Abstract model? Do I have to use a ProxyModel? QObject_META? Didn't understand these latter solutions, to be honest :)

    I greatly appreciate your help! If you need something more, please let me know.

    Regards
    Gerhard

    PS: why struct? because my persons just represent raw data, and have no functions at all. Why qlist and not qvector? I changed that because I need access in the middle of my list_of_people with thousand persons, and somebody in internet said lists are better for that.


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Either put everything in your QStandardItemModel and share it with other classes that need to access the information contained in it or implement a QAbstractTableModel that will return your struct elements per column.

    The rowCount implementation should return the size of your list.
    The columnCount implementation should return the number of members of your struct.



  • @SGaist Thank you for your quick reply!

    But if I put everything in my QStandarditemModel, how can I change my original instance of person in my Qlist: "list_of_people"?
    E.g. if I change an item (name) via my QTableView, how can I route it further to my original object? How can I connect my data with my model?


  • Lifetime Qt Champion

    That's why I wrote "put everything in your QStandardItemModel" and you would only use that as data source.

    Where else do you use your data structure ?

    On a side note, as static variable for that is not a good idea.



  • @SGaist: Thank you for your answer, I want to be more precise:

    Imagine that I have Qlist as my data source, with 100 Persons.
    I have two different qTableViews, first with all persons, and second with persons user can decide. Thatswhy I thought that it is good idea tio have one source of truth: my Qlist. First qtable reads all persons in. You can mark a person, and then (reference) of that object is transfered to that second qtableview. Addiotioonally to that, there will be more and dynamic amount of qtableviews for that. That approach would effectively reduce my amount of memory.
    I have to think about your approach, if it is possible. I don't know yet.
    I have an external variable there, not static variable. Reason is: I have several widget classes, and I want to share that qlist with all of them by their functions. Do you have a better approach? I tried to make it "friend" once, and use a free function, but due to an error I couldn't handle that. As a noob there are so many things to learn, and sources of errors are endless :)


  • Lifetime Qt Champion

    Hi
    To fix the sorting issues, you could use
    https://doc.qt.io/qt-5/qstandarditem.html#setData
    and put the Index of the person to a UserRole so that
    you can use that to look up the person it represents.
    However, that is only valid if you don't add or remove persons
    while the view is active.

    However, im not sure that a QStandardItem model will make you very happy in the long run if you
    plan to allow the user to edit a Person and need to write back the changed data to the
    Persons list. For that, a QAbstractTableModel would be much more smooth.
    You could reuse the model from
    https://doc.qt.io/qt-5/qtwidgets-itemviews-addressbook-example.html
    as its very close to what you have ( Contact vs Person)



  • Hi!

    Thank you very much for your help. I thought and I want to make little steps at first, so I changed my struct:

    // person.h
    struct person 
    {
        int id;
        QStandardItem* name;
        QStandardItem* family;
    }
    

    Now I can directly use my members and append them.
    I do not need write access, but to show my objects directly in QAbstractTableModel is indeed a sexy idea. I am trying around with QModelIndex, club_model->item(), club_model->itemData() and roles, and when I am done with that, maybe I'll try my own model.

    For now, my question is solved!

    Regards Gerhard



  • How you done can you share full code ?



  • The code in the starting post almost works, you just have to let QSortFilterProxyModel do the sorting.

    Club::Club(QWidget *parent) : QWidget(parent), uiclub(new Ui::Club)
    {
        club_model = new QStandardItemModel(this);
        sort_proxy = new QSortFilterProxyModel(this);
        sort_proxy->setSourceModel(club_model);
        uiclub->tableView(setModel(sort_proxy);
        QStringList header;
        header << "ID" << "Name" << "Family Name";
        club_model->setHorizontalHeaderLabels(header);
    }
    
    void Club::on_tableView_activated(const QModelIndex &index)
    {
        const int mappedRow = sort_proxy->mapToSource(index).row();
        qDebug() << "user clicks on index row" << mappedRow ;
        do-something_with_person(const_cast<Person&>(list_of_people.at(mappedRow)));
    }
    


  • @VRonin Qt::ItemFlags GAS_tool::flags(const QModelIndex& index) const
    {
    //table 1 column 9 and 10 as noneditable
    // QModelIndex index;

    if (index.column() == 9 || index.column() == 10)
        return  ~Qt::ItemIsEditable;
    else
        return  Qt::ItemIsEditable;
    

    }
    how should i make table 1 and table 2 column as non editable using flag?





  • @n-2204
    You can use @VRonin's setFlags() if you want to set the editability on desired, explicitly specified items.

    If you want to do it your way by overriding flags(), your code should have read:

    if (index.column() == 9 || index.column() == 10)
        return  QStandardItemModel::flags() & ~Qt::ItemIsEditable;
    else
        return  QStandardItemModel::flags() | Qt::ItemIsEditable;
    

    Your way would have switched off enablement and selectable.

    You will have to derive from QStandardItemModel() if you are wanting to override its flags() method. Similar if you choose to use QAbstractItemModel() instead.



  • @JonB said in QTableView, QStandardItemModel and underlying data in a QList:
    noneditable is class extracting Qstandarditemmodel
    Qt::ItemFlags nonedittablemodel::flags(const QModelIndex& index) const

    {

    //table 1
    
    if (index.column() == 0 || index.column() == 1)
    
        return  QStandardItemModel::flags(index) & ~Qt::ItemIsEditable;
    
    else
    
        return  QStandardItemModel::flags(index) | Qt::ItemIsEditable;
    

    }
    But still its not working for me
    and where i should specify which model because i have 3 model 3tableview



  • @n-2204 Qt::ItemFlags GAS_tool:: flags(const QModelIndex& index) const
    {
    //table 1
    QStandardItemModel model;
    if (index.column() == 0 || index.column() == 1)
    return model:: flags(index) & ~Qt::ItemIsEditable;
    else
    return flags(index) | Qt::ItemIsEditable;
    }@JonB where i should give like it will be applicable for table1 or table2



  • @n-2204
    Please try to format your posts readably. For lines of code use the Code tag when posting, or but lines of 3-backticks above & below.

    You are also now asking this same question in a new thread you have created (https://forum.qt.io/topic/125774/qtableview-how-to-make-noneditable-column). You should stick to one thread, not create multiple ones.

    where i should give like it will be applicable for table1 or table2

    I don't know what you mean. To use the flags() approach you must sub-class QStandardItemModel, which I assume is what your GAS_tool is, else this will have no effect.

    If you want to use the setFlags() approach, you do not need to sub-class, but must set the flags explicitly on each item which is to be non-editable.

    I also wrote this in your other thread.