QStyledItemDelegate + QTreeView::setIndenation causes glitches on text drawing - qt

It has been two days I can not get the solution.
I have a custom QStyledItemDelegate and a QTreeView. QTreeView's indentation set to 12. And QStyledItemDelegate's QStyleOptionViewItem::rect set to -12.
This set up causes glitches when I change the color of the QTreeView item's text.
CODE
void MyItemViewDelegate::initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const
{
if (m_functor->operator ()(index))
{
option->rect.adjust(-12, 0, 0, 0);
}
}
MyTreeView::MyTreeView(QWidget* parent)
: QTreeView(parent)
{
setIndentation(12);
}
What did I miss?
Somehow it relates with QTreeView's viewport.

So What I forgot to do was to override:
QRect QTreeView::visualRect(const QModelIndex& index) const
{
if (m_functor->operator ()(index))
{
return QTreeView::visualRect(index).adjusted(-m_indentation, 0, 0, 0);
}
return QTreeView::visualRect(index);
}

Related

Why doesn't child QGraphicsObject repaint its background correctly

The QGraphicsObject in green rectangle, it is the parent of the QGraphicsObject in red rectangle.
childInRed->setParentItem(this);
When I drag the parent object in green rect and move it fast, the background of the child object in red rect is not repainted correctly.
I know I can use update in the parent's mouseMoveEvent force the child to repaint. But this is not good, because I don't need to repaint the parent at all.
#include "asdf.h"
#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsView>
#include <QtWidgets>
class CTestGraphicsObject : public QGraphicsObject
{
public:
QColor m_c;
CTestGraphicsObject(QColor c)
: QGraphicsObject(NULL)
, m_c(c)
{
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsFocusable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
auto effect = new QGraphicsDropShadowEffect;
effect->setOffset(4, 4);
effect->setBlurRadius(20);
setGraphicsEffect(effect);
}
virtual QRectF boundingRect() const override
{
auto rc = QRectF(0, 0, 100, 100);
return rc;
}
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
painter->setPen(QPen(m_c));
painter->drawRect(this->boundingRect());
}
};
asdf::asdf(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
auto s = new QGraphicsScene(this);
auto v = new QGraphicsView;
v->setScene(s);
CTestGraphicsObject* pParent = new CTestGraphicsObject(Qt::green);
CTestGraphicsObject* pChild = new CTestGraphicsObject(Qt::red);
pChild->setParentItem(pParent);
pChild->setPos(0, 100);
s->addItem(pParent);
s->addItem(pChild);
QVBoxLayout* l = new QVBoxLayout(this->centralWidget());
l->addWidget(v);
}
asdf::~asdf()
{
}
The QGraphicsDropShadowEffect causes this problem, It seems I'm not using it in right way.
According to the Qt documentation, the scene uses the bounding rect and region to define the area to repainted when an item is updated (moved in your case).
If you child is outside its parent, the scene will miss some part when repainting...
Extend the bouding rect/region to cover its children.
If you do something like that, it will work:
virtual QRectF boundingRect() const override
{
if (this->childrenBoundingRect().isEmpty()) // No children
return QRectF(0, 0, 100, 100);
return QRectF(0, 0, 100, 100).united(this->childrenBoundingRect());
}
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
painter->setPen(QPen(m_c));
painter->drawRect(QRectF(0, 0, 100, 100));
}

QTreeWidgetItem color

I am using the following stylesheet on a QTreeWidget to change the items style:
QTreeWidget::item
{
padding-left:10px;
padding-top: 1px;
padding-bottom: 1px;
border-left: 10px;
}
After that, I am trying to use the following code to change the color of some specific cells:
// item is a QTreeWidgetItem
item->setBackgroundColor(1, QColor(255, 129, 123));
But the color is not changing. I then discovered that, if I remove the stylesheet from the QTreeWidget, then the color change works.
Any idea how to make the background color change to work keeping the stylesheet?
Use a custom delegate to paint your items instead of stylesheets.
Reimplement the paint() method to control the way how are the items drawn:
class CMyDelegate : public QStyledItemDelegate
{
public:
CMyDelegate(QObject* parent) : QStyledItemDelegate(parent) {}
void CMyDelegate::paint(QPainter* painter, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
}
void CMyDelegate::paint(QPainter* painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QStyleOptionViewItemV4 itemOption(option)
initStyleOption(&itemOption, index);
itemOption.rect.adjust(-10, 0, 0, 0); // Make the item rectangle 10 pixels smaller from the left side.
// Draw your item content.
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &itemOption, painter, nullptr);
// And now you can draw a bottom border.
painter->setPen(Qt::black);
painter->drawLine(itemOption.rect.bottomLeft(), itemOption.rect.bottomRight());
}
And this is how to use your delegate:
CMyDelegate* delegate = new CMyDelegate(tree);
tree->setItemDelegate(delegate);
More documentation here: http://doc.qt.io/qt-5/model-view-programming.html#delegate-classes

