Qt tablewidget editbox - qt

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;
}

Related

Set different images for different checkboxes in a QTreeView

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.

QTreeView: resizeRowsToContents equivalent or how to word-wrap text in rows

I have a QTreeView with more than one column (like a table). Columns in the tree have a fixed size. I need to resize row heights and use multi-lined text like QTableView::resizeRowsToContents.
How can I do this?
I've tried using a custom QStyledItemDelegate with reimplemented sizeHint, but I don't know how to calculate multiline text block height with known width.
Using QStyledItemDelegate is the right approach. In your sizeHint function, you can use the style options text with the QFontMetrics class:
QSize MyItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override {
QSize baseSize = this->QStyledItemDelegate::sizeHint(option, index);
baseSize.setHeight(10000);//something very high, or the maximum height of your text block
QFontMetrics metrics(option.font);
QRect outRect = metrics.boundingRect(QRect(QPoint(0, 0), baseSize), Qt::AlignLeft, option.text);
baseSize.setHeight(outRect.height());
return baseSize;
}
Note: Right now I cannot test this, but it should work. You may have to tweak the call to metrics.boundingRect if the output does not fit your needs
EDIT:
It seems the sizeHint will be only called once to create the initial layout, but not after resizing the columns.
A final Idea could be to override the QAbstractItemModel::data function to return the desired size using Qt::SizeHintRole. You could either add it to your existing model or provide a proxy-model to do this:
QSize MyModel::data(const QModelIndex &index, int role) const override {
switch(role) {
//...
case Qt::SizeHintRole:
{
QSize baseSize(getFixedWidth(index.column()), baseSize.setHeight(10000));//something very high, or the maximum height of your text block
QFontMetrics metrics(this->data(index, Qt::FontRole).value<QFont>());
QRect outRect = metrics.boundingRect(QRect(QPoint(0, 0), baseSize), Qt::AlignLeft, this->data(index, Qt::DisplayRole)));
baseSize.setHeight(outRect.height());
return baseSize;
}
//...
}
}
Important: Everytime your view gets resized, you will have to emit the dataChanged signal for all those items. getFixedWidth is something you will have to implement to return the current width of the given column.

QListItem adds an offset to each item

I am trying to draw QWwidget in a QListView with a delegate. I can get the widget to render but it seems like there is a clip window that is moving over the widget in the left direction as I draw them and I can't figure why. It's almost like the QListView is adding a margin that grows with each item that is added, even if I am still drawing the widget at the right location.
The render is like this:
|-----|
->|-----|
--> |-----|
And here is the code I am using :
void CustomItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
MyWidget w("test", "test");
//At some point I want to load the widget with data from here
if(index.data().canConvert<CustomModelItem>())
{
w.resize(option.rect.size());
painter.save();
//some kind of bug with passing topLeft in. I found this solution somewhere
w.render(painter, painter->deviceTransform().map(option.rect.topLeft()));
painter.restore();
}
else
{
QStyledItemDelegate::paint(painter, option, index);
}
}
Can anyone explain this behavior?
UPDATE: I put some debug code in the render function for QWidgets. What I am observing is that the x coordinate of the systemclip is moving + 3 in every iteration of the loop in QListView's paint function. I have no idea where this coming from.

How to set QStyleOptionButton like a checkbox with custom icons

I have a custom Delegate class which inherits from QStyledItemDelegate. In its paint() event, I would like to add QStyleOptionButton which should be checkable. Is it possible?
For example, it denotes visibility property with an icon of eye; and when the button is pressed, the eye icon turns into closed-eye icon.
Inside the paint() method, this is my current code to create the button:
QStyleOptionButton buttonVis;
buttonVis.rect = getButtonVisibilityRect();
buttonVis.iconSize = QSize(sizeX, sizeY);
buttonVis.icon = icon;
buttonVis.state = QStyle::State_Enabled;
buttonVis.features = QStyleOptionButton::None;
QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonVis, painter);
The icon that I load to the buttonVis is created by:
QIcon icon;
icon.addPixmap(QPixmap(":/control-visibility.svg"), QIcon::Normal, QIcon::On);
icon.addPixmap(QPixmap(":/control-visibilityNo.svg"), QIcon::Normal, QIcon::Off);
For the moment when I run my program, the button has an icon with closed eye. Is there a command to control which icon is displayed? If my initial layout is not possible to implement, what is the right direction to go?
EDIT: I found out how to select which icon to use in order to simulate the checkbox look. Instead if line buttonVis.state = QStyle::State_Enabled; there should be:
if (/* current item checkbox checked? */)
buttonVis.state = QStyle::State_Enabled | QStyle::State_On;
else
buttonVis.state = QStyle::State_Enabled | QStyle::State_Off;
The problem now is to figure out what is that condition or how to set it up from the editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index). The problem is that I cannot really change option by myself since it is constant reference. Any ideas how to do it, or how to get around it?
You can try something like this:
QStyleOptionButton option;
option.initFrom(this);
...
QStylePainter p(this);
if (option.state.testFlag(QStyle::State_Sunken)) {
p.drawPixmap(this->rect(), QPixmap(":/control-visibility"));
} else {
p.drawPixmap(this->rect(), QPixmap(":/control-visibilityNo"));
}
Be careful with SVG file, try first with PNG.
QStyle::State_Sunken 0x00000004 Used to indicate if the widget is
sunken or pressed.
Finally, I figured out how to set up certain flag and then test for it and see if the button changed an icon.
So, for the icon change, as I mentioned on the edit of my question, I have to use something like:
buttonVis.state |= QStyle::State_Enabled;
buttonVis.state |= isChanged? QStyle::State_Off : QStyle::State_On;
So it comes down on how to set up isChanged flag. And it can be done within editorEvent() method of the delegate:
bool Delegate::editorEvent(QEvent *event, QAbstractItemModel *model, const StyleOptionViewItem &option, const QModelIndex &index)
{
if (/* event is release and it is over the button area*/)
{
bool value = index.data(Qt::UserRole).toBool();
// this is how we setup the condition flag
model->setData(index, !value, Qt::UserRole);
return true;
}
}
Now to use the setup flag, we do it in the paint() event right before we set up buttonVis.state:
bool isChanged = index.data(Qt::UserRole).toBool();
By adding these steps my QStyleOptionButton is now behaving like a checkbox, but it changes icons as states.

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.

Resources