19

I have created one table by using QTableview and QAbstractTableModel . In one of the cells, I want to add one help button in the right corner of that cell.

Is there any way to achieve this?

ggorlen
  • 44,755
  • 7
  • 76
  • 106
Learner
  • 245
  • 1
  • 3
  • 9

6 Answers6

34

You will have to implement your own delegate for that.

In Qt, aside from the Data, the Model and the View, you have your Delegates. They provide input capabilities, and they are also responsible for rendering "special" items in the View, which is what you need.

Qt doc has a good coverage on those (keywords: Model/View programming), and you can also find some examples here and here.

Also (a little off-topic, but I think I should point this out), if you use an ordinary QTableWidget, you can insert anything into any cell with it's setCellWidget() function.

UPD

here is a slightly modified example from Qt docs (I suck with model/view stuff in Qt so don't beat me hard for this code). It will draw a button in each cell on the right, and catch the click events in cells to check if the click was on the "button", and react accordingly.

Probably this is not the best way to do it, but as I mentioned, I'm not too good with Qt's models and views.

To do things right and allow proper editing, you will need to also implement createEditor(), setEditorData() and setModelData() functions.

To draw your stuff in a specific cell instead of all cells, just add a condition into the paint() function (note that it gets the model index as an argument, so you can always know in what cell you are painting, and paint accordingly).


delegate.h:

class MyDelegate : public QItemDelegate
{
    Q_OBJECT

public:
    MyDelegate(QObject *parent = 0);
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index);
};

delegate.cpp:

 #include <QtGui>
 #include "delegate.h"

 MyDelegate::MyDelegate(QObject *parent)
     : QItemDelegate(parent)
 {
 }


 void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
 {
     QStyleOptionButton button;
     QRect r = option.rect;//getting the rect of the cell
     int x,y,w,h;
     x = r.left() + r.width() - 30;//the X coordinate
     y = r.top();//the Y coordinate
     w = 30;//button width
     h = 30;//button height
     button.rect = QRect(x,y,w,h);
     button.text = "=^.^=";
     button.state = QStyle::State_Enabled;

     QApplication::style()->drawControl( QStyle::CE_PushButton, &button, painter);
 }

 bool MyDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
 {
     if( event->type() == QEvent::MouseButtonRelease )
     {
         QMouseEvent * e = (QMouseEvent *)event;
         int clickX = e->x();
         int clickY = e->y();

         QRect r = option.rect;//getting the rect of the cell
         int x,y,w,h;
         x = r.left() + r.width() - 30;//the X coordinate
         y = r.top();//the Y coordinate
         w = 30;//button width
         h = 30;//button height

         if( clickX > x && clickX < x + w )
             if( clickY > y && clickY < y + h )
             {
                 QDialog * d = new QDialog();
                 d->setGeometry(0,0,100,100);
                 d->show();
             }
     }

     return true;
 }

main.cpp

#include "delegate.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QStandardItemModel model(4, 2);
    QTableView tableView;
    tableView.setModel(&model);

    MyDelegate delegate;
    tableView.setItemDelegate(&delegate);

    tableView.horizontalHeader()->setStretchLastSection(true);
    tableView.show();
    return app.exec();
}

The result will look like this:

result

jaba
  • 735
  • 7
  • 18
SingerOfTheFall
  • 29,228
  • 8
  • 68
  • 105
  • i am using QTableview . i want to put that button in a specific cell(right corner of that cell) .. could you please share some codes ? thankx in adv .. – Learner Aug 02 '12 at 18:45
  • @vikuseth, Oo it's always visible for me. – SingerOfTheFall Aug 04 '12 at 18:12
  • ya you are right .. please ignore my last comment . But when we are going for editing the cell again it disappears ..is there any solution for that ? – Learner Aug 04 '12 at 20:19
  • But here when we are using our delegates the items present inside the cell is not appearing .(like 1,2,3,4 in the above eg.) . Is there any idea how to solve this ? – Learner Aug 05 '12 at 14:35
  • sorry to ask all this silly ques . since i am new to Qt thats why i am not able to understand whats happening . – Learner Aug 05 '12 at 14:38
  • I need one more help .. i want to set one icon and stylesheet on the top of that button .. I am using button.icon= QIcon(QString::fromUtf8("Resources/Restore.png")); button.iconSize = QSize( 12, 12 ); but here the icon is set left to that button .. i have tried different coordinate but i am not able to find out what coordinate is ok for this .. i have searched in google too but not able to find out how to set the stylesheet for QStyleOptionButton. can u please give some idea on it ? Thankx a lot to all for your valuable reply. – Learner Aug 08 '12 at 14:25
  • @vikuseth, do you mean that you need to have the button text below the icon? If yes, then you will probably have to use `QToolButton` instead. I've had this problem myself and it seems that there is no easy way to put the text below or above the icon on a `QPushButton`, without making your own subclass. So just use `QToolButton` instead, and use it's `toolButtonStyle` property (you can set it with `setToolButtonStyle()` function). For example, if you want text below the icon, set it to `ToolButtonTextUnderIcon`. – SingerOfTheFall Aug 08 '12 at 15:36
14

I've got a solution WITHOUT any complex re-invention of the whole paint-processes. I have a TableView in which I have a button in every row. Note, that, in my case the for loop runs through each row.