QToolButton with icon + text: How to center both?

I am using multiple QToolButtons in a custom QGridLayout widget. The buttons are set to display icon + text based on an assigned default QAction. The only issue is that the content (icon + text) is always left-aligned.
The content (icon + text, marked as a red box in the screenshot), should be center in the button (indicated by the blue box).
For most cases this is just fine, given that Qt automatically tries to render that button with the minimal size. However I am stretching the button to fit nicely into my QGridLayout.
QToolButton* pButton = new QToolButton(0);
pButton->addDefaultAction(pAction);
pButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
pButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QGridLayout *pActionAreaLayout = new QGridLayout;
pActionAreaLayout->addWidget(pSomeOtherWidget, 0, 0, 1, 2);
pActionAreaLayout->addWidget(pButton , 1, 0, 1, 1);
Is there a way to force the content to be centered in the button?
PS: I found the following comment in another forum, which however seems quite invasive and is not really clear to me yet:
You can try doing the horizontal alignment using a stylesheet, but you probably have to implement a QStyle proxy and reimplement drawControl() for QStyle::CE_ToolButtonLabel
Or derive from QToolButton, overwrite paintEvent() and call the style for everything other than the label.
As I suggest in answer to you another question. https://stackoverflow.com/a/28630318/1917249 Do not use QToolButton, just use QPushButton, and add popup menu if needed.
Then you wont have different sizes of QToolButton and QPushButton widgets. And you will have centered icon and text.
Popupmenu can be easily added to QPushButton ( only small arrow wont be shown )
QPushButton *pushButton = new QPushButton(toolAction->icon(), "PushButton", window);
// window - widget where button is placed ( to get correct QMenu position )
QObject::connect(pushButton, &QPushButton::released, [window, pushButton, action](){
QMenu menu;
menu.addAction(action);
QPoint pos = window->mapToGlobal(pushButton3->pos());
pos += QPoint(0, pushButton->height());
menu.exec(pos);
});
Or you can subclass QPushButton and add Popup menu handling there. Much better then try to center text with icon in QToolButton or have in same size of QPushButton and QToolButton
For complex example please see my answer: https://stackoverflow.com/a/28630318/1917249
The following class does the job for me:
class CenteredToolButtonStyle : public QProxyStyle
{
Q_OBJECT
public:
CenteredToolButtonStyle(QToolButton* b, const QSize& sIcon);
virtual void drawItemPixmap(QPainter *painter, const QRect &rect, int, const QPixmap &pixmap) const
override { m_pic = pixmap; m_ny = rect.y(); Draw(painter); }
virtual void drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled,
const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const override;
void Draw(QPainter *painter) const;
const QToolButton* B;
const QSize SICON;
mutable QString m_s;
mutable QPixmap m_pic;
mutable QRect m_r;
mutable int m_nf, m_ny;
mutable bool m_bEnabled;
mutable QPalette m_pal;
mutable QPalette::ColorRole m_textRole;
};
CenteredToolButtonStyle::CenteredToolButtonStyle(QToolButton* b, const QSize& sIcon)
: QProxyStyle(), B(b), SICON(sIcon), m_nf(0), m_bEnabled(true), m_ny(0)
{
b->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
setParent(b);
}
void CenteredToolButtonStyle::drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal,
bool enabled, const QString &text, QPalette::ColorRole textRole/* = QPalette::NoRole*/) const
{
m_s = text;
m_r = rect;
m_nf = flags | Qt::AlignCenter;
m_bEnabled = enabled;
m_pal = pal;
m_textRole = textRole;
Draw(painter);
}
void CenteredToolButtonStyle::Draw(QPainter *painter) const
{
if (m_ny) {
if (m_r.y() != m_ny) return;
auto r = m_r;
r.adjust(-SICON.width() - 8, m_ny = 0, -itemTextRect(B->fontMetrics(), m_r, m_nf, m_bEnabled, m_s).width(), 0);
QProxyStyle::drawItemPixmap(painter, r, Qt::AlignCenter, m_pic);
}
QProxyStyle::drawItemText(painter, m_r, m_nf, m_pal, m_bEnabled, m_s, m_textRole);
}
Sample use:
foreach(auto b, ui.mainToolBar->findChildren<QToolButton*>())
b->setStyle(new CenteredToolButtonStyle(b, ui.mainToolBar->iconSize()));

