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. How to use a QList of UserDefined class, as the Storage Model for QAbstractItemModel to be used by QTreeWidget
Forum Updated to NodeBB v4.3 + New Features

How to use a QList of UserDefined class, as the Storage Model for QAbstractItemModel to be used by QTreeWidget

Scheduled Pinned Locked Moved Solved General and Desktop
13 Posts 2 Posters 898 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.
  • A Offline
    A Offline
    Andrea_Venturelli
    wrote on last edited by Andrea_Venturelli
    #1

    hi guys, I'm puzzled about which would be the more elegant way to use a " QList<MyTypeClass> " as model for a QTreeWidget with 4 columns.

    the final result shoul look like the following:

    3a6e9216-cdf1-4019-88ff-e8969617431b-image.png
    ** attention this is only a graphic design in figma - there is not a concrete implementation yet.

    description
    • QTreeWidget with 4 column : "file type", "full_path", "State" and "Openable"
      -> file type [bool] : display folder icon when the value is 0, else display File icon.
      -> full path [QString] : a path to a folder or a file
      -> State [bool] : 0 if the path doesn't exist anymore at that location display red circle and a text, else a green circle and a text
      -> Openable[bool] : this option will let the user decide whether to open the file/folder when a PushButton is pressed.

    my UserDefinedClass, called PathInfo will hold all the 4 values quoted above

    // WARNING: this is not a real code implementation.
    //          is only a trace for listing the features I'll need
    class PathInfo
    {
          // the constructor will inspect the path and fill
         // "isValid", "isFile" and "isOpenable" to the respective member variables
          PathInfo( QString& path ) ;
    
     public:
          static bool pathIsValid( const QString& path ) ;
          static bool isFile( const QString& path ) ;
    
          // getter
          bool openable() const ;
          bool fileType() const;
          bool validityState() const ;
          QString path() const ;
    
          // setter
          void changePath( QString& path ) ;
          void updateValidityState() ;
          void toggleOpenable() ;
    
    private:
         QString path;
         bool isValid;
         bool isFile;
         bool isOpenable { 1 };
    };
    
    Questions:
    1. How can I subClass QAbstractItem model and take the QList<PathInfo> as the model structure that will display the PathInfo.isFile , PathInfo.path(), PathInfo.isValid and PathInfo.isOpenable on the TreeWidget ?
    2. Should I implement both an abstractItemModel and also a custom TreeWidget ?
    GOALS:

    my goals are to update the model and the view as a consequence of adding a new Instance of PathInfo (my user defined class) to the QList<PathInfo> and
    implement a QStyledItemDelegate for the fourth colum. For the first and third column, QDisplayRole and QDecorationRole, maybe, will be enough ?

    JonBJ 1 Reply Last reply
    0
    • A Offline
      A Offline
      Andrea_Venturelli
      wrote on last edited by
      #12

      @JonB you were right and indeed makes sense not calling "painter.end()" because this will create problem to the painting of the second element and so on... using Painter::save() at the beginning and Painter::restore() at the end of the designated class to paint the obj is enoug like StarDelegate example provided by Qt's documentation Illustrates.

      SOLUTION:

      I found a easier a fastest way to change the state of a "togglable item" (like checkbox, toggle sw, ecc..) inside a Custom Delegate.
      here the breakdown of the steps to follow:

      1. Is always better and simpler to draw the "static version" of the delegate inside the QStyledItemDelegate() instead of creating a custom class and pass the delegate's painter to a custom method of a class that have the painting implementation details about how to drow the item.

      2. You don't always follow the starDelegate example provided by Qt because is not suitable for every purpose as I just learned.

      3. if you want to directly toggle an item without passing through the process "click the cell you want to toggle" >> "onother click or double-click to trigger the editor creation" >> "onother click to change the state inside the editor just created" , you can directly toggle the state with only one click and using one methods like so:

      #ifndef TOGGLESWITCHDELEGATE_H
      #define TOGGLESWITCHDELEGATE_H
      #include <QStyledItemDelegate>
      #include "viewtogglesw.h"
      #include <QPaintEvent>
      #include <QMouseEvent>
      
      class ToggleSwitchDelegate: public QStyledItemDelegate
      {
          Q_OBJECT
      public:
          ToggleSwitchDelegate(QObject* parent);
          ~ToggleSwitchDelegate();
      
          QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
      
          void paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
      
          //* THIS IS THE MAGIC METHOD YOU WANT
          bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;
      private:
          mutable ViewToggleSw* toggle_sw;
      
      };
      
      #endif // TOGGLESWITCHDELEGATE_H
      
      #include "toggleswitchdelegate.h"
      
      ToggleSwitchDelegate::ToggleSwitchDelegate(QObject* parent )
          : QStyledItemDelegate(parent) , toggle_sw( new ViewToggleSw( qobject_cast<QWidget*>(parent)) )
      {}
      
      
      ToggleSwitchDelegate::~ToggleSwitchDelegate()
      {
         delete toggle_sw;
      }
      
      
      void ToggleSwitchDelegate::paint(
          QPainter *painter,
          const QStyleOptionViewItem &option,
          const QModelIndex &index
      ) const
      {
      
          if ( index.data().canConvert<bool>() )
          {
              auto is_checked = qvariant_cast<bool>( index.data() );
      
              // *** DRAW DIRECTLY - NO WIDGET INSTANCE ***
      
              painter->setRenderHint(QPainter::Antialiasing, true);
      
              int handle_radius = std::round(0.24 * option.rect.height());
              int w_fx_h = std::round((option.rect.height() * 11.0) / 13.0); // Use 11.0 and 13.0 for floating-point division
      
              QRectF frame_slider(option.rect.x(), option.rect.y(), w_fx_h - handle_radius, option.rect.height() * 0.40);
              frame_slider.moveCenter(option.rect.center());
      
              int border_radius = frame_slider.height() / 2;
              int handle_path_len = w_fx_h - handle_radius;
              int x_position = frame_slider.x() + (handle_path_len * (is_checked ? 1.0 : 0.0));
      
              // Draw Frame (using colors from ViewToggleSw or define them here)
              QPen framePen(is_checked ? QColor(152, 73, 249) : QColor(113, 113, 113), 3);
              QBrush frameBrush(is_checked ? QColor(255, 255, 255) : QColor(207, 207, 207));
              painter->setPen(framePen);
              painter->setBrush(frameBrush);
              painter->drawRoundedRect(frame_slider, border_radius, border_radius);
      
              // Draw Handle
              QPen handlePen(QColor(81, 80, 102), 3);
              QBrush handleBrush(is_checked ? QColor(161, 86, 253) : QColor(132, 131, 151));
              painter->setPen(handlePen);
              painter->setBrush(handleBrush);
              painter->drawEllipse(QPointF(x_position, option.rect.center().y()), handle_radius, handle_radius);
          }
      }
      
      // MAGIC METHOD IMPLEMENTATION
      bool ToggleSwitchDelegate::editorEvent(
          QEvent *event,
          QAbstractItemModel *model,
          const QStyleOptionViewItem &option,
          const QModelIndex &index
      )
      {
          // checks if index is valid, colum is the fourth (in my case, the column where i will install the delegate on the view
         // remember that first column as index.column() = 0 not 1, so Column 3 is the fourth
         // in my case I chose to toggle the checkedState when the event is of type mouseReleaseEvent
          bool idx_isValid = index.isValid();
          bool isCol3_isTypeBool = ( index.column() == 3 && index.data().canConvert<bool>() );
          bool isMouseReleaseEvent = ( event->type() == QEvent::MouseButtonRelease );
          if ( idx_isValid && isCol3_isTypeBool && isMouseReleaseEvent )
          {
              QMouseEvent* mouseEvent = static_cast<QMouseEvent*>( event );
              // if the mouse Click is inside the cell toggle the model value
              if ( option.rect.contains(mouseEvent->pos()) )
              {
              //  if the current value is true, set it to false and viceversa
                  bool is_checked = model->data( index, Qt::DisplayRole ).toBool();
                  model->setData( index, !is_checked, Qt::EditRole );
                  return true; // needed to consume the event
              }
      
              return true;
          }
      
          return false;
      }
      
      QSize ToggleSwitchDelegate::sizeHint(
          const QStyleOptionViewItem &option,
          const QModelIndex &index
      ) const
      {
          Q_UNUSED(option)
      
          if ( index.data().canConvert<bool>() )
          {
              return toggle_sw->sizeHint();
          }
      
          return QSize();
      }
      
      Important: see the comments inside the above code snippet for further explanations

      here the code on the constructor of QWidget (could be of type QMainWindow also)

      Widget::Widget(QWidget *parent)
          : QWidget(parent)
          , ui(new Ui::Widget)
      {
          ui->setupUi(this);
      
          PathInfo info(QString("C:\\Users\\some\\path\\to\\Desktop\\file.png"));
          PathInfo info2(QString("C:\\Users\\123\\test\\documents"));
          PathInfo info3(QString("C:\\Users\\test\\Tor Browser\\Browser\\distribution\\extensions"));
      
         // only for test, default is set to true
          info3.setOpenable(false);
      
          QList<PathInfo> group;
          group.append( info );
          group.append( info2 );
          group.append( info3 );
      
          paths_model = new PathsInfoTableModel( group, this );
      
          // hide the row headers and add wrap word if path is to long
          ui->t_view->setModel( paths_model );
          ui->t_view->verticalHeader()->hide();
          ui->t_view->setWordWrap(true);
          ui->t_view->verticalHeader()->setDefaultSectionSize(54);
      
          // setting toggle switch delegate for col 3
          auto* toggle_sw_delegate = new ToggleSwitchDelegate( ui->t_view );
          ui->t_view->setItemDelegateForColumn( 3, toggle_sw_delegate );
      
         // there is no needs of editTriggers because our magic method calls automatically setModel without creating an Editor
          ui->t_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
      
          // this allow the user to double-click on the path cell and select a new one
         // with the QFileDialog::getOpenFileName and update the model..
          connect(
              ui->t_view, &QTableView::doubleClicked,
              this, [this] ( const QModelIndex& index ) {
                  if ( !index.isValid() ) return;
      
                  if ( index.column() == 1 )
                  {
                      auto* model = qobject_cast<PathsInfoTableModel*>( ui->t_view->model() );
                      if ( !model ) return;
      
                      QString current_path = model->data( index, Qt::DisplayRole ).toString();
      
                      // if the parent of current path exist, use
                      // it as default location else, Home
                      PathInfo path_info( current_path );
                      auto default_path = (QFileInfo::exists(path_info.absPath())) ? path_info.absPath() : QDir::homePath();
      
                      QString newPath = QFileDialog::getOpenFileName(this, "Select new Path", default_path );
                      if ( !newPath.isEmpty() && newPath != current_path )
                      {
                          QVariant newPathVariant = QVariant( newPath );
                          model->setData( index, newPathVariant, Qt::EditRole );
                      }
                  }
              }
          );
      }
      
      1 Reply Last reply
      0
      • A Andrea_Venturelli

        hi guys, I'm puzzled about which would be the more elegant way to use a " QList<MyTypeClass> " as model for a QTreeWidget with 4 columns.

        the final result shoul look like the following:

        3a6e9216-cdf1-4019-88ff-e8969617431b-image.png
        ** attention this is only a graphic design in figma - there is not a concrete implementation yet.

        description
        • QTreeWidget with 4 column : "file type", "full_path", "State" and "Openable"
          -> file type [bool] : display folder icon when the value is 0, else display File icon.
          -> full path [QString] : a path to a folder or a file
          -> State [bool] : 0 if the path doesn't exist anymore at that location display red circle and a text, else a green circle and a text
          -> Openable[bool] : this option will let the user decide whether to open the file/folder when a PushButton is pressed.

        my UserDefinedClass, called PathInfo will hold all the 4 values quoted above

        // WARNING: this is not a real code implementation.
        //          is only a trace for listing the features I'll need
        class PathInfo
        {
              // the constructor will inspect the path and fill
             // "isValid", "isFile" and "isOpenable" to the respective member variables
              PathInfo( QString& path ) ;
        
         public:
              static bool pathIsValid( const QString& path ) ;
              static bool isFile( const QString& path ) ;
        
              // getter
              bool openable() const ;
              bool fileType() const;
              bool validityState() const ;
              QString path() const ;
        
              // setter
              void changePath( QString& path ) ;
              void updateValidityState() ;
              void toggleOpenable() ;
        
        private:
             QString path;
             bool isValid;
             bool isFile;
             bool isOpenable { 1 };
        };
        
        Questions:
        1. How can I subClass QAbstractItem model and take the QList<PathInfo> as the model structure that will display the PathInfo.isFile , PathInfo.path(), PathInfo.isValid and PathInfo.isOpenable on the TreeWidget ?
        2. Should I implement both an abstractItemModel and also a custom TreeWidget ?
        GOALS:

        my goals are to update the model and the view as a consequence of adding a new Instance of PathInfo (my user defined class) to the QList<PathInfo> and
        implement a QStyledItemDelegate for the fourth colum. For the first and third column, QDisplayRole and QDecorationRole, maybe, will be enough ?

        JonBJ Online
        JonBJ Online
        JonB
        wrote on last edited by
        #2

        @Andrea_Venturelli
        Normally to subclass QAbstractItemModel you need to supply rows and columns from your data. Do read https://doc.qt.io/qt-6/qabstractitemmodel.html#subclassing for what you need to implement for subclassing. Your rowCount() will be the the length of the QList. columnCount() would return 4. You implement data() (and setData() if editable) to map between the fields/members in your class and corresponding column numbers.

        You may choose to implement some of the columns via different data() roles instead of columns if it seems more suitable, e.g. the file type icon. Whatever you can resolve how it is displayed in your view/QStyledItemDelegate.

        At the moment what you show does not look like a tree view/widget. You would presumably want to do something with Full Path for that. Look into parent to create the tree hierarchy.

        1 Reply Last reply
        2
        • A Offline
          A Offline
          Andrea_Venturelli
          wrote on last edited by
          #3

          hi @JonB , i'm using a QTreeWidget only because QList has a "single column only" and the QTableWidget has QCornerButton and rows indicator that I don't need in the view.

          So I opted to use QTreeWidget with only QModelIndex() as parent and I'm not planning to use children elements in it.
          My idea is to use it as a "multi-column" QListView with Headers. This is the reason I made when I choose It.

          for the Structure I Thought about the possible implementation and come to the same conclusion you suggested that is:
          -> rowCounts() will be QList<FileInfo>.size()
          -> columnCounts() will be fixed to 4.

          • What I'm struggling with, is how in the setData() I'll fill the model starting only from an indexModel ?
          • Is setData being called for every cell like (row 0, col 0) ecc.. or by rows ( row 0: col 0, col 1, col 2, col 3) ?
          is this how setData passes the IndexModel to fill the data?

          63391d2c-27cf-4625-8fbd-f47983c05304-image.png

          custom behaviors:

          • the rows CAN be deleted with a dedicated QPushButton on the currentItemSelected ( so i need to implement the model's RemoveRows() virtual method)

          • only the last colum is editable by the user via a QStyledItemDelegate ( when pressed inside the option.rect() toggle the bool value ) ... is like a checkbox with a custom paintEvent

          • double-clicking on one row, will call "QFileSystemExplorer" to choose a new path to substitute in place of the current one (this will trigger a check on the other 3 columns because the all depend on the path

          Can you please write a boilerPlate of a generic implementation of "setData()" in order to understand visually ?

          JonBJ 1 Reply Last reply
          0
          • A Andrea_Venturelli

            hi @JonB , i'm using a QTreeWidget only because QList has a "single column only" and the QTableWidget has QCornerButton and rows indicator that I don't need in the view.

            So I opted to use QTreeWidget with only QModelIndex() as parent and I'm not planning to use children elements in it.
            My idea is to use it as a "multi-column" QListView with Headers. This is the reason I made when I choose It.

            for the Structure I Thought about the possible implementation and come to the same conclusion you suggested that is:
            -> rowCounts() will be QList<FileInfo>.size()
            -> columnCounts() will be fixed to 4.

            • What I'm struggling with, is how in the setData() I'll fill the model starting only from an indexModel ?
            • Is setData being called for every cell like (row 0, col 0) ecc.. or by rows ( row 0: col 0, col 1, col 2, col 3) ?
            is this how setData passes the IndexModel to fill the data?

            63391d2c-27cf-4625-8fbd-f47983c05304-image.png

            custom behaviors:

            • the rows CAN be deleted with a dedicated QPushButton on the currentItemSelected ( so i need to implement the model's RemoveRows() virtual method)

            • only the last colum is editable by the user via a QStyledItemDelegate ( when pressed inside the option.rect() toggle the bool value ) ... is like a checkbox with a custom paintEvent

            • double-clicking on one row, will call "QFileSystemExplorer" to choose a new path to substitute in place of the current one (this will trigger a check on the other 3 columns because the all depend on the path

            Can you please write a boilerPlate of a generic implementation of "setData()" in order to understand visually ?

            JonBJ Online
            JonBJ Online
            JonB
            wrote on last edited by JonB
            #4

            @Andrea_Venturelli
            If you're not going to create a hierarchical model with parentage you don't want a QTree.... anything, you want to stick to a QTable....
            You can suppress display of corner buttons or rows indicators in a QTableWidget.
            QTreeWidget is a QTreeView with its own internal model and a way of working, and the same for QTableWidget versus QTableView.
            If you wish to use your own model (e.g. your QList<MyTypeClass>) you will want to use one of the Q...View classes not the Q...Widget ones. If you use the latter you will have to copy your data in & out of its internal model.
            QTableView is more suitable for multi-column table presentation than QListView. But you may have your reasons for using the latter with a QStyledItemDelgate and paintEvent()s etc.
            With a Q{Table,Tree}View it retrieves data from your model via data() and the QModelIndexes like you show in our picture. setData() works just the same if you want to store data to your model. Once you have written data() correctly it is self-evident how to write setData(). Do the former the way you want first, then similar code in setData().

            1 Reply Last reply
            1
            • A Offline
              A Offline
              Andrea_Venturelli
              wrote on last edited by
              #5

              Hi everyone, I implemented the PathInfo class and defined a basic implementation of PathsInfoTableModel which inherit from QAbstractTableModel

              PathsInfoTableModel.cpp

              
              PathsInfoTableModel::PathsInfoTableModel(QList<PathInfo> group, QObject *parent)
                  : QAbstractTableModel{parent}, m_model_data{ group }
              {}
              
              int PathsInfoTableModel::rowCount(const QModelIndex &parent) const
              {
                  Q_UNUSED(parent)
                  return m_model_data.size();
              }
              
              int PathsInfoTableModel::columnCount(const QModelIndex &parent) const
              {
                  Q_UNUSED(parent)
                  return 4;
              }
              
              QVariant PathsInfoTableModel::data(const QModelIndex &index, int role) const
              {
                  Q_UNUSED(role)
                  qDebug() << QString("-").repeated(30);
                  qDebug() << "\nindex" << index << "[row] :" << index.row() <<"   [col]: " << index.column();
              
                  return QVariant();
              }
              

              and PathInfo.h

              class PathInfo: QObject
              {
                  Q_OBJECT
              
              public:
                  Q_SIGNAL void pathChanged( QString, QString);
              
                  // constructors
                  PathInfo( QString path );
                  PathInfo( char* path );
              
                  PathInfo(const PathInfo& other );
                  PathInfo operator=(const PathInfo& other );
              
                  // destructor
                  ~PathInfo() {} ;
              
                  // getters
                  bool openable()     const { return m_isOpenable; };
                  bool fileType()     const { return m_isFile; };
                  bool pathValidity() const { return m_isValid; };
              
                  //setters
                  void changePath ( QString new_path );
                  void setOpenable( bool state )     { m_isOpenable = state; };
                  void setFileType( bool type )      { m_isFile = type;      };
                  void setPathValidity( bool state ) { m_isValid = state;    };
              
                  // file info
                  QList<QString> absolute_fullPath_fName();
                  QString absPath()  const;
                  QString fileName() const;
                  QString filePath() const;
              
                  // static convinience methods
                  static bool pathIsValid( const QString path );
                  static int  isFile( const QString path );
              
                  void printInfo();
              
              private:
                  QString m_path;
                  bool m_isValid{ 0 };
                  bool m_isFile{ 1 };
                  bool m_isOpenable{ 1 };
              
                  QFileInfo m_file_info;
              
                  void updatePathInfo();
              };
              

              Question:

              How can I access the member variables (m_path, m_isValid, m_isFile, m_isOpenable) with the array access notation like: my_obj[0], my_obj[1] ... ?

              The problem:

              QAbstractTableModel.data( params ) will provide a QIndexModel(row, col) for each cell in the table times the numbers of Qt::Roles

              My model is QList<PathInfo> so the QModelIndex.row() indicates the element in the QList and this is the easy part. What If I want to access the 4 members inside PathInfo like so:

              PathInfo[ QModelIndex.column() ]
              and this will give me something like:
              PathInfo[ 0 ] = PathInfo.m_isFile,
              PathInfo[ 1 ] = PathInfo.m_path,
              PathInfo[ 2 ] = PathInfo.m_isValid,
              PathInfo[ 3 ] = PathInfo.m_isOpenable

              I thought about implementing FileInfo::operator[] with a bunch of switchCase conditions or via a random access iterator pointing to each member_var address...

              My main concern is that, if a throw a bunch of "if-else" statements inside QAbstractTableModel.data() this will slow down significantly if a user decide to add a good amount of rows

              JonBJ 1 Reply Last reply
              0
              • A Andrea_Venturelli

                Hi everyone, I implemented the PathInfo class and defined a basic implementation of PathsInfoTableModel which inherit from QAbstractTableModel

                PathsInfoTableModel.cpp

                
                PathsInfoTableModel::PathsInfoTableModel(QList<PathInfo> group, QObject *parent)
                    : QAbstractTableModel{parent}, m_model_data{ group }
                {}
                
                int PathsInfoTableModel::rowCount(const QModelIndex &parent) const
                {
                    Q_UNUSED(parent)
                    return m_model_data.size();
                }
                
                int PathsInfoTableModel::columnCount(const QModelIndex &parent) const
                {
                    Q_UNUSED(parent)
                    return 4;
                }
                
                QVariant PathsInfoTableModel::data(const QModelIndex &index, int role) const
                {
                    Q_UNUSED(role)
                    qDebug() << QString("-").repeated(30);
                    qDebug() << "\nindex" << index << "[row] :" << index.row() <<"   [col]: " << index.column();
                
                    return QVariant();
                }
                

                and PathInfo.h

                class PathInfo: QObject
                {
                    Q_OBJECT
                
                public:
                    Q_SIGNAL void pathChanged( QString, QString);
                
                    // constructors
                    PathInfo( QString path );
                    PathInfo( char* path );
                
                    PathInfo(const PathInfo& other );
                    PathInfo operator=(const PathInfo& other );
                
                    // destructor
                    ~PathInfo() {} ;
                
                    // getters
                    bool openable()     const { return m_isOpenable; };
                    bool fileType()     const { return m_isFile; };
                    bool pathValidity() const { return m_isValid; };
                
                    //setters
                    void changePath ( QString new_path );
                    void setOpenable( bool state )     { m_isOpenable = state; };
                    void setFileType( bool type )      { m_isFile = type;      };
                    void setPathValidity( bool state ) { m_isValid = state;    };
                
                    // file info
                    QList<QString> absolute_fullPath_fName();
                    QString absPath()  const;
                    QString fileName() const;
                    QString filePath() const;
                
                    // static convinience methods
                    static bool pathIsValid( const QString path );
                    static int  isFile( const QString path );
                
                    void printInfo();
                
                private:
                    QString m_path;
                    bool m_isValid{ 0 };
                    bool m_isFile{ 1 };
                    bool m_isOpenable{ 1 };
                
                    QFileInfo m_file_info;
                
                    void updatePathInfo();
                };
                

                Question:

                How can I access the member variables (m_path, m_isValid, m_isFile, m_isOpenable) with the array access notation like: my_obj[0], my_obj[1] ... ?

                The problem:

                QAbstractTableModel.data( params ) will provide a QIndexModel(row, col) for each cell in the table times the numbers of Qt::Roles

                My model is QList<PathInfo> so the QModelIndex.row() indicates the element in the QList and this is the easy part. What If I want to access the 4 members inside PathInfo like so:

                PathInfo[ QModelIndex.column() ]
                and this will give me something like:
                PathInfo[ 0 ] = PathInfo.m_isFile,
                PathInfo[ 1 ] = PathInfo.m_path,
                PathInfo[ 2 ] = PathInfo.m_isValid,
                PathInfo[ 3 ] = PathInfo.m_isOpenable

                I thought about implementing FileInfo::operator[] with a bunch of switchCase conditions or via a random access iterator pointing to each member_var address...

                My main concern is that, if a throw a bunch of "if-else" statements inside QAbstractTableModel.data() this will slow down significantly if a user decide to add a good amount of rows

                JonBJ Online
                JonBJ Online
                JonB
                wrote on last edited by
                #6

                @Andrea_Venturelli
                Yes you need to map between your class with individual C++ member variables and QModelIndex integer column indexes. In both directions. Yes you should use a switch statement in data()/setData() methods. Yes there is a marginal overhead in a switch (or ifs, switch might be faster here), no it won't be significant. There are only 4 cases anyway. Yes this is standard.

                1 Reply Last reply
                2
                • A Offline
                  A Offline
                  Andrea_Venturelli
                  wrote on last edited by
                  #7

                  I did some work on the model and the basics works as expected; this is the result I have:

                  image.png

                  problem with column 4:

                  When I try to set the custom delegate on the column 4, I get some Console's messages saying the following:

                  QPainter::save: Painter not active
                  QPainter::setRenderHint: Painter must be active to set rendering hints
                  QPainter::setPen: Painter not active
                  QPainter::setBrush: Painter not active
                  QPainter::drawRects: Painter not active
                  QPainter::restore: Unbalanced save/restore
                  QPainter::save: Painter not active
                  

                  and if three items is given to the model, the view draws only the first and the others will not been drawn.

                  toggleswitchdelegate.h

                  #ifndef TOGGLESWITCHDELEGATE_H
                  #define TOGGLESWITCHDELEGATE_H
                  #include <QStyledItemDelegate>
                  #include "toggleswitcheditor.h"
                  
                  // implementation info
                  
                  // every time the editing is finished
                  // you must emit QStyledItemDelegate::commitData( pointer to Editor)
                  // and QStyledItemDelegate::closeEditor( pointer to Editor)
                  
                  
                  class ToggleSwitchDelegate: public QStyledItemDelegate
                  {
                      Q_OBJECT
                  public:
                      ToggleSwitchDelegate(QObject* parent);
                      //~ToggleSwitchDelegate();
                  
                      QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
                  
                      // editing releated methods
                      QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
                      void setEditorData(QWidget *editor, const QModelIndex &index) const override;
                      void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
                  
                      void paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
                  
                  private:
                      //mutable ViewToggleSw* toggle_sw;
                  
                  private slots:
                      void commitAndCloseEditor();
                  };
                  

                  toggleswitchdelegate.cpp

                  #include "toggleswitchdelegate.h"
                  #include "toggleswitcheditor.h"
                  
                  ToggleSwitchDelegate::ToggleSwitchDelegate(QObject* parent )
                      : QStyledItemDelegate(parent) //, toggle_sw( new ViewToggleSw( qobject_cast<QWidget*>(parent)) )
                  {}
                  
                  /*
                  ToggleSwitchDelegate::~ToggleSwitchDelegate()
                  {
                      delete toggle_sw;
                  }*/
                  
                  void ToggleSwitchDelegate::paint(
                      QPainter *painter,
                      const QStyleOptionViewItem &option,
                      const QModelIndex &index
                  ) const
                  {
                  
                      if ( index.data().canConvert<bool>() )
                      {
                          auto sw_state = qvariant_cast<bool>( index.data() );
                          ViewToggleSw toggle_sw;
                          toggle_sw.setChecked( sw_state );
                          //toggle_sw->setChecked( sw_state );
                  
                          // highlighted bg when selected
                          if ( option.state & QStyle::State_Selected )
                              painter->fillRect( option.rect, option.palette.highlight() );
                  
                          // static paint of the widget
                          toggle_sw.paint( painter, option.rect, option.palette );
                          //toggle_sw->paint( painter, option.rect, option.palette );
                      }
                  }
                  
                  
                  QSize ToggleSwitchDelegate::sizeHint(
                      const QStyleOptionViewItem &option,
                      const QModelIndex &index
                  ) const
                  {
                      Q_UNUSED(option)
                  
                      if ( index.data().canConvert<bool>() )
                      {
                          ViewToggleSw toggle_sw;
                          return toggle_sw.sizeHint();
                      }
                  
                      return QSize();
                  }
                  
                  
                  QWidget* ToggleSwitchDelegate::createEditor(
                      QWidget* parent,
                      const QStyleOptionViewItem &option,
                      const QModelIndex &index
                  ) const
                  {
                      Q_UNUSED(option)
                  
                      if ( index.data().canConvert<bool>() )
                      {
                          ToggleSwitchEditor* editor = new ToggleSwitchEditor( parent );
                          connect( editor, &ToggleSwitchEditor::editingFinished, this, &ToggleSwitchDelegate::commitAndCloseEditor );
                          return editor;
                      }
                  
                      return nullptr;
                  }
                  
                  
                  void ToggleSwitchDelegate::setEditorData(
                      QWidget* editor,
                      const QModelIndex &index
                  ) const
                  {
                      if ( index.data().canConvert<bool>() )
                      {
                          auto sw_state = qvariant_cast<bool>( index.data() );
                          ViewToggleSw toggle_sw;
                          toggle_sw.setChecked( sw_state );
                  
                          auto* my_editor = qobject_cast<ToggleSwitchEditor*>( editor );
                          my_editor->setToggleSwitch( toggle_sw );
                      }
                  }
                  
                  
                  void ToggleSwitchDelegate::setModelData(
                      QWidget *editor,
                      QAbstractItemModel *model,
                      const QModelIndex &index
                  ) const
                  {
                      if ( index.data().canConvert<bool>() )
                      {
                          auto* my_editor = qobject_cast<ToggleSwitchEditor*>( editor );
                          model->setData( index, QVariant::fromValue(my_editor->viewToggleSw().isChecked()) );
                      }
                  }
                  
                  
                  /***************************
                   *
                   *          SLOTS
                   *
                   ***************************/
                  void ToggleSwitchDelegate::commitAndCloseEditor()
                  {
                      auto* editor = qobject_cast<ToggleSwitchEditor*>( sender() );
                      // this two signal come from QStyledItemDelegate
                      emit this->commitData( editor );
                      emit this->closeEditor( editor );
                  }
                  
                  JonBJ 1 Reply Last reply
                  0
                  • A Offline
                    A Offline
                    Andrea_Venturelli
                    wrote on last edited by
                    #8

                    the ViewToggleSw class look like that:

                    viewtogglesw.h

                    class ViewToggleSw : public QCheckBox
                    {
                        Q_OBJECT
                        Q_PROPERTY(qreal m_handle_pos READ handlePosition WRITE setHandlePosition NOTIFY handlePosChanged FINAL)
                    
                        using PenBrushStyle = std::pair<QPen, QBrush>;
                    
                        int PEN_WIDTH { 3 };
                    
                        // unchecked state
                        QBrush BG_COL_UNCHECK   { QColor(207, 207, 207) };
                        QBrush BORDER_COL_UNCHECK { QColor(113, 113, 113) };
                    
                        //  checked state
                        QBrush BG_COL_CHECK   { QColor(255, 255, 255) };
                        QBrush BORDER_COL_CHECK { QColor(152, 73, 249) };
                    
                        // handle unchecked
                        QBrush HANDLE_COL_UNCHECK  { QColor(132, 131, 151) };
                        QBrush BORDER_HANDLE_COL     { QColor(81, 80, 102) };
                    
                        // handle checked
                        QBrush HANDLE_COL_CHECK { QColor(161, 86, 253) };
                    
                    public:
                        explicit ViewToggleSw(QWidget *parent = nullptr);
                        ~ViewToggleSw() override = default;
                    
                        // copy constructor
                        ViewToggleSw( const ViewToggleSw& other);
                        // copy assignment
                        ViewToggleSw& operator= (const ViewToggleSw& other);
                    
                        QSize sizeHint()    const override;
                        void paint(QPainter *painter, const QRect &rect, const QPalette &palette) const;
                    
                    protected:
                        bool hitButton( const QPoint& pos ) const override;  // CheckBox method re-implementation
                    
                    private:
                        qreal  m_handle_pos{};
                        QPropertyAnimation* m_handle_animation;
                        PenBrushStyle m_handle_style;
                        PenBrushStyle m_frame_style;
                    
                        void  setHandlePosition( qreal pos ) { m_handle_pos = pos; }
                        qreal handlePosition() const { return m_handle_pos; }
                    
                        void setPainterBrushPen(QPainter* p, QPen& pen, QBrush& brush) const;
                    
                    private slots:
                        void setAnimDirection( bool checked );
                        void onAnimationFinished();
                    
                    signals:
                        void handlePosChanged( int pos );
                        void animationFinished();
                    };
                    

                    viewtogglesw.cpp

                    
                    ViewToggleSw::ViewToggleSw(QWidget *parent)
                        : QCheckBox{parent}
                    {
                        // setup checkbox and size
                        this->setCheckable(true);
                        setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
                    
                        setMouseTracking(true);
                        setContentsMargins(8, 0, 8, 0);     // padding left-right for the handle
                    
                        // setup animation
                        m_handle_animation = new QPropertyAnimation(this, "m_handle_pos", parent);
                        m_handle_animation->setStartValue(0.0);
                        m_handle_animation->setDuration(150);
                    
                        // connect animation::finished
                        // to a slot that will notify
                        // the editor
                        connect( m_handle_animation, &QPropertyAnimation::finished, this, &ViewToggleSw::onAnimationFinished );
                    
                        // change the Animation->endValue()
                        // The ViewToggleSw.setChecked(bool) will
                        // be called from the editor
                        connect( this, &QCheckBox::toggled, this, &ViewToggleSw::setAnimDirection );
                    
                        m_handle_style = std::make_pair(
                            QPen(BORDER_HANDLE_COL, PEN_WIDTH),
                            HANDLE_COL_UNCHECK
                            );
                    
                        m_frame_style = std::make_pair(
                            QPen(BG_COL_UNCHECK, PEN_WIDTH),
                            BORDER_COL_UNCHECK
                        );
                    }
                    
                    
                    //-------------------- copy constructor -----------------------------
                    ViewToggleSw::ViewToggleSw( const ViewToggleSw& other)
                        : QCheckBox{ qobject_cast<QWidget*>( other.parent() ) }
                    {
                        // setup checkbox and size
                        this->setCheckable(true);
                        this->setChecked( other.isChecked() );
                    
                        setMouseTracking(true);
                        setContentsMargins(8, 0, 8, 0);     // padding left-right for the handle
                    
                        // setup animation
                        m_handle_animation = other.m_handle_animation;
                    
                        // setup toggle switch color
                        m_handle_style = other.m_handle_style;
                        m_frame_style  = other.m_frame_style;
                    
                        // connect animation::finished
                        // to a slot that will notify
                        // the editor
                        connect( m_handle_animation, &QPropertyAnimation::finished, this, &ViewToggleSw::onAnimationFinished );
                    
                        // change the Animation->endValue()
                        // The ViewToggleSw.setChecked(bool) will
                        // be called from the editor
                        connect( this, &QCheckBox::toggled, this, &ViewToggleSw::setAnimDirection );
                    }
                    
                    
                    //------------------ copy assignment ---------------------------------
                    ViewToggleSw& ViewToggleSw::operator= (const ViewToggleSw& other)
                    {
                        if ( this == &other ) return *this;
                    
                        this->setChecked( other.isChecked() );
                    
                        // copy animation
                        this->m_handle_animation = other.m_handle_animation;
                    
                        // copy toggle switch colors
                        this->m_handle_style = other.m_handle_style;
                        this->m_frame_style  = other.m_frame_style;
                    
                        return *this;
                    }
                    
                    
                    bool ViewToggleSw::hitButton( const QPoint& pos ) const
                    {
                        return this->contentsRect().contains(pos);
                    }
                    
                    
                    void ViewToggleSw::setPainterBrushPen(QPainter* p, QPen& pen, QBrush& brush) const
                    {
                        p->setPen( pen );
                        p->setBrush( brush );
                    }
                    
                    
                    /*********************************
                     *
                     *            SLOTS
                     *
                     * *******************************/
                    void ViewToggleSw::setAnimDirection( bool checked )
                    { 
                        QPen handle_pen( BORDER_HANDLE_COL, PEN_WIDTH );
                    
                        if ( checked )
                        {
                            // handle pen and brush for paintEvent
                            m_handle_style =  std::make_pair( handle_pen, HANDLE_COL_CHECK );
                            // frame pen and brush
                            m_frame_style = std::make_pair( QPen(BORDER_COL_CHECK, PEN_WIDTH), BG_COL_CHECK );
                    
                        } else {
                            // handle pen and brush for paintEvent
                            m_handle_style = std::make_pair( handle_pen, HANDLE_COL_UNCHECK );
                            // frame pen and brush
                            m_frame_style = std::make_pair( QPen(BORDER_COL_UNCHECK, PEN_WIDTH), BG_COL_UNCHECK );
                        }
                    
                        // sets animation direction
                        m_handle_animation->stop();
                        m_handle_animation->setEndValue( checked ? 1.0 : 0.0 );
                        m_handle_animation->start();
                    }
                    
                    
                    void ViewToggleSw::onAnimationFinished()
                    {
                        qDebug() << "\nanimation completed...";
                        emit animationFinished();
                    }
                    
                    
                    /*********************************
                     *
                     *        PUBLIC METHODS
                     *
                     * *******************************/
                    QSize ViewToggleSw::sizeHint() const
                    {
                        return QSize(30, 60);
                    }
                    
                    
                    void ViewToggleSw::paint(
                        QPainter *painter,
                        const QRect &rect,
                        const QPalette &palette
                    ) const
                    {
                        Q_UNUSED(palette)
                    
                        // this method will be called within
                        // the Editor::paintEvent or
                        // QStyledItemDelegate::paint()
                        painter->setRenderHint(QPainter::Antialiasing, true);
                    
                        auto handle_radius = std::round( 0.24 * rect.height() );
                    
                        // width will vary depending on Height.. 11/13 * h
                        auto w_fx_h = std::round( (rect.height() * 11) / 13 );
                    
                        auto frame_slider = QRectF(
                            rect.x(), rect.y(),            // x, y
                            w_fx_h - handle_radius,       // width = total_width minus radius of handle
                            rect.height() * 0.40         // height = 40% of the frame_rect (analogus to 30% top & bottom padding)
                        );
                        frame_slider.moveCenter( QPointF(rect.center()) );
                    
                        auto border_radius = frame_slider.height() / 2;
                    
                        // the lenght that the handle will follow
                        auto handle_path_len = w_fx_h - handle_radius;
                    
                        // x offset where the origin of the handle
                        // will be drawn
                        auto x_position = frame_slider.x() +                        // x constant offset
                                          ( handle_path_len * m_handle_pos );      // % of path lenght run
                    
                        //! drawing //
                        // frame_rect's colors
                        auto  [pen, brush] = m_frame_style;
                        this->setPainterBrushPen( painter, pen, brush );
                        painter->drawRoundedRect( frame_slider, border_radius, border_radius );
                    
                        // handle's colors
                        auto [pen2, brush2] = m_handle_style;
                        this->setPainterBrushPen( painter, pen2, brush2 );
                        painter->drawEllipse(
                            QPointF( x_position, rect.center().y() ),  // ellipse center point
                            handle_radius,
                            handle_radius
                        );
                        painter->end();
                    }
                    

                    I asked google's AI (Gemini) to troubleshoot the possible reason and it said:

                    The errors "QPainter::save: Painter not active", "QPainter::setRenderHint: Painter must be active to set rendering hints", etc., within your ToggleSwitchDelegate::paint() method indicate that you're attempting to use a QPainter outside of a valid paint event context. Specifically, you are creating a local ViewToggleSw object and calling its paint method, but that paint method expects to be called by a QPainter that is already active (i.e., inside a widget's paintEvent).

                    I don't really get it.. Gemini suggested to directly paint it inside the paint event, but i'm using the ViewToggleSw also for painting the Editor that is an external class with a member variable ViewToggleSw toggle_sw and inside his paintEvent calls
                    QPainter painter(this);
                    toggle_sw.paint( painter, rect(), options);

                    1 Reply Last reply
                    0
                    • A Andrea_Venturelli

                      I did some work on the model and the basics works as expected; this is the result I have:

                      image.png

                      problem with column 4:

                      When I try to set the custom delegate on the column 4, I get some Console's messages saying the following:

                      QPainter::save: Painter not active
                      QPainter::setRenderHint: Painter must be active to set rendering hints
                      QPainter::setPen: Painter not active
                      QPainter::setBrush: Painter not active
                      QPainter::drawRects: Painter not active
                      QPainter::restore: Unbalanced save/restore
                      QPainter::save: Painter not active
                      

                      and if three items is given to the model, the view draws only the first and the others will not been drawn.

                      toggleswitchdelegate.h

                      #ifndef TOGGLESWITCHDELEGATE_H
                      #define TOGGLESWITCHDELEGATE_H
                      #include <QStyledItemDelegate>
                      #include "toggleswitcheditor.h"
                      
                      // implementation info
                      
                      // every time the editing is finished
                      // you must emit QStyledItemDelegate::commitData( pointer to Editor)
                      // and QStyledItemDelegate::closeEditor( pointer to Editor)
                      
                      
                      class ToggleSwitchDelegate: public QStyledItemDelegate
                      {
                          Q_OBJECT
                      public:
                          ToggleSwitchDelegate(QObject* parent);
                          //~ToggleSwitchDelegate();
                      
                          QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
                      
                          // editing releated methods
                          QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
                          void setEditorData(QWidget *editor, const QModelIndex &index) const override;
                          void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
                      
                          void paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
                      
                      private:
                          //mutable ViewToggleSw* toggle_sw;
                      
                      private slots:
                          void commitAndCloseEditor();
                      };
                      

                      toggleswitchdelegate.cpp

                      #include "toggleswitchdelegate.h"
                      #include "toggleswitcheditor.h"
                      
                      ToggleSwitchDelegate::ToggleSwitchDelegate(QObject* parent )
                          : QStyledItemDelegate(parent) //, toggle_sw( new ViewToggleSw( qobject_cast<QWidget*>(parent)) )
                      {}
                      
                      /*
                      ToggleSwitchDelegate::~ToggleSwitchDelegate()
                      {
                          delete toggle_sw;
                      }*/
                      
                      void ToggleSwitchDelegate::paint(
                          QPainter *painter,
                          const QStyleOptionViewItem &option,
                          const QModelIndex &index
                      ) const
                      {
                      
                          if ( index.data().canConvert<bool>() )
                          {
                              auto sw_state = qvariant_cast<bool>( index.data() );
                              ViewToggleSw toggle_sw;
                              toggle_sw.setChecked( sw_state );
                              //toggle_sw->setChecked( sw_state );
                      
                              // highlighted bg when selected
                              if ( option.state & QStyle::State_Selected )
                                  painter->fillRect( option.rect, option.palette.highlight() );
                      
                              // static paint of the widget
                              toggle_sw.paint( painter, option.rect, option.palette );
                              //toggle_sw->paint( painter, option.rect, option.palette );
                          }
                      }
                      
                      
                      QSize ToggleSwitchDelegate::sizeHint(
                          const QStyleOptionViewItem &option,
                          const QModelIndex &index
                      ) const
                      {
                          Q_UNUSED(option)
                      
                          if ( index.data().canConvert<bool>() )
                          {
                              ViewToggleSw toggle_sw;
                              return toggle_sw.sizeHint();
                          }
                      
                          return QSize();
                      }
                      
                      
                      QWidget* ToggleSwitchDelegate::createEditor(
                          QWidget* parent,
                          const QStyleOptionViewItem &option,
                          const QModelIndex &index
                      ) const
                      {
                          Q_UNUSED(option)
                      
                          if ( index.data().canConvert<bool>() )
                          {
                              ToggleSwitchEditor* editor = new ToggleSwitchEditor( parent );
                              connect( editor, &ToggleSwitchEditor::editingFinished, this, &ToggleSwitchDelegate::commitAndCloseEditor );
                              return editor;
                          }
                      
                          return nullptr;
                      }
                      
                      
                      void ToggleSwitchDelegate::setEditorData(
                          QWidget* editor,
                          const QModelIndex &index
                      ) const
                      {
                          if ( index.data().canConvert<bool>() )
                          {
                              auto sw_state = qvariant_cast<bool>( index.data() );
                              ViewToggleSw toggle_sw;
                              toggle_sw.setChecked( sw_state );
                      
                              auto* my_editor = qobject_cast<ToggleSwitchEditor*>( editor );
                              my_editor->setToggleSwitch( toggle_sw );
                          }
                      }
                      
                      
                      void ToggleSwitchDelegate::setModelData(
                          QWidget *editor,
                          QAbstractItemModel *model,
                          const QModelIndex &index
                      ) const
                      {
                          if ( index.data().canConvert<bool>() )
                          {
                              auto* my_editor = qobject_cast<ToggleSwitchEditor*>( editor );
                              model->setData( index, QVariant::fromValue(my_editor->viewToggleSw().isChecked()) );
                          }
                      }
                      
                      
                      /***************************
                       *
                       *          SLOTS
                       *
                       ***************************/
                      void ToggleSwitchDelegate::commitAndCloseEditor()
                      {
                          auto* editor = qobject_cast<ToggleSwitchEditor*>( sender() );
                          // this two signal come from QStyledItemDelegate
                          emit this->commitData( editor );
                          emit this->closeEditor( editor );
                      }
                      
                      JonBJ Online
                      JonBJ Online
                      JonB
                      wrote on last edited by JonB
                      #9

                      @Andrea_Venturelli said in How to use a QList of UserDefined class, as the Storage Model for QAbstractItemModel to be used by QTreeWidget:

                          toggle_sw.paint( painter, option.rect, option.palette );
                      

                      I hope I'm not wrong, and I have never done anything like you do, but I don't think this is right: you never explicitly call paint() on something else. [EDIT per later posts this is incorrect.] Qt infrastructure calls it when something needs painting. I think this will give rise to the errors you see. If you comment that out do the 7 errors go away?

                      Don't ask me what you're supposed to do, I'm not sure.

                      [This reply written before you typed your latest post, and refers to the one before.]

                      1 Reply Last reply
                      0
                      • A Offline
                        A Offline
                        Andrea_Venturelli
                        wrote on last edited by
                        #10

                        now i will try to better explain what should happen, simplifing the example a little bit.

                        instead of a toggle switch, take as assumption a QCheckBox instead:

                        • the delegate must draw a checked checkbox if the model value is true, otherwise must draw only the empty checkbox frame

                        • when the user click inside the cell, the editor should be called and an animation will start as soon as the user release the mouse's left button. when the animation::finished() signal is emitted, the editor catch it and emit "closeEditor()" for notifing the Delegate to setModelData and destroy the editor.

                        When I copied this code (suggested by Gemini), and commented out the line you suggested @JonB, the view correctly displayed the widget:

                        void ToggleSwitchDelegate::paint(
                            QPainter *painter,
                            const QStyleOptionViewItem &option,
                            const QModelIndex &index
                        ) const
                        {
                        
                            if ( index.data().canConvert<bool>() )
                            {
                                auto sw_state = qvariant_cast<bool>( index.data() );
                                ViewToggleSw toggle_sw;
                                toggle_sw.setChecked( sw_state );
                                //toggle_sw->setChecked( sw_state );
                        
                                // highlighted bg when selected
                                if ( option.state & QStyle::State_Selected )
                                    painter->fillRect( option.rect, option.palette.highlight() );
                        
                                // static paint of the widget
                                //toggle_sw.paint( painter, option.rect, option.palette );
                                //toggle_sw->paint( painter, option.rect, option.palette );
                                
                                // *** DRAW DIRECTLY - NO WIDGET INSTANCE ***
                                painter->setRenderHint(QPainter::Antialiasing, true);
                        
                                int handle_radius = std::round(0.24 * option.rect.height());
                                int w_fx_h = std::round((option.rect.height() * 11.0) / 13.0); // Use 11.0 and 13.0 for floating-point division
                        
                                QRectF frame_slider(option.rect.x(), option.rect.y(), w_fx_h - handle_radius, option.rect.height() * 0.40);
                                frame_slider.moveCenter(option.rect.center());
                        
                                int border_radius = frame_slider.height() / 2;
                                int handle_path_len = w_fx_h - handle_radius;
                                int x_position = frame_slider.x() + (handle_path_len * (sw_state ? 1.0 : 0.0));
                        
                                // Draw Frame (using colors from ViewToggleSw or define them here)
                                QPen framePen(sw_state ? QColor(152, 73, 249) : QColor(113, 113, 113), 3);
                                QBrush frameBrush(sw_state ? QColor(255, 255, 255) : QColor(207, 207, 207));
                                painter->setPen(framePen);
                                painter->setBrush(frameBrush);
                                painter->drawRoundedRect(frame_slider, border_radius, border_radius);
                        
                                // Draw Handle
                                QPen handlePen(QColor(81, 80, 102), 3);
                                QBrush handleBrush(sw_state ? QColor(161, 86, 253) : QColor(132, 131, 151));
                                painter->setPen(handlePen);
                                painter->setBrush(handleBrush);
                                painter->drawEllipse(QPointF(x_position, option.rect.center().y()), handle_radius, handle_radius);
                            }
                        }
                        

                        resulting in that:
                        ac11b1f1-9df8-4fc2-b570-9a33a985e60e-image.png

                        and it's okay but the animation does not work but i think it finish to fast and the editor is closed but the Delegate:paint() provided by Gemini doesn't take in account the inactive state.. I will try implementing that and see if it works.. but i rather prefer to calling ViewToggleSw::paint() instead in a similar way as described by the Qt's StarDelegate Example

                        JonBJ 1 Reply Last reply
                        0
                        • A Andrea_Venturelli

                          now i will try to better explain what should happen, simplifing the example a little bit.

                          instead of a toggle switch, take as assumption a QCheckBox instead:

                          • the delegate must draw a checked checkbox if the model value is true, otherwise must draw only the empty checkbox frame

                          • when the user click inside the cell, the editor should be called and an animation will start as soon as the user release the mouse's left button. when the animation::finished() signal is emitted, the editor catch it and emit "closeEditor()" for notifing the Delegate to setModelData and destroy the editor.

                          When I copied this code (suggested by Gemini), and commented out the line you suggested @JonB, the view correctly displayed the widget:

                          void ToggleSwitchDelegate::paint(
                              QPainter *painter,
                              const QStyleOptionViewItem &option,
                              const QModelIndex &index
                          ) const
                          {
                          
                              if ( index.data().canConvert<bool>() )
                              {
                                  auto sw_state = qvariant_cast<bool>( index.data() );
                                  ViewToggleSw toggle_sw;
                                  toggle_sw.setChecked( sw_state );
                                  //toggle_sw->setChecked( sw_state );
                          
                                  // highlighted bg when selected
                                  if ( option.state & QStyle::State_Selected )
                                      painter->fillRect( option.rect, option.palette.highlight() );
                          
                                  // static paint of the widget
                                  //toggle_sw.paint( painter, option.rect, option.palette );
                                  //toggle_sw->paint( painter, option.rect, option.palette );
                                  
                                  // *** DRAW DIRECTLY - NO WIDGET INSTANCE ***
                                  painter->setRenderHint(QPainter::Antialiasing, true);
                          
                                  int handle_radius = std::round(0.24 * option.rect.height());
                                  int w_fx_h = std::round((option.rect.height() * 11.0) / 13.0); // Use 11.0 and 13.0 for floating-point division
                          
                                  QRectF frame_slider(option.rect.x(), option.rect.y(), w_fx_h - handle_radius, option.rect.height() * 0.40);
                                  frame_slider.moveCenter(option.rect.center());
                          
                                  int border_radius = frame_slider.height() / 2;
                                  int handle_path_len = w_fx_h - handle_radius;
                                  int x_position = frame_slider.x() + (handle_path_len * (sw_state ? 1.0 : 0.0));
                          
                                  // Draw Frame (using colors from ViewToggleSw or define them here)
                                  QPen framePen(sw_state ? QColor(152, 73, 249) : QColor(113, 113, 113), 3);
                                  QBrush frameBrush(sw_state ? QColor(255, 255, 255) : QColor(207, 207, 207));
                                  painter->setPen(framePen);
                                  painter->setBrush(frameBrush);
                                  painter->drawRoundedRect(frame_slider, border_radius, border_radius);
                          
                                  // Draw Handle
                                  QPen handlePen(QColor(81, 80, 102), 3);
                                  QBrush handleBrush(sw_state ? QColor(161, 86, 253) : QColor(132, 131, 151));
                                  painter->setPen(handlePen);
                                  painter->setBrush(handleBrush);
                                  painter->drawEllipse(QPointF(x_position, option.rect.center().y()), handle_radius, handle_radius);
                              }
                          }
                          

                          resulting in that:
                          ac11b1f1-9df8-4fc2-b570-9a33a985e60e-image.png

                          and it's okay but the animation does not work but i think it finish to fast and the editor is closed but the Delegate:paint() provided by Gemini doesn't take in account the inactive state.. I will try implementing that and see if it works.. but i rather prefer to calling ViewToggleSw::paint() instead in a similar way as described by the Qt's StarDelegate Example

                          JonBJ Online
                          JonBJ Online
                          JonB
                          wrote on last edited by JonB
                          #11

                          @Andrea_Venturelli said in How to use a QList of UserDefined class, as the Storage Model for QAbstractItemModel to be used by QTreeWidget:

                          but i rather prefer to calling ViewToggleSw::paint() instead in a similar way as described by the Qt's StarDelegate Example

                          Ah, I now see that has

                          starRating.paint(painter, option.rect, option.palette,
                                                   StarRating::EditMode::ReadOnly);
                          

                          (called from a paint() event) so apparently that is OK, so I withdraw my earlier comment.

                          Going back then to your code. Warnings went away when you did not call ViewToggleSw::paint(). So look at that. Yours has no QPainter::begin() but has QPainter::end(). Hence warning messages for mismatch? Star Delegate's StarRating::paint() is enclosed with QPainter::save()/restore() instead. I think you should do that.

                          1 Reply Last reply
                          0
                          • A Offline
                            A Offline
                            Andrea_Venturelli
                            wrote on last edited by
                            #12

                            @JonB you were right and indeed makes sense not calling "painter.end()" because this will create problem to the painting of the second element and so on... using Painter::save() at the beginning and Painter::restore() at the end of the designated class to paint the obj is enoug like StarDelegate example provided by Qt's documentation Illustrates.

                            SOLUTION:

                            I found a easier a fastest way to change the state of a "togglable item" (like checkbox, toggle sw, ecc..) inside a Custom Delegate.
                            here the breakdown of the steps to follow:

                            1. Is always better and simpler to draw the "static version" of the delegate inside the QStyledItemDelegate() instead of creating a custom class and pass the delegate's painter to a custom method of a class that have the painting implementation details about how to drow the item.

                            2. You don't always follow the starDelegate example provided by Qt because is not suitable for every purpose as I just learned.

                            3. if you want to directly toggle an item without passing through the process "click the cell you want to toggle" >> "onother click or double-click to trigger the editor creation" >> "onother click to change the state inside the editor just created" , you can directly toggle the state with only one click and using one methods like so:

                            #ifndef TOGGLESWITCHDELEGATE_H
                            #define TOGGLESWITCHDELEGATE_H
                            #include <QStyledItemDelegate>
                            #include "viewtogglesw.h"
                            #include <QPaintEvent>
                            #include <QMouseEvent>
                            
                            class ToggleSwitchDelegate: public QStyledItemDelegate
                            {
                                Q_OBJECT
                            public:
                                ToggleSwitchDelegate(QObject* parent);
                                ~ToggleSwitchDelegate();
                            
                                QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
                            
                                void paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
                            
                                //* THIS IS THE MAGIC METHOD YOU WANT
                                bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;
                            private:
                                mutable ViewToggleSw* toggle_sw;
                            
                            };
                            
                            #endif // TOGGLESWITCHDELEGATE_H
                            
                            #include "toggleswitchdelegate.h"
                            
                            ToggleSwitchDelegate::ToggleSwitchDelegate(QObject* parent )
                                : QStyledItemDelegate(parent) , toggle_sw( new ViewToggleSw( qobject_cast<QWidget*>(parent)) )
                            {}
                            
                            
                            ToggleSwitchDelegate::~ToggleSwitchDelegate()
                            {
                               delete toggle_sw;
                            }
                            
                            
                            void ToggleSwitchDelegate::paint(
                                QPainter *painter,
                                const QStyleOptionViewItem &option,
                                const QModelIndex &index
                            ) const
                            {
                            
                                if ( index.data().canConvert<bool>() )
                                {
                                    auto is_checked = qvariant_cast<bool>( index.data() );
                            
                                    // *** DRAW DIRECTLY - NO WIDGET INSTANCE ***
                            
                                    painter->setRenderHint(QPainter::Antialiasing, true);
                            
                                    int handle_radius = std::round(0.24 * option.rect.height());
                                    int w_fx_h = std::round((option.rect.height() * 11.0) / 13.0); // Use 11.0 and 13.0 for floating-point division
                            
                                    QRectF frame_slider(option.rect.x(), option.rect.y(), w_fx_h - handle_radius, option.rect.height() * 0.40);
                                    frame_slider.moveCenter(option.rect.center());
                            
                                    int border_radius = frame_slider.height() / 2;
                                    int handle_path_len = w_fx_h - handle_radius;
                                    int x_position = frame_slider.x() + (handle_path_len * (is_checked ? 1.0 : 0.0));
                            
                                    // Draw Frame (using colors from ViewToggleSw or define them here)
                                    QPen framePen(is_checked ? QColor(152, 73, 249) : QColor(113, 113, 113), 3);
                                    QBrush frameBrush(is_checked ? QColor(255, 255, 255) : QColor(207, 207, 207));
                                    painter->setPen(framePen);
                                    painter->setBrush(frameBrush);
                                    painter->drawRoundedRect(frame_slider, border_radius, border_radius);
                            
                                    // Draw Handle
                                    QPen handlePen(QColor(81, 80, 102), 3);
                                    QBrush handleBrush(is_checked ? QColor(161, 86, 253) : QColor(132, 131, 151));
                                    painter->setPen(handlePen);
                                    painter->setBrush(handleBrush);
                                    painter->drawEllipse(QPointF(x_position, option.rect.center().y()), handle_radius, handle_radius);
                                }
                            }
                            
                            // MAGIC METHOD IMPLEMENTATION
                            bool ToggleSwitchDelegate::editorEvent(
                                QEvent *event,
                                QAbstractItemModel *model,
                                const QStyleOptionViewItem &option,
                                const QModelIndex &index
                            )
                            {
                                // checks if index is valid, colum is the fourth (in my case, the column where i will install the delegate on the view
                               // remember that first column as index.column() = 0 not 1, so Column 3 is the fourth
                               // in my case I chose to toggle the checkedState when the event is of type mouseReleaseEvent
                                bool idx_isValid = index.isValid();
                                bool isCol3_isTypeBool = ( index.column() == 3 && index.data().canConvert<bool>() );
                                bool isMouseReleaseEvent = ( event->type() == QEvent::MouseButtonRelease );
                                if ( idx_isValid && isCol3_isTypeBool && isMouseReleaseEvent )
                                {
                                    QMouseEvent* mouseEvent = static_cast<QMouseEvent*>( event );
                                    // if the mouse Click is inside the cell toggle the model value
                                    if ( option.rect.contains(mouseEvent->pos()) )
                                    {
                                    //  if the current value is true, set it to false and viceversa
                                        bool is_checked = model->data( index, Qt::DisplayRole ).toBool();
                                        model->setData( index, !is_checked, Qt::EditRole );
                                        return true; // needed to consume the event
                                    }
                            
                                    return true;
                                }
                            
                                return false;
                            }
                            
                            QSize ToggleSwitchDelegate::sizeHint(
                                const QStyleOptionViewItem &option,
                                const QModelIndex &index
                            ) const
                            {
                                Q_UNUSED(option)
                            
                                if ( index.data().canConvert<bool>() )
                                {
                                    return toggle_sw->sizeHint();
                                }
                            
                                return QSize();
                            }
                            
                            Important: see the comments inside the above code snippet for further explanations

                            here the code on the constructor of QWidget (could be of type QMainWindow also)

                            Widget::Widget(QWidget *parent)
                                : QWidget(parent)
                                , ui(new Ui::Widget)
                            {
                                ui->setupUi(this);
                            
                                PathInfo info(QString("C:\\Users\\some\\path\\to\\Desktop\\file.png"));
                                PathInfo info2(QString("C:\\Users\\123\\test\\documents"));
                                PathInfo info3(QString("C:\\Users\\test\\Tor Browser\\Browser\\distribution\\extensions"));
                            
                               // only for test, default is set to true
                                info3.setOpenable(false);
                            
                                QList<PathInfo> group;
                                group.append( info );
                                group.append( info2 );
                                group.append( info3 );
                            
                                paths_model = new PathsInfoTableModel( group, this );
                            
                                // hide the row headers and add wrap word if path is to long
                                ui->t_view->setModel( paths_model );
                                ui->t_view->verticalHeader()->hide();
                                ui->t_view->setWordWrap(true);
                                ui->t_view->verticalHeader()->setDefaultSectionSize(54);
                            
                                // setting toggle switch delegate for col 3
                                auto* toggle_sw_delegate = new ToggleSwitchDelegate( ui->t_view );
                                ui->t_view->setItemDelegateForColumn( 3, toggle_sw_delegate );
                            
                               // there is no needs of editTriggers because our magic method calls automatically setModel without creating an Editor
                                ui->t_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
                            
                                // this allow the user to double-click on the path cell and select a new one
                               // with the QFileDialog::getOpenFileName and update the model..
                                connect(
                                    ui->t_view, &QTableView::doubleClicked,
                                    this, [this] ( const QModelIndex& index ) {
                                        if ( !index.isValid() ) return;
                            
                                        if ( index.column() == 1 )
                                        {
                                            auto* model = qobject_cast<PathsInfoTableModel*>( ui->t_view->model() );
                                            if ( !model ) return;
                            
                                            QString current_path = model->data( index, Qt::DisplayRole ).toString();
                            
                                            // if the parent of current path exist, use
                                            // it as default location else, Home
                                            PathInfo path_info( current_path );
                                            auto default_path = (QFileInfo::exists(path_info.absPath())) ? path_info.absPath() : QDir::homePath();
                            
                                            QString newPath = QFileDialog::getOpenFileName(this, "Select new Path", default_path );
                                            if ( !newPath.isEmpty() && newPath != current_path )
                                            {
                                                QVariant newPathVariant = QVariant( newPath );
                                                model->setData( index, newPathVariant, Qt::EditRole );
                                            }
                                        }
                                    }
                                );
                            }
                            
                            1 Reply Last reply
                            0
                            • A Offline
                              A Offline
                              Andrea_Venturelli
                              wrote on last edited by
                              #13

                              Thanks to @JonB for always take part on my complicated, unusual problems and questions and for helping me.
                              I really appreciate it!

                              this is how the application look like now
                              image.png

                              soon I'll post the full code on my github at https://github.com/aVenturelli-qt so anyone interested in it can take a look.

                              1 Reply Last reply
                              0
                              • A Andrea_Venturelli has marked this topic as solved on

                              • Login

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