Need some assistance with QStandardItemModel and multiple Views



  • I have a QStandardItemModel that represents website, account, and cookie information for that particular website. I need to use 2 separate views to display the data. In the left view is a simple QTreeView that shows 2 columns (website and account name). In the other view I need to show cookie information for the particular website/account that is selected in the left view. Both views don't require expanding of any items, so they are all top level items. Here is a diagram of what I'm trying to achieve:

    alt text

    I'm a bit confused on how this is supposed to work with a single model. My gut says I need to make my model 3 columns (Website, Account, Cookies), where Cookies is a QStandardItem with children (being the individual cookies). For the left view do I have to hide all those other columns with leftView.setColumnHidden()? How is setHorizontalHeaderLabels() supposed to work when using multiple views like this?

    I have a pretty good understanding of the Model View Framework in Qt when using a single view. But when mixing views like this, it's got me a bit confused.

    Any pointers in the right direction would be appreciated.

    Thanks.



  • Both views don't require expanding of any items

    Use QTableView Then

    My gut says I need to make my model 3 columns

    You'll need 7 columns (all those shown in your picture)
    the top level items will have just the first 2 columns populated, then each index(i,0,QModelIndex()) will have children with the last 7 columns populated.

    something like this:

    #include <QWidget>
    #include <QDate>
    #include <QStandardItemModel>
    #include <QTableView>
    #include <QHBoxLayout>
    #include <QHeaderView>
    #include <QTimer>
    class TestWidget : public QWidget
    {
        Q_OBJECT
        Q_DISABLE_COPY(TestWidget)
    public:
        enum ModelColumns{
            mcWebsite
            ,mcAccount
            ,mcCookieName
            ,mcCookieValue
            ,mcCookieDomain
            ,mcCookiePath
            ,mcCookieExpire
    
            ,ModelColCount
        };
        explicit TestWidget(QWidget *parent = Q_NULLPTR)
            :QWidget(parent)
        {
    
    
            model=new QStandardItemModel(this);
            model->insertColumns(0,ModelColCount);
            model->setHeaderData(mcWebsite,Qt::Horizontal,tr("Website"));
            model->setHeaderData(mcAccount,Qt::Horizontal,tr("Account"));
            model->setHeaderData(mcCookieName,Qt::Horizontal,tr("Cookie Name"));
            model->setHeaderData(mcCookieValue,Qt::Horizontal,tr("Cookie Value"));
            model->setHeaderData(mcCookieDomain,Qt::Horizontal,tr("Cookie Domain"));
            model->setHeaderData(mcCookiePath,Qt::Horizontal,tr("Cookie Path"));
            model->setHeaderData(mcCookieExpire,Qt::Horizontal,tr("Cookie Expiration"));
    
            leftView=new QTableView(this);
            leftView->horizontalHeader()->setStretchLastSection(true);
            leftView->setModel(model);
            leftView->setSelectionBehavior(QAbstractItemView::SelectRows);
            leftView->setSelectionMode(QAbstractItemView::SingleSelection);
            for(int i=0;i<ModelColCount;++i)
                leftView->setColumnHidden(i,i!=mcWebsite && i!=mcAccount);
            rightView=new QTableView(this);
            rightView->setModel(model);
            rightView->horizontalHeader()->setStretchLastSection(true);
            for(int i=0;i<ModelColCount;++i)
                rightView->setColumnHidden(i,i==mcWebsite || i==mcAccount);
    
            connect(leftView->selectionModel(),&QItemSelectionModel::selectionChanged,this,&TestWidget::webSiteSelected);
            QHBoxLayout *mainLay=new QHBoxLayout(this);
            mainLay->addWidget(leftView);
            mainLay->addWidget(rightView);
    
            ///////////////////////////////////////////////////////////////////////////////
            //insert example data
            model->insertRows(0,2);
            model->setData(model->index(0,mcWebsite),QStringLiteral("Website1"));
            model->setData(model->index(0,mcAccount),QStringLiteral("TestAccount1"));
            model->setData(model->index(1,mcWebsite),QStringLiteral("Website2"));
            model->setData(model->index(1,mcAccount),QStringLiteral("TestAccount2"));
            const QModelIndex exParent=model->index(0,0);
            model->insertRows(0,2,exParent);
            model->insertColumns(0,ModelColCount,exParent);
            model->setData(model->index(0,mcCookieName,exParent),QStringLiteral("some_cookie"));
            model->setData(model->index(0,mcCookieValue,exParent),QStringLiteral("some_value"));
            model->setData(model->index(0,mcCookieDomain,exParent),QStringLiteral("website.com"));
            model->setData(model->index(0,mcCookiePath,exParent),QStringLiteral("/"));
            model->setData(model->index(0,mcCookieExpire,exParent),QDate(2017,7,25));
            model->setData(model->index(1,mcCookieName,exParent),QStringLiteral("some_cookie2"));
            model->setData(model->index(1,mcCookieValue,exParent),QStringLiteral("different_value"));
            model->setData(model->index(1,mcCookieDomain,exParent),QStringLiteral("sub.website.com"));
            model->setData(model->index(1,mcCookiePath,exParent),QStringLiteral("/blog"));
            model->setData(model->index(1,mcCookieExpire,exParent),QStringLiteral("Session"));
            model->insertColumns(0,ModelColCount,model->index(1,0));
            ///////////////////////////////////////////////////////////////////////////////
    
            webSiteSelected();
        }
    private slots:
        void webSiteSelected(const QItemSelection &selected = QItemSelection()){
            QModelIndexList selectedIdx= selected.indexes();
            std::sort(selectedIdx.begin(),selectedIdx.end(),[](const QModelIndex& a, const QModelIndex& b)->bool{return a.column()<b.column();});
            const QModelIndex parent = selectedIdx.value(0,QModelIndex());
            rightView->setRootIndex(parent);
            const int rowCnt=rightView->model()->rowCount();
            for(int i=0;i<rowCnt;++i)
                rightView->setRowHidden(i,!parent.isValid());
        }
    
    private:
      QAbstractItemModel* model;
      QTableView* leftView;
      QTableView* rightView;
    };
    


  • Thanks for the quick reply. I should have mentioned I'm using PyQt. I do have some experience with C++ in the past, so I can mostly read what is going on. I'm a bit confused why you are using setData() and index(). Isn't the point of using a QStandardItemModel so you don't have to calling these lower level QAbstractItemModel methods? Can I not just use QStandardItem's and appendRow()?

    Also in the slot what is setRowHidden supposed to be doing? What are we wanting to hide in the right view? The function also seems to be missing a QModelIndex as the second parameter

    In the example setData, Why do we need to insertColumns for parent of the second row?

    "will have children with the last 7 columns populated"

    Did you mean last 5?

    model->insertColumns(0,ModelColCount,model->index(1,0));

    Also I like the look of QTreeView over QTableView. With QTableView I get a "spreadsheet" looking view which I don't want. Unless there is a way to get the table view to look like a 2D tree view. I haven't dug that deep into the view functions yet.



  • @Wallboy said in Need some assistance with QStandardItemModel and multiple Views:

    I should have mentioned I'm using PyQt. I do have some experience with C++ in the past, so I can mostly read what is going on.

    I'm afraid I'm a total ignorant in python (and I'm ashamed of it) so I'll have to stick with C++

    I'm a bit confused why you are using setData() and index(). Isn't the point of using a QStandardItemModel so you don't have to calling these lower level QAbstractItemModel methods?

    No, QStandardItemModel gives you another interface to access the data (the so called QStandardItem interface) but nothing prevents you from using the QAbstractItemModel interface. The difference is that, if in the future you decide to change model (go for a custom one?) all you'll need to do to my code is change model=new QStandardItemModel(this); to model=new MyCustomModel(this); if you had used the QStandardItem interface you'd have to rewrite the code from scratch.

    Can I not just use QStandardItem's and appendRow()?

    Yes, of course you can

    Also in the slot what is setRowHidden supposed to be doing? What are we wanting to hide in the right view?

    It's just a dirty trick to handle the case when no website is selected. I basically hide all rows in the right view if nothing is selected and show them all if one website is selected

    The function also seems to be missing a QModelIndex as the second parameter

    the signature of the signal is QItemSelectionModel::selectionChanged(const QItemSelection &, const QItemSelection &), since I don't need to know what indexes were deselected I just ignore the second parameter (just to be clear, the code above is tested and works)

    In the example setData, Why do we need to insertColumns for parent of the second row?

    It's just to display the headers, really. if you did not insert the columns model->columnCount(model->index(1,0)) would return 0 so the right view would not display the headers if you select the second row. this basically means every time you add a top level row, insert the columns for the child at the same time.

    "will have children with the last 7 columns populated"
    Did you mean last 5?

    Yes, my bad

    Also I like the look of QTreeView over QTableView. With QTableView I get a "spreadsheet" looking view which I don't want. Unless there is a way to get the table view to look like a 2D tree view. I haven't dug that deep into the view functions yet.

    Up to you really, you can try rightView->showGrid(false); leftView->showGrid(false); or change the views to QTreeViews and call leftView->setItemsExpandable(false); it's just a matter of style, not of functionality



  • "The function also seems to be missing a QModelIndex as the second parameter"

    I was speaking of setRowHidden() function, but then realized your version was for QTableView, and mine is QTreeView, which needed the second parent QModelIndex parameter.

    And if we only want to display the last 5 columns, why do you do this:

    model->insertColumns(0,ModelColCount,exParent);
    

    Should it not be:

    model->insertColumns(0,5,exParent);
    

    "It's just to display the headers, really. if you did not insert the columns model->columnCount(model->index(1,0)) would return 0 so the right view would not display the headers if you select the second row. this basically means every time you add a top level row, insert the columns for the child at the same time."

    But what this before is doing already?

    model->insertColumns(0,ModelColCount,exParent);
    

    I have the following snippet of code which is not working and making a lot of columns in the right tree view. and only showing text for one row in the left view. Basically I have "Website" objects that hold "Account" objects. I loop through all websites and accounts extracting the information I need:

            for site in list(registeredSites.values()):
                for account in list(enumerate(list(site.accounts.values()))):
                    self.insertRow(0)
                    self.setData(self.index(account[0], self.mWebsite), site.__class__.__name__)
                    self.setData(self.index(account[0], self.mAccount), account[1].login)
    
                    parent = self.index(account[0], 0)
                    self.insertColumns(0, 5, parent)
    
                    numCookies = randint(1, 15)  # Just for testing
    
                    for cookieNum in range(0, numCookies):
                        self.insertRow(0, parent)
                        cName = 'Cookie Name {}'.format(cookieNum)
                        cValue = 'Cookie Value {}'.format(cookieNum)
                        cDomain = 'Cookie Domain {}'.format(cookieNum)
                        cPath = 'Cookie Path {}'.format(cookieNum)
                        cExpires = 'Cookie Expires {}'.format(cookieNum)
    
                        self.setData(self.index(cookieNum, 0, parent), cName)
                        self.setData(self.index(cookieNum, 1, parent), cValue)
                        self.setData(self.index(cookieNum, 2, parent), cDomain)
                        self.setData(self.index(cookieNum, 3, parent), cPath)
                        self.setData(self.index(cookieNum, 4, parent), cExpires)
    

    I know you said you don't know much Python, but maybe you can see where I'm logically messing up in this code?

    EDIT: Ok I got it working correctly, but I don't understand why I had to "append" the row by switching insertRow(0) to insertRow(self.rowCount()) (The enumerate value is wrong for subsequent loops. I seen what I did wrong there now) and also why I had to move the insertRow outside of the cookie loop. And I'm also a bit lost why I have to add all 7 columns as a child and not just the 5 cookie values. And just to clear about the sort lambda, we do that to make sure index(x, 0) is the first in the list? Are they not in order to begin with by column? Here is the now working code:

            for site in list(registeredSites.values()):
                for account in list(enumerate(list(site.accounts.values()))):
                    self.insertRow(self.rowCount())
                    self.setData(self.index(account[0], self.mWebsite), site.__class__.__name__)
                    self.setData(self.index(account[0], self.mAccount), account[1].login)
    
                    parent = self.index(account[0], 0)
                    self.insertColumns(0, self.mColumnCount, parent)
    
                    numCookies = randint(1, 15)
                    self.insertRows(0, numCookies, parent)
    
                    for cookieNum in range(0, numCookies):
                        # self.insertRow(0, parent)
                        cName = 'Cookie Name {}'.format(cookieNum)
                        cValue = 'Cookie Value {}'.format(cookieNum)
                        cDomain = 'Cookie Domain {}'.format(cookieNum)
                        cPath = 'Cookie Path {}'.format(cookieNum)
                        cExpires = 'Cookie Expires {}'.format(cookieNum)
    
                        self.setData(self.index(cookieNum, self.mName, parent), cName)
                        self.setData(self.index(cookieNum, self.mValue, parent), cValue)
                        self.setData(self.index(cookieNum, self.mDomain, parent), cDomain)
                        self.setData(self.index(cookieNum, self.mPath, parent), cPath)
                        self.setData(self.index(cookieNum, self.mExpires, parent), cExpires)
    

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.