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
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
-
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.. -
@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.
-
@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 } }
-
@JonB said in Highlighting selected row to be Bold in QTreeView:
@sierdzio
Before you confuse @sogo, you accidentally omitted thereturn
in front ofQFileSystem:data(index, role);
:)Good catch, thanks :D
-
-
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.
-
@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'sdata()
method multiple times (behind the scenes), passing in the variousrole
s 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 therole
s it passes in isFontRole
. Your override ofdata()
returns a bold font if and only if you want it to be bold. In the code proposed, that is whenrow == 0 && col == 0
, so that item (only) comes out in bold. -
-
@sogo
Your post has crossed with my latest. Read that and see if you follow now? Yes, behind the scenes it calls yourdata()
many times, with different values forrole
. You can put in aqDebug()
statement, to see how many times it is called, with which row/column and with various values forrole.
-
@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 toQt::DisplayRole
. But the view (QTreeView and others) will calldata()
with other roles, too, don't worry. If you want to force it (as you should in case of your double click thingy), emitdataChanged()
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) {
-
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