Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Need some assistance with QStandardItemModel and multiple Views
Forum Updated to NodeBB v4.3 + New Features

Need some assistance with QStandardItemModel and multiple Views

Scheduled Pinned Locked Moved Unsolved General and Desktop
6 Posts 2 Posters 3.7k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • W Offline
    W Offline
    Wallboy
    wrote on last edited by
    #1

    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.

    1 Reply Last reply
    0
    • VRoninV Offline
      VRoninV Offline
      VRonin
      wrote on last edited by VRonin
      #2

      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;
      };
      

      "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
      ~Napoleon Bonaparte

      On a crusade to banish setIndexWidget() from the holy land of Qt

      1 Reply Last reply
      2
      • W Offline
        W Offline
        Wallboy
        wrote on last edited by Wallboy
        #3

        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.

        VRoninV 1 Reply Last reply
        0
        • W Wallboy

          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.

          VRoninV Offline
          VRoninV Offline
          VRonin
          wrote on last edited by VRonin
          #4

          @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

          "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
          ~Napoleon Bonaparte

          On a crusade to banish setIndexWidget() from the holy land of Qt

          1 Reply Last reply
          0
          • W Offline
            W Offline
            Wallboy
            wrote on last edited by Wallboy
            #5

            "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)
            
            1 Reply Last reply
            0
            • W Offline
              W Offline
              Wallboy
              wrote on last edited by
              #6

              Sorry to bump my old thread, but I'm now in the situation where I need to switch the model from QStandardItemModel to a QAbstractItemModel and I can't figure out how to implement parent() and index() correctly. I still have Account objects in the following form:

              class Account:
                  def __init__(website, name, cookies):
                      self.website = website   # string
                      self.name = name         # string
              
                      # list of lists:  
                      # [ ['CookieName1', 'CookieValue1', 'Domain1', 'Path1', 'Expiration1'],
                      #   ['CookieName2', 'CookieValue2', 'Domain2', 'Path2', 'Expiration2'] ]
                      self.cookies = cookies  
              

              In my mind the model is supposed to look something like this (in this example pretend I have two Accounts in the model. First Account has 3 cookies, and the second Account only has 1 cookie):

                [Website]   [Account]   [CName]   [CValue]     [CDomain]  [CPath]    [CExpires]
              - Website1    Account1    (empty)   (empty)      (empty)    (empty)    (empty)
                (empty)     (empty)     CName1    CVal1        Dom1       Path1      Exp1
                (empty)     (empty)     CName2    CVal2        Dom2       Path2      Exp2
                (empty)     (empty)     CName3    CVal3        Dom3       Path3      Exp3
              - Website2    Account2    (empty)   (empty)      (empty)    (empty)    (empty)
                (empty)     (empty)     CName1    CVal1        Dom1       Path1      Exp1
              

              for rowCount() I've done the following:

              def rowCount(self, parentIdx):
                      # If no parent index I assume top-level row, so just return the total amount of Accounts we have
                      if not parentIdx.isValid():
                          return len(self.accounts)
                      
                      # Otherwise get the Account object and return how many cookies we have
                      account = parentIdx.internalPointer()
                      return len(account.cookies)
              

              But I'm completely lost on how to implement index() and parent(). For index() I was thinking it would be just as simple as:

              def index(self, row, column, parentIdx=QModelIndex()):
                          return self.createIndex(row, column, self.accounts[row])
              

              Where we need access to the Account object for every row, so we just create a pointer to the Account, but then how do I implement parent()? How do I know when I'm on a "child" (cookie) row and need to get a parent index?

              I've been working with Qt for over a year and Tree Models still confuse the hell out of me. I do have a few models that follow the Qt Simple Tree Model Example, but those are designs that allow for arbitrary tree structure depth, whereas the one I'm trying to design is fixed to only 1 level max (the cookies if there is any).

              I would appreciate any help.

              Thanks

              1 Reply Last reply
              0

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved