Set different images for different checkboxes in a QTreeView - css

I subclassed a QTreeView and I have two columns where there are checkboxes. I would like to set two different images: one for the first column, and another one for the second column. I know I can change the image in the stylesheet with:
QTreeView::indicator:checked{
image: url(:/checked);
}
QTreeView::indicator:unchecked{
image: url(:/unchecked);
}
but it will change all the checkboxes in the tree view. Is there a way to do it with the stylesheets, or do I need to use a delegate?

Short answer: Stylesheets can't do that (as far as I know). They are a pretty immature feature in Qt, and there seems to be no development on them either.
What you can do:
Stylesheets
You cannot assign properties to a column or an item and you cannot access the columns by index.
But you can use some of the pseudo-states selectors like :first, :middle and :last:
QTreeView::indicator:first:checked{
background: red;
}
QTreeView::indicator:middle:checked{
background: blue;
}
QTreeView::indicator:unchecked{
background: lightgray;
}
I used colors instead of images for the sake of simplicity.
Note however, that these pseudo-states are actual currently visible states, so if the user is allowed to reorder columns, the style of the column might change. For example if the user drags one of the :middlecolumns and drops it at the end, the box will not be blue anymore.
DecorationRole
You can fake it using Qt::DecorationRole.
To do so, you have to receive the mousePressEvent either by subclassing QTreeView or by installing an event filter. You can then change the icon (via Qt::DecorationRole + emit dataChanged()) when the user clicks in the area of the icon.
This does not work with the keyboard of course.
Custom ItemDelegate
Subclass QStyledItemDelegate and override paint(), just as you suggested.
Custom Style
If you are creating a heavily styled application, you probably have to create a custom Style sooner or later. Stylesheets just don't support some features.
To do so, subclass QProxyStyle, override drawPrimitive and handle the drawing if QStyle::PE_IndicatorViewItemCheck is passed. You will also receive a QStyleOptionViewItem of which has some useful properties like checkState, features (contains QStyleOptionViewItem::HasCheckIndicator if there is a checkbox), and of course index so you can determine what kind of checkbox you want to draw.
Edit: Appendix
Example Using a Custom QStyledItemDelegate
void MyItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QStyledItemDelegate::paint(painter, option, index);
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
const QWidget *widget = option.widget;
QStyle *style = widget ? widget->style() : QApplication::style();
QRect checkRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &opt, widget);
drawCheckBox(painter, checkRect, opt.checkState, index);
}
void MyItemDelegate::drawCheckBox(QPainter * painter, const QRect & checkRect, Qt::CheckState checkState, const QModelIndex & index) const
{
if (checkState == Qt::Checked)
{
switch (index.column())
{
case 0:
painter->fillRect(checkRect, Qt::red);
break;
default:
painter->fillRect(checkRect, Qt::blue);
}
}
else
{
painter->fillRect(checkRect, Qt::lightGray);
}
}
This example is quick and easy. Simply paint over the checkbox drawn by QStyledItemDelegate. Requires you to fill the whole box however, otherwise the original will be visible.
You can try to use QStyledItemDelegate to draw anything but the checkbox, and draw the checkbox afterwards, but that is a little harder and will leave you with some minor drawing artifacts if you don't want to spend too much time on it.
Example Using a Custom QProxyStyle
void MyStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption * opt, QPainter * p, const QWidget * w) const
{
if (pe == QStyle::PE_IndicatorViewItemCheck)
{
const QStyleOptionViewItem * o = static_cast<const QStyleOptionViewItem *>(opt);
drawCheckBox(p, opt->rect, o->checkState, o->index);
return;
}
QProxyStyle::drawPrimitive(pe, opt, p, w);
}
The drawCheckBox() function is the same as in the first example.
As you can see, this way is much simpler, cleaner and has none of the drawbacks. You can apply the style globally, or only for a single widget.

Related

Qt tablewidget editbox

I created a tablewidget like this:
I want to edit cell(0) value, (double click), but the edit box was too big and it covers cell(1):
How do I avoid the edit box covering the cell after it?
You should make your own children QStyledItemDelegate and redefine QStyledItemDelegate::createEditor method.
Something like that:
QWidget * MyStyledItemDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QWidget * editor = QStyledItemDelegate::createEditor(parent, option, index);
editor->setWidth( 20 ); // Handle editor here.
return editor;
}

