I defined pure virtual method QStyledItemDelegate::paint as:
void FooViewDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
bool selected = option.state & QStyle::State_Selected;
// ...
// drawing code
}
But I cant't figure how to know is the drawing item current or no (The same item as from QListView::currentIndex()).
Qt MVC is not designed for such usecases, because, theoretically, delegate should not know, what view you are using (it may be QListView or QTableView).
So, a "good way" is to keep this information inside your delegate (because model may be used by sevaral views). Fox example (pseudo-code):
class FooViewDelegate : ...
{
private:
QModelIndex _currentIndex;
void connectToView( QAbstractItemView *view )
{
connect( view, &QAbstractItemView::currentChanged, this, &FooViewDelegate ::onCurrentChanged );
}
void onCurrentChanged( const QModelIndex& current, const QModelIndex& prev )
{
_currentIndex = current;
}
public:
void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
bool selected = index == _currentIndex;
// ...
// drawing code
}
}
The parent of the delegate is the view, you can directly obtain the current index from the view.
void FooViewDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
bool selected = index == parent()->currentIndex();
}
You're along the right track:
auto current = option.state & QStyle::State_HasFocus;
The item with focus is the current item.
Related
I'm having an issue with overriding the text displayed for a QTreeView using a QStyledItemDelegate. When some condition is met following code is executed:
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
.
.
QStyleOptionViewItemV4 opt = option;
initStyleOption(&opt, index);
QString text = opt.text;
text = text + QString("TEST");
opt.text = text;
QStyledItemDelegate::paint(painter, opt, index);
}
I confirmed in the debbugger that TEST is added to opt.text.
However, when I run my program and look at the TreeVuew it is still displaying the original text without the TEST string appended.
It seems that when I call QStyledItemDelegate::paint(painter, opt, index), it's ignoring the change I've made to the opt parameter.
The default implementation of the QStyledItemDelegate::paint() method uses it's own QStyleOptionViewItem instance initialized with data from the model.
From the Qt 5.4.0 source code:
void QStyledItemDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_ASSERT(index.isValid());
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
const QWidget *widget = QStyledItemDelegatePrivate::widget(option);
QStyle *style = widget ? widget->style() : QApplication::style();
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
}
Solution:
Do not call the default implementation and implement your delegate's paint() method like this:
void MyDelegate::paint(QPainter* painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QStyleOptionViewItem itemOption(option);
initStyleOption(&itemOption, index);
itemOption.text = "Test Text"; // override text
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &itemOption, painter, nullptr);
}
The alternative solution, if you want to change the displayed text in a view, is to override displayText() method.
Example for Qt5:
mydelegate.h
virtual QString displayText(const QVariant &value,
const QLocale &locale) const override;
mydelegate.cpp
QString MyDelegate::displayText(const QVariant &value,
const QLocale &locale) const
{
Q_UNUSED(locale)
QString result = value.toString() + "TEST";
return result;
}
doc link: https://doc.qt.io/qt-5/qstyleditemdelegate.html#displayText
Depending what type of delegates it is, I would also try to override the setEditorData() method or even the createEditor() (where you can add values different from your model). It is less time consuming than doing such operation in paint.
Otherwise, you can use something like that to draw your text where you want:
painter->drawText(option.rect, Qt::AlignJustify, text + "_test");
You probably have a reason for doing so, but it seems like something is wrong in your design if you want to add extra text on the fly?
Possible QStyledItemDelegate::paint picks a text directly from index.data( Qt::DisplayRole ).toString(). That's why text is not changed. You may debug through Qt sources to be sure.
I propose you to use QIdentityProxyModel to do such things. Delegates are not designed for such solutions. You just need to override 1 method. So your code should look like this:
class MyProxyModel : public QIdentityProxyModel
{
// ...
};
QVariant MyProxyModel::data(const QModelIndex &index, int role) const override
{
if ( /*Conditions when you don't want to change source text*/ )
return QIdentityProxyModel::data( index, role );
// Extra check for editors or other roles to return original data
if ( role == Qt::EditRole || role != Qt::DisplayRole )
return QIdentityProxyModel::data( index, role );
const auto sourceIndex = mapToSource( index );
const auto originalText = sourceModel()->data( sourceIndex, Qt::DisplayRole ).toString();
const auto newText = QString( "%1 [TEST]" ).arg( originalText );
return newText;
}
// Usage
auto yourModel = YourOriginalModel( this );
auto proxy = MyProxyModel( this );
proxy->setSourceModel( yourModel );
view->setModel( proxy );
My original aim was to feed items (QStandardItem) of a specific column with rich text, therefore I implemented a subclass delegate as suggested.
Everything looked fine except one thing: when I moved the mouse pointer over these items, they were not highlighted at all. (The other items in the row - where the original paint method is used - were highlighted.) Item selection worked fine although. Then I added line
if ( optionV4.state & QStyle::State_MouseOver )
in which I was able to handle the item text highlight, but I have no idea how to highlight the background too. It is still white. Any ideas?
Here is the relevant code:
class MStyledItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
protected:
void paint ( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
QSize sizeHint ( const QStyleOptionViewItem& option, const QModelIndex& index ) const;
};
void MStyledItemDelegate::paint( QPainter* aPainter, const QStyleOptionViewItem& aOption, const QModelIndex& aIndex ) const
{
// ...
QStyleOptionViewItemV4 optionV4 = aOption;
initStyleOption( &optionV4, aIndex );
QStyle* style = optionV4.widget ? optionV4.widget->style() : QApplication::style();
QTextDocument doc;
doc.setHtml( optionV4.text );
optionV4.text = QString();
style->drawControl( QStyle::CE_ItemViewItem, &optionV4, aPainter );
// highlight text
QAbstractTextDocumentLayout::PaintContext ctx;
if ( optionV4.state & QStyle::State_MouseOver )
{
ctx.palette.setColor( QPalette::Text, Qt::blue );
}
// draw
aPainter->save();
QRect textRect = style->subElementRect( QStyle::SE_ItemViewItemText, &optionV4 );
aPainter->translate( textRect.topLeft() );
aPainter->setClipRect( textRect.translated( - textRect.topLeft() ) );
doc.documentLayout()->draw( aPainter, ctx );
aPainter->restore();
}
I suppose you use it with a QTableView, call the method setMouseTracking(true).
I have this slot and I want to be able to use the string that is at the index being passed through. How can I get to it?
void Dialog::on_list_Favorites_2_clicked(const QModelIndex &index)
{
}
Since you're using QListWidget instead of QListView you should also use the signal itemClicked(QListWidgetItem*) instead of clicked(const QModelIndex &).
void Dialog::on_list_Favorites_2_itemClicked(QListWidgetItem* item)
{
qDebug() << item->text();
}
You can use below function for this case.
QListWidgetItem * QListWidget::itemFromIndex(const QModelIndex & index) const
And then, the text of item can get using by QString QListWidgetItem::text() const
void Dialog::on_list_Favorites_2_clicked(const QModelIndex &index)
{
QListWidgetItem* pItem = m_listWidget->itemFromIndex( index );
Q_ASSERT( pItem );
if ( pItem )
{
QString itemName = pItem->text();
}
}
Here is what I'm trying to do (all parents and children must have a close button on the right, in the future, only the hovered item will be able to show the **close ** button):
My delegate code:
class CloseButton : public QItemDelegate
{
Q_OBJECT
public:
CloseButton( QObject* parent = 0 )
: QItemDelegate( parent )
{};
QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
if ( index.column() == 1 )
{
QToolButton* button = new QToolButton( parent );
button->setIcon( QIcon( CLOSE_ICON ) );
//button->setFixedSize( 16, 16 );
//button->setAutoRaise( true );
//button->setVisible( true );
CONNECT( button, SIGNAL( clicked() ), this, SLOT( emitCommitData() ) );
return button;
}
return ( new QWidget );
}
private slots:
void emitCommitData()
{
emit commitData( qobject_cast< QWidget* >( sender() ) );
}
private:
//Q_DISABLE_COPY( CloseButton );
};
With QTreeWidget connection code:
recipientsView()->setItemDelegateForColumn( 1, new CloseButton( this ) );
where recipientsView() is a simple QTreeWidget.
Problem is that QToolButtons are not shown at all (it must be in the second column, i.e. column index in the tree is 1). What I'm doing wrong?
I have checked already all Qt demo examples about delegates and the first Google result about QItemDelegate's and similar stuff.
You can use the QStyledDelegate::paint function to draw the close icon, without using any widget, and the editorEvent to receive mouse events for the item, even if you don't use the editor or make the item editable.
class CloseButton : public QStyledItemDelegate {
Q_OBJECT
public:
explicit CloseButton(QObject *parent = 0,
const QPixmap &closeIcon = QPixmap())
: QStyledItemDelegate(parent)
, m_closeIcon(closeIcon)
{
if(m_closeIcon.isNull())
{
m_closeIcon = qApp->style()
->standardPixmap(QStyle::SP_DialogCloseButton);
}
}
QPoint closeIconPos(const QStyleOptionViewItem &option) const {
return QPoint(option.rect.right() - m_closeIcon.width() - margin,
option.rect.center().y() - m_closeIcon.height()/2);
}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const {
QStyledItemDelegate::paint(painter, option, index);
// Only display the close icon for top level items...
if(!index.parent().isValid()
// ...and when the mouse is hovering the item
// (mouseTracking must be enabled on the view)
&& (option.state & QStyle::State_MouseOver))
{
painter->drawPixmap(closeIconPos(option), m_closeIcon);
}
}
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QSize size = QStyledItemDelegate::sizeHint(option, index);
// Make some room for the close icon
if(!index.parent().isValid()) {
size.rwidth() += m_closeIcon.width() + margin * 2;
size.setHeight(qMax(size.height(),
m_closeIcon.height() + margin * 2));
}
return size;
}
bool editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index)
{
// Emit a signal when the icon is clicked
if(!index.parent().isValid() &&
event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
QRect closeButtonRect = m_closeIcon.rect()
.translated(closeIconPos(option));
if(closeButtonRect.contains(mouseEvent->pos()))
{
emit closeIndexClicked(index);
}
}
return false;
}
signals:
void closeIndexClicked(const QModelIndex &);
private:
QPixmap m_closeIcon;
static const int margin = 2; // pixels to keep arount the icon
Q_DISABLE_COPY(CloseButton)
};
First, I should ask if you are really using a QTreeWidget, or rather a QTreeView? You cannot use custom delegates with a QTreeWidget according to the documentation for QTreeView, and will have to use a QTree*View* and some form of QAbstractItemModel for you to be able to use a custom delegate.
Ah, scratch that. I see that you're calling setItemDelegateForColumn, which is a QTreeView function, but you should be aware of the difference, so I'm keeping the above paragraph. :)
I would check that your model's flags() function is returning Qt::ItemIsEditable as part of its item flags. The createEditor() method is called whenever an edit event is reported by the view. (The view events that will trigger an edit depend on the model's EditTriggers) Usually a double-click on the delegate will trigger an edit by default, among other things.
I doubt that you want the close button to appear only on double-click, though. To get the button to appear all the time, you'll have to reimplement the delegate's paint() function to draw a button, Among other things. I found Qt's StarDelegate example to be quite helpful in this regard, and I suspect you'll find it useful too.
You can use a QItemDelegate with a QTreeWidget this way (example in PyQt, sorry):
myTreeWidget = QtGui.QTreeWidget()
myTreeWidget.setItemDelegate(myDelegate())
class myDelegate(QtGui.QItemDelegate):
def paint(self, painter, option, index):
#Custom Draw Column 1
if index.column() == 1:
icon = QtGui.QIcon(index.data(QtCore.Qt.DecorationRole))
if icon:
icon.paint(painter, option.rect)
#You'll probably want to pass a different QRect
#Use the standard routine for other columns
else:
super(myDelegate, self).paint(painter, option, index)
I'm subclassing QAbstractItemDelegate. This is my code. Suggestions are welcome:
QWidget *ParmDelegate::createWidget(Parm *p, const QModelIndex &index) const {
QWidget *w;
if (index.column() == 0) {
w = new QLabel(p->getName().c_str());
} else {
if (p->isSection())
return NULL;
w = p->createControl();
}
return w;
}
QWidget *ParmDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
cout << "createEditor called" << endl;
Parm *p = reinterpret_cast<Parm*>(index.internalPointer());
QWidget *retval = createWidget(p, index);
retval->setFocusPolicy(Qt::StrongFocus);
retval->setParent(parent);
return retval;
}
void ParmDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const {
QRect rect(option.rect);
editor->setGeometry(QRect(QPoint(0,0), rect.size()));
}
void ParmDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
Parm *p = reinterpret_cast<Parm*>(index.internalPointer());
scoped_ptr<QWidget> w(createWidget(p, index));
if (!w)
return;
QRect rect(option.rect);
w->setGeometry(QRect(QPoint(0,0), rect.size()));
w->render(painter, rect.topLeft());
}
QSize ParmDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
Parm *p = reinterpret_cast<Parm*>(index.internalPointer());
scoped_ptr<QWidget> w(createWidget(p, index));
if (!w)
return QSize(0,0);
return w->sizeHint();
}
bool ParmDelegate::editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index ) {
cout << "editorEvent called" << endl;
return false;
}
When this is run, I only see that editorEvent gets called twice for every edit event -- no createEditor!
From Qt's AbstractItemDelegate documentation:
To provide custom editing, there are two approaches that can be used. The first approach is to create an editor widget and display it directly on top of the item. To do this you must reimplement createEditor() to provide an editor widget, setEditorData() to populate the editor with the data from the model, and setModelData() so that the delegate can update the model with data from the editor.
The second approach is to handle user events directly by reimplementing editorEvent().
This appears to say that you are missing something to trigger the first approach. My guess is that your model's data() function isn't returning the proper value for the Qt::EditRole option.
I had implemented a TableView which i had inhertied from QItemDelegate. Then i had similar problem. I tracked it down to not calling 'return QItemDelegate::editorEvent(event, model, option, index);' in the editorEvent(...) method.
You can try this. Maybe it helps.