How to remove QTreeView indentation - qt

I want to have a QTreeView without an indentation on the left side increasing at each nesting level. I tried setting QTreeView::setIndentation(0). It removes the indentations just as I want, however it also hides the tree arrows.
Default behavior:
With indentations ✗
With arrows ✔
After setIndentation(0):
Without indentations ✔
Without arrows ✗
Desired behavior:
Without indentations ✔
With arrows ✔
So how can I achieve the result shown in the third example? Is there any standard way of doing it, or I will have to reimplement the QTreeView::paintEvent(), QTreeView::drawBranches(), etc.?

To solve the problem I used a delegate to translate the paint of the items, and paint the arrows.
#include <QtWidgets>
class BranchDelegate: public QStyledItemDelegate
{
public:
using QStyledItemDelegate::QStyledItemDelegate;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override{
QStyleOptionViewItem opt(option);
if(index.column() == 0)
opt.rect.adjust(opt.rect.height(), 0, 0, 0);
QStyledItemDelegate::paint(painter, opt, index);
if(index.column() == 0){
QStyleOptionViewItem branch;
branch.rect = QRect(0, opt.rect.y(), opt.rect.height(), opt.rect.height());
branch.state = option.state;
const QWidget *widget = option.widget;
QStyle *style = widget ? widget->style() : QApplication::style();
style->drawPrimitive(QStyle::PE_IndicatorBranch, &branch, painter, widget);
}
}
};
class TreeView: public QTreeView
{
public:
TreeView(QWidget *parent=nullptr):QTreeView(parent)
{
BranchDelegate *delegate = new BranchDelegate(this);
setItemDelegate(delegate);
setIndentation(0);
}
protected:
void mousePressEvent(QMouseEvent *event) override{
QModelIndex index = indexAt(event->pos());
bool last_state = isExpanded(index);
QTreeView::mousePressEvent(event);
if(index.isValid() && last_state == isExpanded(index))
setExpanded(index, !last_state);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TreeView w;
QFileSystemModel model;
model.setRootPath(QDir::rootPath());
w.setModel(&model);
w.setRootIndex(model.index(QDir::homePath()));
/*for (int i = 1; i< model.columnCount() ; ++i) {
w.hideColumn(i);
}*/
w.expandAll();
w.resize(640, 480);
w.show();
return a.exec();
}

eyllanesc's falls appart if there is horizontal scrolling. Also usually the view only expands/collapses when clicking the branch-indicator, not the index.
My Solution: Only change the Rect of indices which have a parent but no children. Also dont set the indentation to 0. No need to subclass QTreeView.
#include <QtWidgets>
class BranchDelegate: public QStyledItemDelegate
{
public:
mIndent = 50;
using QStyledItemDelegate::QStyledItemDelegate;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
QStyleOptionViewItem opt(option);
if(index.parent().isValid && (!index.model() || !index.model()->index(0, 0, index).isValid()))
{
opt.rect.adjust(-mIndent, 0, 0, 0);
}
QStyledItemDelegate::paint(painter, opt, index);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeView apView;
BranchDelegate* const apDelegate = new BranchDelegate(apView);
apDelegate->mIndent = 50;
apView->setIndentation(apDelegate->mIndent);
apView->setItemDelegateForColumn(0, apDelegate);
QFileSystemModel model;
model.setRootPath(QDir::rootPath());
apView.setModel(&model);
apView.setRootIndex(model.index(QDir::homePath()));
/*for (int i = 1; i< model.columnCount() ; ++i) {
apView.hideColumn(i);
}*/
apView.expandAll();
apView.resize(640, 480);
apView.show();
return a.exec();
}

ellyanesc's answer works, but is incorrect by one small detail in the line:
branch.rect = QRect(0, opt.rect.y(), opt.rect.height(), opt.rect.height());
The reason is because when the view is horizontally scrolled, option.rect.x() becomes negative. If branch.rect.x() is 0 (as in ellyanesc's answer), the branch indicator will always be shown, which also causes artifacts during scroll:
https://i.stack.imgur.com/O518A.png
https://i.stack.imgur.com/qYJMj.png
To solve this, replace the above line with:
branch.rect = QRect(option.rect.x(), opt.rect.y(), opt.rect.height(), opt.rect.height());
(I would have just pointed that out as a comment in ellyanesc's answer, but I don't have enough reputation for that.)

Related

QTableWidget, different selection colors for the different cells

I'd like to have the different selection colors for the different cells of the QTableWidget.
To change selection color for the whole table I can use
QTableWidget* table = new QTableWidget;
table->setStyleSheet("QTableWidget::item{selection-background-color:#ff0000;}");
To set the usual background color of the single cells it's possible to write in the following way:
table->setItem(row, column, new QTableWidgetItem(""));
table->item(row, column)->setBackgroundColor(QColor(255,255,0));
But I couldn't find any info about the different selection colors for the different cells.
Please, help!
it was a very interesting question for me and I wrote an example))
I use the delegate to resolve this issue
class MyDelegate : public QItemDelegate
{
public:
MyDelegate( QObject *parent ) : QItemDelegate( parent ) { }
void paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const;
};
void MyDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
QItemDelegate::paint( painter, option, index );
QColor background = QColor(rand()%255, rand()%255, rand()%255);
QColor background2 = QColor(255, 255, 255);
painter->fillRect(option.rect, background);
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, background);
} else {
painter->fillRect(option.rect, background2);
}
}
how will it work? with each selection, it will generate a new color for selected item
delegate integration
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTableWidget* m_pTableWidget = new QTableWidget();
m_pTableWidget->setRowCount(10);
m_pTableWidget->setColumnCount(10);
for (int i = 0; i < m_pTableWidget->rowCount(); ++i) {
m_pTableWidget->setItemDelegateForRow(i, new MyDelegate(m_pTableWidget));
}
m_pTableWidget->show();
return a.exec();
}
results
you can change the code and give a specific color for certain item .