QTableView format individual row and columns

Maybe what I'm about to ask is so basic that i missed it somewhere, but i google all kind of things and i have not been able to find the answer. I have the following table view, feed from a SQLite table:
body = new QSqlTableModel(parent,data->m_db);
body->setTable("C"+QString::number(markTime.toSecsSinceEpoch()));
body->select();
ui->bodyView->setModel(body);
ui->bodyView->sortByColumn(0,Qt::AscendingOrder);
ui->bodyView->setColumnWidth(0,30);
ui->bodyView->setColumnWidth(1,80);
for(int x=2;x<ui->columns->maximum()+2;x++) ui->bodyView->setColumnWidth(x,40);
ui->bodyView->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->bodyView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->bodyView->setAlternatingRowColors(true);
ui->bodyView->show();
Further down in the program i add data to the table to be displayed. That works with no problems. My question is:
How i can access individual lines to format the row, i would like to change the size of the font, format the display to show number aligned and make it boldface but only for a specific row.
Thanks for the help.
Qt's proposed solution to customize the details of individual view items rendering (and editing) is via delegates. The level of customization depends on your use-case: you can do some minor adjustment like changing a font or you can draw something completely special, like what's shown in Star Delegate example.
There are basically two options to proceed with a custom delegate: either you subclass QStyledItemDelegate (or its base class QItemDelegate if you need to draw the items of Qt's datatypes somewhat specially) and change some particular details of interest to you leaving the rest to the base class or you subclass QAbstractItemDelegate to have full control over the view item's appearance and behavior.
A delegate can be set up for the view in either of three different ways:
for the whole view (i.e. for all model items) via setItemDelegate method
for a particular column via setItemDelegateForColumn method
for a particular row via setItemDelegateForRow method
For the sake of example, here's how you can specify a slightly larger font size for the item rendering:
class CustomDelegate: public QStyledItemDelegate
{
public:
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const Q_DECL_OVERRIDE;
};
void CustomDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (!index.isValid()) {
return;
}
QFont font = option.font;
font.setPointSize(font.pointSize() + 1);
QStyleOptionViewItem localOption(option);
localOption.font = font;
QStyledItemDelegate::paint(painter, localOption, index);
}

Qt: QListWidget separator line after particuler items?

This is related to Qt: QListWidget separator line between items?
But this above answer adds separator line after each items, I would like to know a way to add the separator line after particular items.
Create a QListWidgetItem representing the separator. Such item would need to have defined the setSizeHint(), so its height is small, and also the setFlags() should define Qt::NoItemFlags, so the item is not selectable, etc. Then, after adding the item to the QListWidget, place a QFrame, with its shape set to QFrame::HLine, as the item's widget (using QListWidget::setItemWidget()).
As for your additional question from the comment, which is:
I want to add some gap on each sides of this separator line/frame. How can I achieve this?
The only solution that comes to my mind right now is to embed the QFrame inside of another QWidget and put the QWidget as item's widget (remember that you need to add a layout manager to the QWidget in order to embed anything in it). Then set proper margins on the widget: QWidget::setContentsMargins(int left, int top, int right, int bottom)
I found another possibility and tested it this time :p
You could create a new class inheriting QStyledItemDelegate that look like this :
void MyStyledItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
// I have decided to use Qt::UserRole + 1 to store my boolean
// but it could be any other role while it's value is bigger than Qt::UserRole
QVariant isSeparator = index.data(Qt::UserRole + 1);
if (isSeparator.isValid() && isSeparator.toBool())
{
QRect rct = option.rect;
rct.setY(rct.bottom() - 1);
painter->fillRect(rct, QColor::fromRgb(qRgb(0, 0, 0)));
}
}
And the for each QListWidgetItem you can do the following :
// Qt::UserRole + 1 => Must match the role set in the delegate
item->setData(Qt::UserRole + 1, true);
Install the custom in your QListWidget like this
listWidget->setItemDelegate(new MyStyledItemDelegate());
It will draw a black line under the text of the item if the Qt::UserRole + 1 is set to true.
You can try using the same trick with dynamic properties.
myListWidget->setStyleSheet( "QListWidget::item[separator="true"] { border-bottom: 1px solid black; }" );
And on the widget you want the line to be drawn :
myWidget->setProperty("separator", true);
However be carefull the documentation says :
Warning: If the value of the Qt property changes after the style sheet has been set, it might be necessary to force a style sheet recomputation. One way to achieve this is to unset the style sheet and set it again.

