PDA

View Full Version : inserting custom Widget to listview



Amit_3117
31st December 2009, 05:10
Hi All,

I want to add one custom widget having a label and a button to my listview as one list item ...

If there is any code snippet or example code in this scenario .. please do suggest ..


Thanks,
Amit

axeljaeger
8th January 2010, 11:52
Use void QAbstractItemView::setIndexWidget ( const QModelIndex & index, QWidget * widget )
See also the warning in the documentation that you have to create a delegate if you want anything sensible with the widget. Generally, adding widgets to an itemview is a bad idea because it does not scale. If you 1000 rows, you need 1000 widgets in a naive implementation. The better implementation is to have only as much widgets as currently visibie and reuse widgets that get out of view for the appearing rows during scrolling. Basically, that is what the delegate does.

faldzip
8th January 2010, 12:21
If you want to show only standard controls like labels, buttons, check boxes then the most efficient way is to make your own item delegate where in paint() method you paint those controls with QStyle (see drawControl() and others). But those controls are only drawn so you have to implement by hand event processing in QAbstractItemDelegate::editorEvent() and you have to make eventFilter and install it on view's viewport to which you have to enable mouse tracking (and maybe set WA_Hover attribute to get Leave and Enter events - I don't remember exactly). Sounds complicate but it is the most efficient way in case with simple widgets, because you don't really have any real widget objects.

MarkoSan
10th January 2010, 06:34
Use void QAbstractItemView::setIndexWidget ( const QModelIndex & index, QWidget * widget )
See also the warning in the documentation that you have to create a delegate if you want anything sensible with the widget. Generally, adding widgets to an itemview is a bad idea because it does not scale. If you 1000 rows, you need 1000 widgets in a naive implementation. The better implementation is to have only as much widgets as currently visibie and reuse widgets that get out of view for the appearing rows during scrolling. Basically, that is what the delegate does.

So, then the custom widget is "placed" into delegate and delegate then handles number of showing widgets?

axeljaeger
10th January 2010, 10:04
I guess the widget replaces the delegate for that certain cell. If you have 100 rows, you will have to set 100 widgets. I think you will also have to to data binding of that widget yourself.

MarkoSan
10th January 2010, 10:30
I guess the widget replaces the delegate for that certain cell. If you have 100 rows, you will have to set 100 widgets. I think you will also have to to data binding of that widget yourself.

Hmm, I am a little confused right now. Here is how I want for cell to look like in attachment. Is delegate only interaction part (3 buttons on the right side) or whole cell with labels and pic?

axeljaeger
10th January 2010, 10:33
Delegate is responsible for drawing the whole cell's content including labels. It is also responsible for the behaviour.

MarkoSan
10th January 2010, 10:36
So, I subclass QWidget with all labels, pic, buttons, ..., layout them as in attachment and then I set this widget to subclassed QAbstractItemDelegate?

axeljaeger
10th January 2010, 10:44
Yes but there is one showstopper: The delegate will use the widget only in edit state. For all other cells, it will use QAbstractItemDelegate::paint.

MarkoSan
10th January 2010, 11:18
Yes but there is one showstopper: The delegate will use the widget only in edit state. For all other cells, it will use QAbstractItemDelegate::paint.

With "edit state" you mean Qt::EditRole?

axeljaeger
10th January 2010, 11:23
No: For example, if you have a table, you see a lot of cells, but only one cell can show a blinking textcursor at the same time. This cell is in "editing" state. All others are just "labels". You usually not use full widgets for the cells that are not in editing state because of performance reasons.

MarkoSan
10th January 2010, 11:27
Well, thanks, but that means my cell design will be performance demanding??

axeljaeger
10th January 2010, 11:33
If you go with setIndexWidget, YES, if you use a delegate, it depends. You can only use your widget for the edit state. You have to fake drawing of that widget in delegate::paint. It depends on how clever you do that. For example, if you instantiate a widget, take a screenshot of it and blit it to the cell, it is not clever.

faldzip
10th January 2010, 15:16
as I understand from the picture all you need from the widgets are 3 buttons, the rest are labels, so you can draw it all by yourself in delegate.
int QAbstractItemDelegate::sizeHint() you should return required size hint, and in QAbstractItemDelegate::paint() you can draw all you want width painter. Drawing text is simple (see QPainter::drawText()) and drawing buttons is also simple: QStyle::drawControl()

Here is a code snippet from my code painting a button in one cell at column number = ButtonColumn:



void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
if (!index.isValid() || index.column() != ButtonColumn) {
return;
}

QStyleOptionButton opt;
State s = (State)(index.data(Qt::UserRole).toInt());
if (s == Hovered)
opt.state |= QStyle::State_MouseOver;
if (s == Pressed)
opt.state |= QStyle::State_Sunken;
opt.state |= QStyle::State_Enabled;
opt.rect = option.rect.adjusted(1, 1, -1, -1);
opt.text = trUtf8("Button text");
QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, painter, 0);
}

As you see the state of the button is stored in model cause there is one button for each index (in this column ButtonColumn) so the best way to store the state is to store it in model :] (State is my own enum).

but then you have do your own event handling for hover and push events. I geting quite compicated, because you have to implement it in QAbstractItemDelegate::editorEvent() and in event filter installed on view's viewport. And view's viewport has to have mouse tracking enabled. So for my button's in columns here is there relevant, example code:



