I am using a custom delegate to render rich text in a QComboBox. It is used to allow users to select a color to plot a variable on a graph. It works for items in the drop down menu, but not the selected item. Any help would be appreciated.
Here is the code for the delegate I am using:
class CustomDelegate : public QStyledItemDelegate
{
public:
CustomDelegate();
protected:
void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
};
void CustomDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const{
QStyleOptionViewItemV4 optionV4 = option;
initStyleOption(&optionV4, index);
QStyle *style = optionV4.widget? optionV4.widget->style() : QApplication::style();
QTextDocument doc;
doc.setHtml(optionV4.text);
/// Painting item without text
optionV4.text = QString();
style->drawControl(QStyle::CE_ItemViewItem, &optionV4, painter);
QAbstractTextDocumentLayout::PaintContext ctx;
// Highlighting text if item is selected
if (optionV4.state & QStyle::State_Selected)
ctx.palette.setColor(QPalette::Text, optionV4.palette.color(QPalette::Active, QPalette::HighlightedText));
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &optionV4);
painter->save();
painter->translate(textRect.topLeft());
painter->setClipRect(textRect.translated(-textRect.topLeft()));
doc.documentLayout()->draw(painter, ctx);
painter->restore();
}
QSize CustomDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const{
QStyleOptionViewItemV4 optionV4 = option;
initStyleOption(&optionV4, index);
QTextDocument doc;
doc.setHtml(optionV4.text);
doc.setTextWidth(optionV4.rect.width());
return QSize(doc.idealWidth(), doc.size().height());
}
I set the options and delegate for the combo box using code similar, I removed some of the options to reduce the size of the code:
ui->SelectColor->clear();
ui->SelectColor->addItem("Select Color");
ui->SelectColor->addItem("<font color='blue'>Blue</font>");
ui->SelectColor->addItem("<font color='darkBlue'>Dark Blue</font>");
ui->SelectColor->addItem("<font color='red'>Red</font>");
ui->SelectColor->addItem("Dark Yellow");
ui->SelectColor->addItem("<font color='magenta'>Dark Magenta</font>");
ui->SelectColor->addItem("White");
ui->SelectColor->setItemDelegate(new CustomDelegate);
Some of the options are just the names because they don't look good when rendered.
Your custom styled item delegate is applied to items that are part of QComboBox (i.e. ones in QComboBox popup), not for the current item which is represented. The easiest way to achieve what you want would be to introduce your custom class inheriting QComboBox, and then override void QWidget::paintEvent(QPaintEvent *event) method by applying changes you would like to introduce, e.g. set some colored text. Another way (in case you can not introduce another class inheriting QComboBox for some reason) would be to introduce an event filter that does something just after the QPaintEvent. However, usage of event filters might be tricky, and I would advise you to just introduce some another class, and then override paint event.
Now, in case you want to show current item in the same way as an option in the combo box list popup, you could do the following (code is not complete, and you should apply it to your needs yourself):
virtual void paintEvent(QPaintEvent* e) override
{
// QComboBox::paintEvent(e); - this will leave just a rectangle in which you can perform your custom drawings.
// Will make your option colored at least.
QPainter p(this);
QTextDocument doc;
doc.setHtml(this->currentText());
doc.drawContents(&p, rect());
}
Finally, it is worth to mention that QComboBox painting is pretty complex, and you might still need to rewrite pretty everything that Qt already has done for you, so it would be possible to apply other styles you might want not to lose. In order to do that, you should take a look into the source code of this class. You can do this in the Code Browser by Woboq for C & C++ (QComboBox). Hopefully, this clarifies the problem for you more, and now you know what to do in order to achieve your goal.
Related
Recently, I made the switch to QT. It has taken some time, but I am starting to find my way around. However, one issue remains:
I want to port a program, that responds to every key press while editing a cell in a table view (QTableView with QStandardItemModel). The idea is to show and update a list of possibilities on a separate form while the user is entering a text in a table view's cell. After every key stroke, the list needs to be updated according to the current text in the edit field of some cell.
Using QTableView::installEventFilter and QEvent::KeyPress, I can get every key press while the table view is in focus, but the cell text / model is only updated after editing, which prohibits live updates of the list.
The model's dataChanged signal is only emitted after editing has finished and not during the user's input.
Any ideas on how to solve this?
Should I use a QItemDelegate?
Or should a QLineEdit be connected to a cell somehow and can this be done without it visually being apparent, so the user still appears to be working directly inside a cell?
Thank you for any help
It works (that is, until the main window is resized...)
Perhaps not the best solution, but at least i found a way to make it work.
I put the slot in the source file that contains the main window, because that is what I have always been used to (C++ Builder)...
GLiveEdit.H - Subclass QStyledItemDelegate
#ifndef GLIVEEDIT_H
#define GLIVEEDIT_H
#include <QStyledItemDelegate>
class GLiveEdit : public QStyledItemDelegate {
public:
GLiveEdit (QObject *_ParentWindow = 0, const char *_Slot = 0);
protected:
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
protected:
mutable QLineEdit *Editor;
const char *Slot;
QWidget *ParentWindow;
};
#endif // GLIVEEDIT_H
GLiveEdit.CPP
#include "gliveedit.h"
#include <QLineEdit>
GLiveEdit::GLiveEdit (QObject *_ParentWindow, const char *_Slot)
: QStyledItemDelegate (_ParentWindow)
{
Editor = 0;
Slot = _Slot;
ParentWindow = (QWidget *) _ParentWindow;
}
QWidget *GLiveEdit::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
Editor = (QLineEdit *) QStyledItemDelegate::createEditor (parent, option, index);
connect (Editor, SIGNAL (textChanged (const QString &)), ParentWindow, Slot);
return Editor;
}
MainWindow.CPP - Install Subclassed QStyledItemDelegate ("GLiveEdit")
void MainWindow::SetUp()
{
// Instantiate subclassed QStyledItemDelegate "GLiveEdit" as "Live"
// Also pass the slot "OnEditChanged" that is to be called during editing
Live = new GLiveEdit (this, SLOT (OnEditChanged (const QString &)));
// Tell the table about the instantiated subclassed delegate "Live"
ui->tvOverview->setItemDelegate (Live);
}
void MainWindow::OnEditChanged (const QString &NewText)
{
// NewText contains the up to date text that is currently being edited
}
Anyone any ideas on having it also work after the window has been resized?
Calling the SetUp function from within QMainWindow::resizeEvent does not seem to work, unfortunately.
Also, I suppose QTableView deletes the item delegate from memory itself?
Edit: The item delegate only stops working if the window is resized during the editing... It keeps functioning if the edit is finished first.
It works! Problems after resizing were due to a bug of mine :-) (located elsewhere in the source code)
Any suggestions for improvement still welcome!
I extented a QStyledItemDelegate to draw some pixmaps on the right side of a QTreeView. This works well, however, I realized that the pixmaps can be in front of the text if it gets too long.
So I tried to draw a rectangle of the same color as the background before drawing my pixmap.
void MyItemDelegate::paint(
QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
...
QStyledItemDelegate::paint(painter, option, index);
...
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
...
painter->fillRect(rect, opt.backgroundBrush);
painter->drawPixmap(topLeft, pixmap);
}
I know that all variables are correct except opt.backgroundBrush. It's always invisible black, with or without the initStyleOption call.
Then I started to check the value of opt.state and opt.features to pick the color myself. I had some success with the alternating color (QStyleOptionViewItem::Alternate) but it's starting to get long and tedious because there are other states (hover and selected) and other OS that may have other states. There must be a simpler way.
So, my question is: How can I get the actual color that will be used to paint the row when I'm in the paint method? Or, do you have any other clean way to avoid this situation?
As suggested by #G.M. I used the QItemDelegate class instead of QStyledItemDelegate and I do have more control on the drawing process. Notably because of the added protected functions like drawDisplay. Overriding this method, I can adjust the "display rectangle" to a smaller size than what was calculated by Qt.
void ActionsItemDelegate::drawDisplay(
QPainter *painter,
const QStyleOptionViewItem &option,
const QRect &rect,
const QString &text) const
{
const int minX = ...; // x position of the left-most icon
const int iconsWidth = rect.width() - (minX - rect.x());
QItemDelegate::drawDisplay(painter, option, rect.adjusted(0, 0, -iconsWidth, 0), text);
}
The drawback is that the UI changes when using QStyledItemDelegate. I don't know how to get the benefits of both: normal style and more controls.
I may process differently. I won't start by calling:
QStyledItemDelegate::paint(painter, option, index);
in the first place.
Then, the important part: if you know how much icons there are on your items, you could calculate the bounding rectangle.
How do you know how many icons you have ? Maybe by calling something like:
bool hasIcon = index->data(YourAPP::HasIcons).toBool();
You could have something like this in your delegate:
void MyItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
...
QStyleOptionViewItem o = option;
initStyleOption(&o, index);
bool hasIcon = index->data(YourModel::HasIcons).toBool();
// or
bool hasCustomRole1 = index->data(YourModel::Role1).toBool();
bool hasCustomRole2 = index->data(YourModel::Role2).toBool();
if (hasIcon) {
// width of your icons, can be like o.rect.height() * numbers of icons
int width = 0;
// shrink your first rectangle
o.rect.adjust(0, 0, -width, 0);
// create a second rectangle
QStyleOptionViewItem o2 = option;
initStyleOption(&o2, index);
o2.rect.adjust(width, 0, 0, 0);
// paint your icons
...
painter->drawPixmap(o2, pixmap);
} else {
QStyledItemDelegate::paint(painter, option, index);
}
I have implemented an OSGQt based model viewer. I am currently attempting to reposition submodels by changing their properties in a child QFrame to the main MainWindow based viewer frame. The child frame is non-modal and the key events are being passed to the OSG Viewer based handler in the MainWindow instead of the child window.
What would be the appropriate attributes for the child window to keep the keyboard focus there and allow typing new values in the QLineEdit based modifier for the QTreeWidgetItem column.
I have implemented a working solution by using a custom ItemDelegate as follows.
class DoublePositionEditDelegate: public QStyledItemDelegate {
public:
DoublePositionEditDelegate(QObject* parent=0): QStyledItemDelegate(parent) {}
virtual QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
QDoubleSpinBox* dspinbox = new QDoubleSpinBox(parent);
dspinbox->setMaximum(1000000.0);
dspinbox->setMinimum(-1000000.0);
dspinbox->grabKeyboard();
return dspinbox;
}
};
A more elegant/better solution is still welcome.
I am setting a QPixmap to a QStandardItem:
QStandardItem* item = new QStandardItem();
item->setData( pixmap, Qt::DecorationRole );
Then I do appendRow() and add item to the Model.
I display all the pixmaps in the Model in a QListView.
How do I set a thin border to only the 1st item (image) in the ListView ?
Subclass QStyledItemDelegate and override it´s paint function. Use this to paint a border to your item. Then set that delegate to your QListView.
Example:
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if(index.row() == 0)
{
painter->setPen(QPen(Qt::red, 2));
painter->drawRect(option.rect.x()+1, option.rect.y(), option.rect.width()-1, option.rect.height());
}
QStyledItemDelegate::paint(painter, option, index);
}
And to set the delegate for your QListView:
listView->setItemDelegate(new MyDelegate);
You don't necessarely have to check the row in the paint function. You can just set the delegate for a specific row:
listView->setItemDelegateForRow(0, new MyDelegate);
When I set flags of QAbstractItemModel selectable but not enabled, I can't select items by mouse click. However internally select() function selects objects.
Is this qt bug, or I do something wrong?
From what I understood, you want to "Disable" the item, but at the same time, be able to select it. it's fairly easy to fake that on the model.
if ( role == Qt::BackgroundRole ){
return QVariant(QApplication::palette()->color(QPalette::Inactive, QPalette::Window );
}
This will paint your item as grayed out, and you will still be able to select it.
You're doing something wrong. If you disable a widget it is greyed out and it doesn't receive user mouse clicks and keyboard input.
I just had similar problem (I need to copy disabled items). Here is solution that sets correct style for disabled items (without ignoring any style sheets).
Create custom item delegate for your model.
/// Returns false only if item needs to be rendered as disabled.
bool isIndexEnabled(const QModelIndex &index)
{
// Implement this function.
}
class ItemDelegate : public QStyledItemDelegate {
public:
explicit ItemDelegate(QObject *parent = nullptr)
: QStyledItemDelegate(parent) {}
protected:
void initStyleOption(
QStyleOptionItemView *option, const QModelIndex &index) const override
{
QStyledItemDelegate::initStyleOption(option, index);
if (!isIndexEnabled(index))
option->state &= ~QStyle::State_Enabled;
}
};
Set the new item delegate to your model.
auto itemDelegate = new ItemDelegate(model)
model->setItemDelegate(itemDelegate);