Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Highlighting selected row to be Bold in QTreeView



  • Hi,
    I'm working on highlighting a selected row in QTreeView bold when double clicked or to be specific through using index of that row selected. As this is an old issue and there are multiple solutions online that I found, I started using QStyledItemDelegate by sub-classing it and using it's paint function.
    In my main window, I have separate class "TreeView" which holds QTreeView widget and in that I'm calling another class "FileSystem" that has sub-classed QStyleItemDelegate.
    Here is the code of TreeView:
    TreeView.h

    #include <QFileSystemModel>
    #include <QModelIndex>
    #include <QDebug>
    #include <QStyledItemDelegate>
    
    #include "filesystem.h"
    namespace Ui {
    class TreeView;
    }
    
    class TreeView : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit TreeView(QWidget *parent = nullptr);
        ~TreeView();
        void openFile(const QString& folderPath);
        QFileSystemModel *myModel;
        FileSystem *ite;
    
    public slots:
        void onClick(const QModelIndex&);
    
    private:
        Ui::TreeView *ui;
    };
    
    #endif // TREEVIEW_H
    

    TreeView.cpp

    #include "TreeView.h"
    #include "ui_TreeView.h"
    
    TreeView::TreeView(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::TreeView)
    {
        ui->setupUi(this);
        myModel = new QFileSystemModel;
        ite = new FileSystem;
        connect( ui->treeView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT( onClick(const QModelIndex&) ) );
    }
    
    TreeView::~TreeView()
    {
        delete ui;
        delete ite;
        delete myModel;
    }
    
    void TreeView::openFile(const QString& folderPath)
    
    {
    myModel->setRootPath(folderPath);
    QModelIndex index = myModel->index(folderPath);
    myModel->setFilter(QDir::NoDotAndDotDot | QDir::AllDirs);
    ui->treeView->setModel(myModel);
    ui->treeView->setRootIndex(index);
    ui->treeView -> expand(index);
    
    }
    
    void TreeView::onClick(const QModelIndex &index)
    {
        ui->treeView->setItemDelegateForRow(index.row(),ite);
    }
    

    FileSystem.h

    #ifndef FILESYSTEM_H
    #define FILESYSTEM_H
    #include <QFileSystemModel>
    #include <QStyledItemDelegate>
    class FileSystem: public QStyledItemDelegate
    {
    public:
        FileSystem();
        QStyledItemDelegate *item;
        void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    };
    
    #endif // FILESYSTEM_H
    
    

    FileSystem.cpp

    #include "filesystem.h"
    #include <QFont>
    #include <QDebug>
    #include <QBrush>
    FileSystem::FileSystem()
    {
      item = new QStyledItemDelegate;
    }
    
    void FileSystem::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
        QStyleOptionViewItem opt = option;
        initStyleOption(&opt, index);
        opt.font.setBold(true);
    
        QStyledItemDelegate::paint(painter, opt, index);
    }
    

    So I'm using QAbstractItemView's function "setItemDelegateForRow(index.row(), ite)" to set custom style item delegate for that specific row. Now their are two problems I'm facing:
    1- As I'm using index.row(), it returns integer number of row which is same for the treeView child rows and all those child rows also get highlighted. Is there any option that can segregate parent from their child rows, please see image below
    1.PNG

    2- When I select another row, the previous one selected needs to get un-bold or back to default view. Is their a way to unset my custom item delegate on previously selected row

    I'm not sure if this is the best way to do it so I'm open to suggestions also. Thanks


  • Moderators

    You can probably simply use Qt::ItemDataRole::FontRole and return a bold font (in model::data() method) for the row you want to be bold..



  • @sierdzio Thanks for your quick response, do you mean that to sub-class QFileSystemModel and use it's QFileSystemModel::data function to implement QT:FontRole?

    Update: corrected typo, changed QT::DisplayRole to QT::FontRole


  • Moderators

    @sogo said in Highlighting selected row to be Bold in QTreeView:

    @sierdzio Thanks for your quick response, do you mean that to sub-class QFileSystemModel and use it's QFileSystemModel::data function to implement QT:FontRole?

    Update: corrected typo, changed QT::DisplayRole to QT::FontRole

    Yes, exactly.



  • @sierdzio I also looked into it here to see how I can sub-class and modify QFileSystemModel::data() method Model/View Tutorial and tried changing my FileSystem class in this way:
    FileSystem.h

    #ifndef FILESYSTEM_H
    #define FILESYSTEM_H
    #include <QFileSystemModel>
    
    class FileSystem: public QFileSystemModel
    {
    public:
        FileSystem();
        QVariant data(const QModelIndex &index, int role) const;
        QFileSystemModel *model;
    };
    
    #endif // FILESYSTEM_H
    

    FileSystem.cpp

    #include "filesystem.h"
    #include <QFont>
    #include <QDebug>
    #include <QBrush>
    FileSystem::FileSystem()
    {
      model = new QFileSystemModel;
    }
    
    QVariant FileSystem::data(const QModelIndex &index, int role) const
    {
        int row = index.row();
        int col = index.column();
        // generate a log message when this method gets called
        qDebug() << QString("row %1, col%2, role %3")
                .arg(row).arg(col).arg(role);
    
        switch (role) {
        case Qt::DisplayRole:
            if (row == 0 && col == 1) return QString("<--left");
            if (row == 1 && col == 1) return QString("right-->");
    
            return QString("Row%1, Column%2")
                    .arg(row + 1)
                    .arg(col +1);
        case Qt::FontRole:
            if (row == 0 && col == 0) { //change font only for cell(0,0)
                QFont boldFont;
                boldFont.setBold(true);
                return boldFont;
            }
            break;
        }
        return QVariant();
    }
    
    
    Now I have changed in my TreeView.h class to use FileSystem as the model. The issue with this one is I'm operating on separate rows, isn't the model applied to whole TreeView? The advantage I saw style item delegate was that I can do it for one row. Also do I have to re-implement all the QFileSystem:data() method as if I remove the QT::DisplayRole from above code, the output treeView shows empty with just arrow icons.

  • Moderators

    @sogo said in Highlighting selected row to be Bold in QTreeView:

    Now I have changed in my TreeView.h class to use FileSystem as the model. The issue with this one is I'm operating on separate rows, isn't the model applied to whole TreeView?

    The model is all QTreeView "knows", that's where it gets all the data from.

    The data() method is called by the view per item when it needs to read data. So, whenever you scroll, highlight, modify, update the view (or change the model), data() will be called - multiple times. View calls it foe every cell which was affected by the change.

    The advantage I saw style item delegate was that I can do it for one row. Also do I have to re-implement all the QFileSystem:data() method as if I remove the QT::DisplayRole from above code, the output treeView shows empty with just arrow icons.

    No you don't need to reimplement QFileSystem:data(), just simply call it ;-)

    QVariant FileSystem::data(const QModelIndex &index, int role) const
    {
      const bool shouldBeBold = (row == 0 && col == 0);
      if (role == Qt::FontRole && shouldBeBold) {
        Font boldFont;
        boldFont.setBold(true);
        return boldFont;
      } else {
        return QFileSystem::data(index, role); // This will call the original QFileSystem::data method
      }
    }
    


  • @sierdzio
    Before you confuse @sogo, you accidentally omitted the return in front of QFileSystem:data(index, role); :)


  • Moderators

    @JonB said in Highlighting selected row to be Bold in QTreeView:

    @sierdzio
    Before you confuse @sogo, you accidentally omitted the return in front of QFileSystem:data(index, role); :)

    Good catch, thanks :D



  • @sierdzio This. Do this.


  • Moderators



  • Hi @sierdzio Oh this is new for me, please give me sometime to check and play with it a little and see how can I implement bold row on double click on any row and if I have any question, I'll ping here. Here is the updated code, were some issues in there:

    QVariant FileSystem::data(const QModelIndex &index, int role) const
    {
      int row = index.row();
      int col = index.column();
      const bool shouldBeBold = (row == 0 && col == 0);
      if (role == Qt::FontRole && shouldBeBold) {
        QFont boldFont;
        boldFont.setBold(true);
        return boldFont;
      } else {
        return QFileSystemModel::data(index, role); // This will call the original QFileSystem::data method
      }
    }
    

    @JonB thanks for your update too

    I have one question though, I was reading the QFileSystemModel source code and I didn't see any case of QT::FontRole in qfilesystemmodel.cpp "::data" method. What confuses me is that in this code this statement "if (role == QT::FontRole && shouldBeBold)", how is it checking this.


  • Moderators

    @sogo said in Highlighting selected row to be Bold in QTreeView:

    I have one question though, I was reading the QFileSystemModel source code and I didn't see any case of QT::FontRole in qfilesystemmodel.cpp "::data" method.

    In general, this flag is rarely used. I think most of the time, delegates just take default font from QPalette / QSS.

    What confuses me is that in this code this statement "if (role == QT::FontRole && shouldBeBold)", how is it checking this.

    I don't understand your question, sorry. How is what checking this?



  • @sogo
    Every model inherits https://doc.qt.io/qt-5/qabstractitemmodel.html#data. The Qt infrastructure (e.g. QTreeView) calls the model's data() method multiple times (behind the scenes), passing in the various roles listed in https://doc.qt.io/qt-5/qt.html#ItemDataRole-enum, to get all the information it wants from the model to display each item as desired. One of the roles it passes in is FontRole. Your override of data() returns a bold font if and only if you want it to be bold. In the code proposed, that is when row == 0 && col == 0, so that item (only) comes out in bold.



  • What I mean't is we are not giving "role" as argument to call the function "QFileSystemModel::data", how does the program knows if role is equal to Qt::FontRole. Does the QFileSystemModel:data function goes to check through all the Qt::ItemDataRole?



  • @sogo
    Your post has crossed with my latest. Read that and see if you follow now? Yes, behind the scenes it calls your data() many times, with different values for role. You can put in a qDebug() statement, to see how many times it is called, with which row/column and with various values for role.



  • Hi @JonB
    Yea, now I get it. Thanks a lot for clarification


  • Moderators

    @sogo said in Highlighting selected row to be Bold in QTreeView:

    What I mean't is we are not giving "role" as argument to call the function "QFileSystemModel::data", how does the program knows if role is equal to Qt::FontRole. Does the QFileSystemModel:data function goes to check through all the Qt::ItemDataRole?

    const QModelIndex &index, int role role is there, it's the second argument.

    If you call data() manually and skip the role, it defaults to Qt::DisplayRole. But the view (QTreeView and others) will call data() with other roles, too, don't worry. If you want to force it (as you should in case of your double click thingy), emit dataChanged() for the row which should be bold.



  • @sierdzio said in Highlighting selected row to be Bold in QTreeView:

    If you call data() manually and skip the role, it defaults to Qt::DisplayRole.

    I'm not sure but whenever I have to call it manually, I have to give Qt::ItemDataRole as arg.. For now this is what I tried in my TreeView.cpp code:

    #include "TreeView.h"
    #include "ui_TreeView.h"
    
    TreeView::TreeView(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::TreeView)
    {
        ui->setupUi(this);
        myModel = new FileSystem;
        connect( ui->treeView, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT( onClick(const QModelIndex&) ) );
    }
    
    TreeView::~TreeView()
    {
        delete ui;
    }
    
    void TreeView::openFile(const QString& folderPath)
    
    {
    myModel->setRootPath(folderPath);
    QModelIndex index = myModel->index(folderPath);
    myModel->setFilter(QDir::NoDotAndDotDot | QDir::AllDirs);
    ui->treeView->setModel(myModel);
    ui->treeView->setRootIndex(index);
    ui->treeView -> expand(index);
    }
    
    void TreeView::onClick(const QModelIndex &index)
    {
        myModel->data(index,Qt::DisplayRole);
    
    }
    

    @sierdzio said in Highlighting selected row to be Bold in QTreeView:

    will call data() with other roles, too, don't worry. If you want to force it (as you should in case of your double click thingy), emit dataChanged() for the row which should be bold

    I also tried tried creating new signal which will emit dataChanged(const QModelIndex&) and emit when double clicked signal:

    void TreeView::onClick(const QModelIndex &index)
    {
       emit dataChanged(index);
    }
    

    I'm not sure how to catch this signal in my FileSystem class so that I can connect it with data function. Another issue is if I change hard code row and columns in FileSystem::data function, it calls it everytime to populate tree view which causes rows and columns to be bold out when directory is loaded. This part.

    int row = index.row();
      int col = index.column();
      const bool shouldBeBold = (row && col);
      if (role == Qt::FontRole && shouldBeBold) {
    

  • Moderators

    You need to change the way you think about the view and the model a bit, I think.

    In Qt model-view framework, there is a clear separation: the model is "the boss". The view is just a dumb "output" for data provided by the model.

    So for example, do not do this:

    void TreeView::onClick(const QModelIndex &index)
    {
        myModel->data(index,Qt::DisplayRole);
    }
    

    The data has not changed, so calling data() here is pointless! Instead, tell the model which row was clicked and update the data inside of the model. I mean something like:

    // view
    void TreeView::onClick(const QModelIndex &index)
    {
        myModel->rowClicked(index);
    }
    
    // model
    
    void FileModel::rowClicked(const QModelIndex &index)
    {
      // m_boldRow is some private member variable you add to your model class
      // Probably better to use QPersistentIndex here!
      m_boldRow = index; 
      emit dataChanged(index, index, Qt::FontRole);
    }
    
    QVariant FileSystem::data(const QModelIndex &index, int role) const
    {
      const bool shouldBeBold = (index == m_boldRow);
      if (role == Qt::FontRole && shouldBeBold) {
        QFont boldFont;
        boldFont.setBold(true);
        return boldFont;
      } else {
        return QFileSystemModel::data(index, role); // This will call the original QFileSystem::data method
      }
    }
    


  • I see, I was trying something similar next i.e. catching the QModelIndex in FileSystem class in separate function and changing the condition inside the data function but I wasn't sure how to update the model. If I'm not wrong, the emit dataChanged() signal is doing that. Thanks for this, this is working. Closing this thread



  • @sogo A simple solution is to override the initStyleOption method of QStyledItemDelegate:

    class StyledItemDelegate: public QStyledItemDelegate{
    public:
        using QStyledItemDelegate::QStyledItemDelegate;
    protected:
        void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const {
            QStyledItemDelegate::initStyleOption(option, index);
            if(option->state & QStyle::State_Selected){
                option->font.setBold(true);
            }     
        }
    };
    

Log in to reply