bool HistoryDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
if (event->type() == QEvent::MouseMove) {
if (index != m_lastUnderMouse) {
if (m_lastUnderMouse.isValid()) {
model->setData(m_lastUnderMouse, (int)Normal, Qt::UserRole);
emit needsUpdate(m_lastUnderMouse);
}
if (index.isValid() && index.column() == ButtonColumn) {
model->setData(index, (int)Hovered, Qt::UserRole);
emit needsUpdate(index);
m_lastUnderMouse = index;
} else {
m_lastUnderMouse = QModelIndex();
}
}
}
if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) {
if (index != m_lastUnderMouse) {
if (m_lastUnderMouse.isValid()) {
model->setData(m_lastUnderMouse, (int)Normal, Qt::UserRole);
emit needsUpdate(m_lastUnderMouse);
}
if (index.isValid() && index.column() == ButtonColumn) {
model->setData(index, (int)Pressed, Qt::UserRole);
emit needsUpdate(index);
emit clicked(index);
m_lastUnderMouse = index;
} else {
m_lastUnderMouse = QModelIndex();
}
} else {
if (m_lastUnderMouse.isValid()) {
model->setData(m_lastUnderMouse, (int)Pressed, Qt::UserRole);
emit needsUpdate(m_lastUnderMouse);
emit clicked(m_lastUnderMouse);
}
}
}
if (event->type() == QEvent::MouseButtonRelease) {
if (index != m_lastUnderMouse) {
if (m_lastUnderMouse.isValid()) {
model->setData(m_lastUnderMouse, (int)Normal, Qt::UserRole);
emit needsUpdate(m_lastUnderMouse);
}
if (index.isValid() && index.column() == ButtonColumn) {
model->setData(index, (int)Hovered, Qt::UserRole);
emit needsUpdate(index);
m_lastUnderMouse = index;
} else {
m_lastUnderMouse = QModelIndex();
}
} else {
if (m_lastUnderMouse.isValid()) {
model->setData(m_lastUnderMouse, (int)Hovered, Qt::UserRole);
emit needsUpdate(m_lastUnderMouse);
}
}
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}

m_lastUnderMouse is QPersistentModelIndex storing which index was last under mouse to know that we have to for example remove the hover highlight because now we are highlightind another index.
needsUpdate() is connected to view and call update(QModelIndex) to call MyDelegate::paint() which will paint new button state.


bool MyWidgetContainingView::eventFilter(QObject *obj, QEvent *event)
{
if (obj != ui->treeView->viewport())
return QWidget::eventFilter(obj, event);
switch (event->type()) {
case QEvent::Leave:
m_delegate->notifyMouseLeave();
break;
case QEvent::MouseMove:
QModelIndex index = ui->treeView->indexAt(static_cast<QMouseEvent *>(event)->pos());
if (!index.isValid())
m_delegate->notifyMouseLeave();
break;
}
return QWidget::eventFilter(obj, event);
}

notifyMouseLeave() is the delegate method to notify delegate that mouse left view's viewport so it has to for example remove the hover highlight from m_lastUnderMouse if it is valid index.

Hope this helps :]


Notice that this solution is very efficient because there is no single real button, they are just drawn by delegate :]

MarkoSan
10th January 2010, 18:07
So, these are two ways of implementing my task?

faldzip
10th January 2010, 22:34
My whole previous post is one solution, and the last sentence was about it's performance advantage compared to setIndexWidget()

MarkoSan
13th January 2010, 07:46
Well, as seen from my attached pic, I must display several strings and a pic. How do send this data to model, using custom roles?

faldzip
13th January 2010, 09:15
Well, as seen from my attached pic, I must display several strings and a pic. How do send this data to model, using custom roles?
It depends on your model. If you use QStandardItemModel then every QStandardItem has setData() method which you can use like this:


QStandardItem *item = new QStandardItem;
item->setData("Label 1 text", Qt::UserRole);
item->setData("Label 2 text", Qt::UserRole+1);
item->setData("Label 3 text", Qt::UserRole+2);
item->setData(false, Qt::UserRole+3); // button 1 pressed?

and so on.

If you have your own mode implementation then you need to return proper value in YourMode::data():


QVariant YourModel::data(const QModelIndex &index, int role) const
{
// ...
if (role == Qt::UserRole + 2) {
QString s = "Label 3 text"; // should take proper text for given index from your underlaying data structure
return s;
}
// ...
}

and so on.

pancake
8th July 2010, 11:40
Hi faldżip,

Just wanted to say thanks for such a great code example of a button delegate in a view. It has helped me a lot! Maybe it should be included in the Qt documentation, because their delegate examples do not cover all of this.

Dan

JuanMO
27th August 2010, 14:54
Is too bad idea to create a widget with the QCreator an use it to render all I need? (To avoid doing it manually)
Here is an example




class CustomItemDelegate : public QStyledItemDelegate
{
WidgetItem * witem;
public:

CustomItemDelegate()
{
witem = new WidgetItem ();
}

~CustomItemDelegate()
{
delete witem;
}


void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
witem->resize(option.rect.size());

/*
Here update the witem with some method with the real item data
Update labels, icons, and so on
*/

painter->save();
painter->translate(option.rect.topLeft());
witem->render(painter);
painter->restore();
}
}



Note that the widget is create just once and then all its components updated

JuanMO
27th August 2010, 17:38
If I have two buttons in an item, how can I know which one was press?