QSignalMapper *signalMapper = new QSignalMapper(this);

for( int i=0; i<rows.length(); i++ ) { //replace rows.length with your list or vector which consists of the data for your rows.
     //do something with your data for normal cells...         
     auto item = model->index(i, COLUMN_FOR_WHATEVER_YOU_WANT);
     model->setData(item, QVariant::fromValue(yourObject.getSpecificInformation()));

     //make new button for this row 
     item = model->index(i, COLUMN_BUTTON); 
     QPushButton *cartButton = new QPushButton("Add");
     ui->table_view->setIndexWidget(item, cartButton);

     signalMapper->setMapping(cartButton, i);
     connect(cartButton, SIGNAL(clicked(bool)), signalMapper, SLOT(map()));
}
connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(doSomething(int)));

Then you automatically get the index of the row in which the user clicked the button. You just need to make your own slot:

private SLOTS:
    void doSomething(int row);

If you have specific cells, it would work similar.

Note, that I did not care about memory leaks in this example, and I don't exactly know what would happen if you update your TableView... (It's working fine, but it might not delete the old button-pointers when new ones are created)

ElectRocnic
  • 1,275
  • 1
  • 14
  • 25
  • 1
    In Qt5 you don't even have to use QSignalMapper; you can connect button;s signal to a lambda with the enclosure of your choice. – maciek gajewski Jul 27 '16 at 09:43
  • 1
    Please note that setIndexWidget is supposed to only be used for static content as it has abysmal performance. If you only ever have one row with a button - use it. If you have n-rows and adding/removing capabilities, prefer using a QStyledItemDelegate derivative instead. – Folling Aug 04 '20 at 07:29
7

setIndexWidget worked for me. Example:

QPushButton* helpButton = new QPushButton("Help");

tableView->setIndexWidget(model->index(position,COLUMN_NUMBER), helpButton);

If you just want to add a button and do something on click of it, adding a button using setIndexWidget() works fine. I believe we don't need the cumbersome paint method or implementing delegates.

wazza
  • 313
  • 3
  • 19
  • I find it will be failure if i add `QList headerlabels = {"col1","col2","col3","col4"}; model->setHorizontalHeaderLabels(headerlabels);` before `setIndexWidget`, but if place to after `setIndexWidget`, it works right ! – Wade Wang Jun 07 '21 at 06:10
  • This doesn't make sense as I'm needing to add a button to EVERY row of the table and as a new row is added, the button would be added to the table on that row. I saw the position argument but at the creation of the table, one wouldn't have access to the row to add it. Something tells me the Delegate is the only suitable solution but I'm still investigating. – Seth D. Fulmer Jul 27 '23 at 20:40
3
    // use only standard style  
QApplication::style()->drawControl(QStyle::CE_PushButtonLabel, &button, painter);

For use user styles, need changed:

    //use user style   
QPushButton* real_button = ....; // button inherited user styles
    real_button->style()->drawControl( QStyle::CE_PushButtonLabel, &button, painter, real_button);
e.shirshov
  • 41
  • 2
0

When the view wants to draw a cell it calls the delegate’s paint() function with some information about how, what, and where to draw the contents of the cell. The default delegate just draws the Qt::DisplayRole text and selectionState.

If you replace the delegate then you completely replace the default behaviour: you can draw whatever you like. If you want the text then you need to arrange to draw it. You can do it yourself or, using standard C++ mechanisms, you can call the default drawing code first then draw over the top. It works after adding QItemDelegate::paint(painter, option, index); at the beginning of my paint() method .

AAEM
  • 1,837
  • 2
  • 18
  • 26
Learner
  • 245
  • 1
  • 3
  • 9
0

I got the solution .. Old paint method :

void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
 {
     QStyleOptionButton button;
     QRect r = option.rect;//getting the rect of the cell
     int x,y,w,h;
     x = r.left() + r.width() - 30;//the X coordinate
     y = r.top();//the Y coordinate
     w = 30;//button width
     h = 30;//button height
     button.rect = QRect(x,y,w,h);
     button.text = "=^.^=";
     button.state = QStyle::State_Enabled;

     QApplication::style()->drawControl( QStyle::CE_PushButton, &button, painter);
 }

here is the updated paint() method .

void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
 {
         QItemDelegate::paint(painter, option, index);
  if(index.row()==8)//since i have to make it display only at (8,0) position .
 {
  if(index.column()==0)
  {
     QStyleOptionButton button;
     QRect r = option.rect;//getting the rect of the cell
     int x,y,w,h;
     x = r.left() + r.width() - 20;//the X coordinate
     y = r.top();//the Y coordinate
     w = 15;//button width(based on the requirement)
     h = 15;//button height(based on the requirement)
  button.icon= QIcon(QString::fromUtf8("Resources/HelpIcon.png"));
  button.iconSize = QSize(20,20);
     button.rect = QRect(x,y,w,h);
     button.text = "";//no text . since if text will be given then it will push the icon to left side based on the coordinates .
     button.state = QStyle::State_Enabled;

     //QApplication::style()->drawControl( QStyle::CE_PushButton, &button, painter);

  QApplication::style()->drawControl( QStyle::CE_PushButtonLabel, &button, painter);//To make the Button transparent .

  }
  }
}
Learner
  • 245
  • 1
  • 3
  • 9