How to immediately detect checkbox state change in a QTreeView?

I'm using Qt 5.9 on linux. I have a QTreeView where I set a custom model which is derived from QAbstractItemModel and I fill the tree with several plain classes for the tree items. Each treeview item has a checkbox next to it.
I have the following attribute set on the QTreeView ...
treeView->viewport()->setAttribute(Qt::WA_Hover);
so that when the mouse hovers over a tree item, I can capture the event via my delegates paint method.
The problem is that I also have a checkbox in each tree item and I'd like to be able to capture when the state of the checkbox changes, but my delegate doesn't seem to capture that. I can tell what state the checkbox is in when I hover the mouse over the item, but what I want is to be able to immediately know when the state of the checkbox changes w/o having to move the mouse any further.
Any thoughts on how to immediately detect when the state of the checkbox changes in a tree item?
A possible solution is to track the state change of the checkbox using the editorEvent method:
#include <QtWidgets>
class CheckboxDelegate: public QStyledItemDelegate{
Q_OBJECT
public:
using QStyledItemDelegate::QStyledItemDelegate;
bool editorEvent(QEvent *event,
QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index) override
{
Qt::CheckState last = static_cast<Qt::CheckState>(model->data(index, Qt::CheckStateRole).toInt());
bool res = QStyledItemDelegate::editorEvent(event, model, option, index);
Qt::CheckState current = static_cast<Qt::CheckState>(model->data(index, Qt::CheckStateRole).toInt());
if(last != current)
Q_EMIT stateChanged(index);
return res;
}
Q_SIGNALS:
void stateChanged(const QModelIndex & index);
};
#include "main.moc"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStandardItemModel model;
model.setColumnCount(2);
for(int i=0; i<4; ++i){
QList<QStandardItem *> l;
for (int c=0; c< model.columnCount(); ++c) {
QStandardItem *parent = new QStandardItem(QString("%1-%2").arg(i).arg(c));
parent->setCheckable(true);
l << parent;
for (int j=0; j<4; ++j) {
QList<QStandardItem *> ll;
for (int c=0; c< model.columnCount(); ++c) {
QStandardItem *child = new QStandardItem(QString("%1-%2-%3").arg(i).arg(j).arg(c));
child->setCheckable(true);
ll << child;
}
parent->appendRow(ll);
}
}
model.appendRow(l);
}
QTreeView w;
w.viewport()->setAttribute(Qt::WA_Hover);
CheckboxDelegate *delegate = new CheckboxDelegate(&w);
w.setItemDelegate(delegate);
QObject::connect(delegate, &CheckboxDelegate::stateChanged, [](const QModelIndex & index){
QString text = index.data().toString();
Qt::CheckState state = static_cast<Qt::CheckState>(index.data(Qt::CheckStateRole).toInt());
qDebug() << text << state;
});
w.setModel(&model);
w.resize(640, 480);
w.expandAll();
w.show();
return a.exec();
}