Qt Color Picker Widget?

I have a QDialog subclass that presents some options to the user for their selecting. One of these options is a color. I have seen the QColorDialog, and I need something much simpler, that is also a regular widget so I can add to my layout as part of my dialog. Does Qt offer anything like this or will I have to make my own? If the latter, what is the best strategy?
Have you looked at the QtColorPicker, part of Qt Solutions?
QtColorPicker provides a small widget in the form of a QComboBox with a customizable set of predefined colors for easy and fast access. Clicking the ... button will open the QColorDialog. It's licensed under LGPL so with dynamic linking and proper attribution it can be used in commercial software. Search for QtColorPicker and you'll find it. Here's a link to one site that hosts many of the Qt Solutions components:
https://github.com/pothosware/PothosFlow/tree/master/qtcolorpicker
There's a very easy way to implement that using a QPushButton to display the current color and pickup one when it is clicked:
Definition:
#include <QPushButton>
#include <QColor>
class SelectColorButton : public QPushButton
{
Q_OBJECT
public:
SelectColorButton( QWidget* parent );
void setColor( const QColor& color );
const QColor& getColor() const;
public slots:
void updateColor();
void changeColor();
private:
QColor color;
};
Implementation:
#include <QColorDialog>
SelectColorButton::SelectColorButton( QWidget* parent )
: QPushButton(parent)
{
connect( this, SIGNAL(clicked()), this, SLOT(changeColor()) );
}
void SelectColorButton::updateColor()
{
setStyleSheet( "background-color: " + color.name() );
}
void SelectColorButton::changeColor()
{
QColor newColor = QColorDialog::getColor(color, parentWidget());
if ( newColor != color )
{
setColor( newColor );
}
}
void SelectColorButton::setColor( const QColor& color )
{
this->color = color;
updateColor();
}
const QColor& SelectColorButton::getColor() const
{
return color;
}
Qt doesn't offer anything simpler than QColorDialog natively, but there are several color picking widgets as part of wwWidgets, a user made set of widgets for Qt (note that this is "wwWidgets" with a "w" and not "wxWidgets" with an "x").
I think QColorDialog is best suited for your application. If you want to go for something simpler, it will come with reduced functionality. I'm not aware of any standard widget in Qt offering such an option but you can try out the following:
QCombobox with each entry corresponding to a different color. You can maybe even have the colors of the names in their actual color.
One or more slider bars to adjust the hue, saturation, val or R,G,B components.
QLineEdit fields for individual R,G,B components. You can also have a signal / slot mechanism wherein once the user changes a color, the color shown to the user gets changed accordingly.
You can have '+' and '-' signs to increase / decrease the above color component values.
I hope the above gives you some ideas.

Elegant way of getting if element is focused in function QGraphicsItem::shape()

In a graphical qt application,
i can learn if my object that inherits from QGraphicsItem is focused in paint method:
Qt Code:
void MyQGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
{
if (option->state & QStyle::State_HasFocus) {
//if focus some shape
} else {
//if no focus another shape
}
}
but i must click it and the shape must change whether it is focused or not.
how can i get if focused information in
Qt Code:
QPainterPath QGraphicsItem::shape() const
method in an appropriate way?
I think to declare a global variable but i do not like this idea.
thanks
Use QGraphicsItem::hasFocus() :
Returns true if this item is active, and it or its focus proxy has
keyboard input focus; otherwise, returns false.
Incidentally, if you want the shape to change when you focus the item, you will need to override focusInEvent() and focusOutEvent() and remember to call prepareGeometryChange() before the shape changes.

Resources