How to indent the title of a QDockWidget using my own style

I want to indent the title of a QDockWidget w/o adding spaces.
#include <QStyle>
#include <QProxyStyle>
#include <iostream>
class my_style : public QProxyStyle
{
Q_OBJECT
public:
my_style (QStyle* style = 0) :
QProxyStyle (style)
{
}
virtual ~my_style ()
{
}
virtual QRect subElementRect (SubElement element, const QStyleOption * option, const QWidget * widget = 0) const
{
QRect rect = QProxyStyle::subElementRect (element, option, widget);
if (element == QStyle::SE_DockWidgetTitleBarText)
{
rect.adjust (50, 0, 0, 0);
}
//std::cerr << "debug:" << element << std::endl;
return rect;
}
};
I have no clue why but when I apply my style it's never running into that if. If I debug the method I only get an output for two different elements which are the buttons in the title bar.
subElementRect is not called to get the title area for all styles. At least, XP, Vista and MacOSX styles are using directly QStyleOption::rect which is passed as parameter to the drawControl function for CE_DockWidgetTitle.
To handle both cases, you should also reimplement drawControl:
void drawControl(ControlElement element, const QStyleOption *option,
QPainter *painter, const QWidget *widget) const
{
const QStyleOptionDockWidget *dockWidget;
if(element == CE_DockWidgetTitle &&
(dockWidget = qstyleoption_cast<const QStyleOptionDockWidget *>(option)))
{
QStyleOptionDockWidget copy = *dockWidget;
copy.rect.adjust(50,0,0,0);
// or you can add spaces in the title to avoid the border moving left too
// copy.title = QString(50 / copy.fontMetrics.width(" "), QChar(' ')) + copy.title;
QProxyStyle::drawControl(element, &copy, painter, widget);
return;
}
QProxyStyle::drawControl(element, option, painter, widget);
}
Alternatively you could use a style sheet, with a padding or a margin:
dockWidget->setStyleSheet("::title { position: relative; padding-left: 50px;"
" text-align: left center }");
The "position" rule does nothing, but is necessary, because strangely the style is only applied if some other rule categories are also present.
The text needs to be vertically realigned too because the alignment seem to be lost when using a style sheet.

Set QTableWidget cell's borders to 0px

How can I set the cell borders in a QTableWidget to 0px? Preferably I can choose which sides of a cell's border to set to 0, but I can live with setting them all to 0 as well.
EDIT: Either setting the border to 0px or setting the color to white would be good as well.
You can disable all the borders with QTableWidget::setShowGrid, and re-enable some of them with a style sheet (for instance: "QTableView::item { border-left: 1px solid black; }")
The latter are cell interior borders, so they might not be as well aligned as the grid.
If you want to change the borders individually for each cell, you need to write a delegate (like in that answer).
check if QTableWidget's setShowGrid would work for you, smth like this:
tableWidget->setShowGrid(false);
hope this helps, regards
The easiest way to do it for me without affecting widget's focus policy and using qss is to create the following custom delegate and install it for table:
*.h:
class FocusControlDelegate : public QStyledItemDelegate {
public:
FocusControlDelegate(QObject *parent = 0);
virtual void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const;
void setFocusBorderEnabled(bool enabled);
protected:
bool f_focus_border_enabled;
};
*.cpp:
FocusControlDelegate::FocusControlDelegate(QObject *parent) : QStyledItemDelegate(parent) {
f_focus_border_enabled = false;
}
void FocusControlDelegate::setFocusBorderEnabled(bool enabled) {
f_focus_border_enabled = enabled;
}
void FocusControlDelegate::initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const {
QStyledItemDelegate::initStyleOption(option, index);
if(!f_focus_border_enabled && option->state & QStyle::State_HasFocus)
option->state = option->state & ~QStyle::State_HasFocus;
}

Resources