qstyleditemdelegate subclassing paint method not working right

I extended the qstyleditemview class. When I am in the editing mode for the qtreeview item, the paint method seems not to be executing right. When I change state to QStyle::State_Selected it works - it paints the selected row (text) in the qtreeview.
Any idea why it is not working in editing mode?
void myQItemDelegate::paint(QPainter *painter,const QStyleOptionViewItem &option,const QModelIndex &index) const
{
QStyleOptionViewItem s = *qstyleoption_cast<const QStyleOptionViewItem*>(&option);
if(s.state & QStyle::State_Editing)
{
painter->fillRect(s.rect, s.palette.highlight());
s.palette.setColor(QPalette::HighlightedText, QColor(Qt::blue));
}
QStyledItemDelegate::paint(painter, s, index);
}
In the State_Editing state the editor that is a widget created in createEditor() method is opened so that it will not be affected by the QStyleOptionViewItem palette.
Also instead of overwriting the paint method, use the initStyleOption() method:
#include <QtWidgets>
class StyledItemDelegate: public QStyledItemDelegate
{
public:
using QStyledItemDelegate::QStyledItemDelegate;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
QWidget * widget = QStyledItemDelegate::createEditor(parent, option, index);
QPalette pal(widget->palette());
pal.setColor(QPalette::HighlightedText, QColor(Qt::blue));
pal.setBrush(QPalette::Highlight, option.palette.highlight());
widget->setPalette(pal);
return widget;
}
protected:
void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override{
QStyledItemDelegate::initStyleOption(option, index);
option->palette.setColor(QPalette::HighlightedText, QColor(Qt::blue));
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeView w;
QStandardItemModel model;
w.setModel(&model);
w.setItemDelegate(new StyledItemDelegate);
for(int i=0; i<3; ++i){
auto it = new QStandardItem(QString::number(i));
model.appendRow(it);
for (int j=0; j<4; ++j) {
it->appendRow(new QStandardItem(QString("%1-%2").arg(i).arg(j)));
}
}
w.expandAll();
w.show();
return a.exec();
}
Thanks for helping me out.
I understand now. I added code in QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const metod to style QLineEdit. I am trying to get the same background color for QLineEdit when it is created- when I am editing qtreeview item. The problem is when I select the item in qtreeview the whole row gets colored. That's ok. Now when I edit the item for example to change the text in qtreeview item, only the text part gets selected and colored with the same color as previous row selection color. The rest of the QLineEdit is white. In editing mode I would like to color the whole row which is edited with the same color. I could as obviously from my code color it with RGB but I don't know the exact RGB values. Is there any way to get the exact RGB color from item selection and then use it in
pal.setColor(QPalette::Highlight,QColor(qRgb(0,0,255)));
my code:
QWidget* myQItemDelegate::createEditor(QWidget *parent,const QStyleOptionViewItem &option,const QModelIndex &index) const
{
QLineEdit *lineEdit = new QLineEdit(parent);
connect(lineEdit,SIGNAL(editingFinished()),this,SLOT(commitAndCloseEditor()));
QPalette pal;
pal.setColor(QPalette::HighlightedText, QColor(Qt::white));
pal.setColor(QPalette::Highlight,QColor(qRgb(0,0,255)));
lineEdit->setPalette(pal);
lineEdit->setFrame(false);
return lineEdit;
}
Thanks, Tom

QStyledItemDelegate custom widget drawn incorrect, when scrolling

One of my earlier Question deals with a customized QStyledItemDelegate containing a QWidget with two QLabels side by side.
I was really satisfied with the easy solution given by Joseph Ireland. Unfortunately, the given is solution is broken, but I didn't realized this immediately. If the QTableWidget containing my QStyledItemDelegate gets too small, the scrollbars are activated.
Now scrolling destroys the correct drawing of my cell elements. This seems to be some kind of update problem. I realized this, after I draw a rectangle around the viewport region of my QAbstractScollArea.
My table looks like the following, if you will scroll violently:
Cells contents are not drawn at the right place or seemingly not draw after all. Happens strangely for every second row. Even more the rectangle drawn around the viewport region of my QAbstractScrollArea is messed up. If the window is redrawn (hide/show window) everything is fine.
What might the solution to this kind of update/repaint problem? Maybe I need to repaint after scrolling is finished?
My adapted solution posted by Joseph Ireland was:
Header-File: TwoNumbersDelegate.h
#pragma once
#include <QStyledItemDelegate>
class QLabel;
class TwoNumbersDelegate : public QStyledItemDelegate {
public:
TwoNumbersDelegate(QObject* parent = nullptr);
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
private:
QLabel* mLeft;
QLabel* mRight;
QFrame* mFrame;
};
Source File: TwoNumbersDelegate.cpp
#include "TwoNumbersDelegate.h"
#include <QLabel>
#include <QPainter>
#include <QDebug>
#include <QHBoxLayout>
#include <QTableWidget>
TwoNumbersDelegate::TwoNumbersDelegate(QObject* parent /*= nullptr*/) : QStyledItemDelegate(parent)
{
mLeft = new QLabel("%1");
mRight = new QLabel("%2");
mFrame = new QFrame;
mFrame->setLayout(new QHBoxLayout);
mFrame->layout()->addWidget(mLeft);
// you could add a spacer here maybe
mFrame->layout()->addWidget(mRight);
mFrame->setAttribute(Qt::WA_DontShowOnScreen, true);
mFrame->show();
}
void TwoNumbersDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
auto data = index.data(Qt::EditRole);
auto list = data.toList();
if (list.size() != 2) {
QStyledItemDelegate::paint(painter, option, index);
}
mLeft->setText(list.at(0).toString());
mRight->setText(list.at(1).toString());
mFrame->resize(option.rect.size());
qDebug() << option.rect.size();
// mFrame->layout()->invalidate();
// mFrame->layout()->activate();
if (auto tableWidget = qobject_cast<QTableWidget*>(parent())) {
auto cellRegion = QRegion(option.rect);
auto viewportRegion = QRegion(tableWidget->viewport()->rect());
auto intersectedRegion = cellRegion.intersected(viewportRegion);
intersectedRegion.translate(-option.rect.topLeft());
painter->drawRect(tableWidget->viewport()->rect().adjusted(0,0,-1,-1));
painter->save();
painter->translate(option.rect.topLeft());
mFrame->render(painter, QPoint(), intersectedRegion, QWidget::DrawChildren);
qDebug() << cellRegion << viewportRegion << intersectedRegion;
painter->restore();
}
}
QSize TwoNumbersDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
return mFrame->minimumSizeHint();
}
The following program served me as a test runner:
#include <QApplication>
#include <QTableWidget>
#include <QVBoxLayout>
#include "TwoNumbersDelegate.h"
int main(int argc, char** args) {
QApplication q(argc, args);
auto frame = new QFrame;
frame->setLayout(new QVBoxLayout);
//frame->setStyleSheet("QFrame { background: green}"); // Activate this to see overdrawing
auto table = new QTableWidget;
table->setAlternatingRowColors(true);
table->setItemDelegate(new TwoNumbersDelegate);
table->setRowCount(20);
table->setColumnCount(10);
for (auto iter = 0; iter < 20; iter++) {
for (auto colIter = 0; colIter < 10; colIter++) {
auto item = new QTableWidgetItem;
QVariantList map;
map << iter << iter*colIter;
item->setData(Qt::EditRole, map);
table->setItem(iter, colIter, item);
}
}
frame->layout()->addWidget(table);
frame->show();
q.exec();
}

Qt5 Subclassing QStyledItemDelegate Formatting

I am developing a GUI for a SQLite database in Qt5. I use QSqlQueryModel and QTableView for storing and displaying the data.
I then created a custom delegate to replace the numeric values of certain columns with their literals in the table view (e.g. 1 = "Hello", 2 = "World") using a switch statement.
The delegate displays the data as it should and works functionally. However, the columns that the custom delegate paints over have a different format compared to the default paint method of QStyledItemDelegate. The values are up in the top left rather than centre left, the altered column no longer automatically expands the column to display the full values, and the cells in column do not turn blue or have the dotted outline when selected.
I created this example program:
#include <QApplication>
#include <QModelIndex>
#include <QPainter>
#include <QStandardItemModel>
#include <QStyledItemDelegate>
#include <QTableView>
class TestDelegate: public QStyledItemDelegate {
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
const Q_DECL_OVERRIDE
{
if (index.column() == 0) {
int value = index.model()->data(index, Qt::DisplayRole).toInt();
QString str;
switch (value) {
case 1:
str = "Hello0000";
break;
case 2:
str = "World0000";
break;
}
if (option.state.testFlag (QStyle::State_Selected)) {
painter->fillRect(option.rect, option.palette.highlight());
qApp->style()->drawItemText(painter, option.rect, option.displayAlignment, option.palette, true, str, QPalette::HighlightedText);
} else {
painter->drawText(option.rect, option.displayAlignment, str);
}
} else {
return QStyledItemDelegate::paint(painter, option, index);
}
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
QStandardItemModel model(2, 2);
model.setHorizontalHeaderItem(0, new QStandardItem(QString("A")));
model.setHorizontalHeaderItem(1, new QStandardItem(QString("B")));
model.setData(model.index(0, 0, QModelIndex()), 1);
model.setData(model.index(1, 0, QModelIndex()), 2);
model.setItem(0, 1, new QStandardItem(QString("Hello")));
model.setItem(1, 1, new QStandardItem(QString("World0000")));
QTableView view;
view.setItemDelegate(new TestDelegate);
view.setModel(&model);
view.resizeColumnsToContents();
view.show();
app.exec();
}
This fixes the text alignment by adding options.displayAlignment to painter->drawText(); I have also added additional code in the if(option.state & QStyle::State_Selected) statement that paints the cell according to its selection state. So if it isn't selected the text is black, if it is the text turns white and the background blue. However I still cannot get the columns to expand to fit the cells' content or add a dotted line around the outside of the cell as it does with the standard delegate.
Is there a simple way to maintain the default style of the table view when using my custom paint method?
The delegate is a rather circuitous and unnecessary way of going about it. We already have a view that paints the elements perfectly fine, no need to redo that. We only need to pass modified data to the view. Thus we insert a QIdentityProxyModel viewmodel between the source and the view.
// https://github.com/KubaO/stackoverflown/tree/master/questions/proxy-reformat-39244309
#include <QtWidgets>
class RewriteProxy : public QIdentityProxyModel {
QMap<QVariant, QVariant> m_read, m_write;
int m_column;
public:
RewriteProxy(int column, QObject * parent = nullptr) :
QIdentityProxyModel{parent}, m_column{column} {}
void addReadMapping(const QVariant & from, const QVariant & to) {
m_read.insert(from, to);
m_write.insert(to, from);
}
QVariant data(const QModelIndex & index, int role) const override {
auto val = QIdentityProxyModel::data(index, role);
if (index.column() != m_column) return val;
auto it = m_read.find(val);
return it != m_read.end() ? it.value() : val;
}
bool setData(const QModelIndex & index, const QVariant & value, int role) override {
auto val = value;
if (index.column() == m_column) {
auto it = m_write.find(value);
if (it != m_write.end()) val = it.value();
}
return QIdentityProxyModel::setData(index, val, role);
}
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QStandardItemModel model{2,2};
model.setData(model.index(0, 0), 1);
model.setData(model.index(1, 0), 2);
model.setData(model.index(0, 1), "Zaphod");
model.setData(model.index(1, 1), "Beeblebrox");
RewriteProxy proxy{0};
proxy.setSourceModel(&model);
proxy.addReadMapping(1, "Hello");
proxy.addReadMapping(2, "World");
QTableView ui;
ui.setModel(&proxy);
ui.show();
return app.exec();
}

Resources