PDA

View Full Version : indexWidget() unexpectedly returns a NULL pointer.



ajax
29th November 2010, 18:43
Hi All,

I am attempting to create a model/view application in Qt 4.7.1. I am a very new Qt developer.

Summary of what I am attempting to do:

I have a treeview that is organized as a rectangular table of rows and columns. One column of items contains a button. By default this button is to be transparent and disabled. A given button is to become visible and enabled when the mouse is hovering over its row.

The approach I am pursuing is to

1. find the model index for the cell that the mouse is hovering over, and
2. obtain a pointer to the widget associated with the widget, and
3. using this pointer manipulate the visibility of the button within said widget.

I cannot get a valid pointer to the widget.

my current code looks like this:

void HistoryTreeView::mouseMoveEvent(QMouseEvent *event)
{

QAbstractItemModel *m(model());

// Only do something when a model is set.
if (m)
{

QModelIndex index = indexAt(event->pos());
if (index.isValid())
{

// if the mouse has moved to another row
if (index.row() != m_currentRow)
{

m_currentRow = index.row();

QMessageBox::information( this, "HistoryTreeView", QString("index(%1)").arg(index.row()));

QWidget * item = indexWidget(index);
Q_ASSERT(item != NULL );
}
}
else // model is invalid
{

m_currentRow = -1;
}
}
}

The symptoms:

I expected the call to indexWidget() to return a valid pointer to the widget the mouse is over. Instead it unexpectedly returns a NULL pointer.

Commentary:

The variable named 'index' is acting as I expected because the QMessageBox shows the correct row value. Consequently I do not think there is anything wrong with the value I am providing to indexWidget().

This is just debug code. It is missing things like code that selects the column that holds the buttons.

franz
29th November 2010, 19:17
From your description it is not entirely clear how many columns the view has. If there is more than one column and you have a widget at (0,1) while hovering (0,0) you will get a null pointer. You should explicitly check for the sibling() at the column of the button. If that doesn't do it for you, could you provide a fully compilable example, so that we know more of the code context?

Please use code tags when putting code into your post.

ajax
29th November 2010, 20:19
Hi Franz,

Thank you so much for your quick response.

Sorry about not using the code tag, I didn't notice them until I had already posted. This is my first time using this forum. I will be sure to use code tags in the future.

Regarding your question about which column will hold the button: the buttons are located in column zero.

At this point in the debug I was just trying to get a pointer to any widget in the treeview. My current objective is to simply turn the cell my mouse is hovering over some contrasting color. I reason that achieving this would be a reasonable midway point toward my ultimate goal; I would have to obtain the correct QWidget pointer, etc., before I could, say, change the item's background color.

Once I master this step my next step would be to obtain the QWidget pointer for the cell containing the button, where said cell is on the same row that the mouse is hovering over.

OK, you recommended that I "explicitly check for the sibling() at the column of the button". I was previously unaware of sibling() so I just looked into it. After reading the documentation I think that sibling() would do the job. But my problem is that I need to obtain a pointer to a QWidget for a given index. sibling() returns an index but when I attempt to use a valid index, say in a call to indexWidget() I get a NULL pointer instead of a pointer to a valid widget.

Added after 14 minutes:

Hi Franz, I just modified my code to incorporate your suggestion that I use sibling(). I encountered the same problem as before, indexWidget() returned a NULL pointer when I fed it the index returned by sibling(). The following is what I did:


void HistoryTreeView::mouseMoveEvent(QMouseEvent *event)
{
QAbstractItemModel *m(model());

// Only do something when a model is set.
if (m)
{
QModelIndex index = this->indexAt(event->pos());
if (index.isValid())
{
// if the mouse has moved to another row
if (index.row() != m_currentRow)
{
m_currentRow = index.row();

QMessageBox::information( this, "HistoryTreeView", QString("index(%1)").arg(index.row()));
//this->setCurrentIndex(index);
QModelIndex itemIndex = m->sibling(m_currentRow, 0, index );

QWidget * item = indexWidget(itemIndex);
Q_ASSERT(item != NULL );
}
}
else // model is invalid
{
m_currentRow = -1;
}
}

QTreeView::mouseMoveEvent(event);
}


You meant the sibling() member of QAbstractItemModel, right?

franz
29th November 2010, 20:19
And (just to be sure) you did use setIndexWidget() to set the widgets?

ajax
29th November 2010, 20:24
Uh....no. I'm looking up setIndexWidget() right now. It looks like I need to understand what that does.

franz
29th November 2010, 20:27
It sets the widget you were expecting to get... It is paired up with indexWidget() by name (and functionality).

ajax
29th November 2010, 21:59
OK, I have googled around and I still do not understand the usage of setIndexWidget().

Some more background on what I am trying to do: the items in column zero of the treeview are delegates. The delegate, named ButtonDelegate, inherits from QStyledItemDelegate and is comprised of a QPushButton and a QLabel.

In the window's constructor I assign this delegate to column zero:


ui->treeView->setItemDelegateForColumn(0, new ButtonDelegate);

The documentation gives as an example for using setIndexWidget():


setIndexWidget(index, new QLineEdit);

To my mind this doesn't make sense to me. I understand that setIndexWidget() is associating the index with the newly created QLineEditor, but that is not what I think I want. I think I want a pointer to the widget that is located in column zero so I can do things like make the button located there visible or invisible. Obtaining a pointer to a freshly created object doesn't seem to have anything to do with accessing a widget that is part of the tree view.

wysota
29th November 2010, 22:31
the items in column zero of the treeview are delegates.
No, they are not. The delegate is a helper class, it doesn't "sit" inside a cell.


The delegate, named ButtonDelegate, inherits from QStyledItemDelegate and is comprised of a QPushButton and a QLabel.
That's the editor, not the delegate.


To my mind this doesn't make sense to me.
And you are correct as using this method usually doesn't make much sense...


I understand that setIndexWidget() is associating the index with the newly created QLineEditor,
Let's be strict - the index of the view but not the index of the model. The widget has no relation to the model at all and no relation to the delegate handling that index of the view.


I think I want a pointer to the widget that is located in column zero
There is no widget in column 0.


so I can do things like make the button located there visible or invisible. Obtaining a pointer to a freshly created object doesn't seem to have anything to do with accessing a widget that is part of the tree view.
Use QAbstractItemView::openPersistentEditor() on your items in column 0 of the view. Note that if you open too many of those, your program will become dead slow (that's the case for setIndexWidget as well, by the way).

ajax
29th November 2010, 23:41
Added after 6 minutes:



I think I want a pointer to the widget that is located in column zero
There is no widget in column 0.


I suspect you are thinking I meant something like column zero of the model. When I said

think I want a pointer to the widget that is located in column zero
I was referring to column zero of the treeview.

Correct me if I am wrong, but since views are comprised of widgets there must be a widget in column zero.

wysota
30th November 2010, 00:00
I suspect you are thinking I meant something like column zero of the model. When I said

I was referring to column zero of the treeview.
No, I mean the delegate doesn't put any widgets in your column 0 (of the view, model or the kitchen sink).


Correct me if I am wrong, but since views are comprised of widgets there must be a widget in column zero.
You are wrong, the views are not made of widgets.

ajax
30th November 2010, 00:08
Thank you for your commentary, wysota. I am endeavoring to get up to speed.

I am getting the message that I am approaching my task the wrong way. Let me describe what I am trying to do and you tell me if I am doing something inappropriate or losing.

The spec calls for the application to present a rectangular table of cells to the user. Conceptually the cells are organized as rows of data. In column zero I am to have a push-button that is to be invisible unless the mouse is hovering on the same row as the button's cell.

wysota, I am understanding you to recommended that I use openPersistentEditor() so as to control the pushbutton's visiblity. I am quite surprised that you recommended an editor for this task. I had intended to either (1) hide the button by making it transparent and disabling it or (2) revealing the button by setting the alpha so it can be seen again and enabling it. This seemed like it would be simple and effective. Are you sure openPersistentEditor() is the best way to go about this?

wysota
30th November 2010, 00:23
I am quite surprised that you recommended an editor for this task.
Actually I don't. I only meant this is a way to have a persistant widget in a table cell.


Are you sure openPersistentEditor() is the best way to go about this?
In my opinion the best way is to fake the button in the delegate using QStyle::drawControl() when the mouse is hovering the item and handle clicks in the editorEvent() of the delegate. Or to get rid of the button idea completely and simply react on clicks on the item in the view or do something I did some time ago - use the horizontal header as the button block. The latter is probably the best approach as it makes the user intuitively know he can click the header instead of discovering by chance that if he hovers the mouse over the item some button will appear and he'll be able to click it.

There is also one more possibility which is likely to be the closest to what you want. Subclass the view, reimplement proper events and make it that if the mouse cursor enters the area occupied by some item, you create a real button as a child of the view, position the button over the area of the cell you want and sync the button's position in case the user scrolls the view. Then you'll have one real button you can connect to and handle its clicked() signal directly in the view. Just remember to implement the delegate in such a way that it adds extra space in the column to allow the view to fit the button.

ajax
30th November 2010, 00:44
Thank you for you commentary, wysota.

Regarding you statements:


There is also one more possibility which is likely to be the closest to what you want. Subclass the view, reimplement proper events and make it that if the mouse cursor enters the area occupied by some item, you create a real button as a child of the view, position the button over the area of the cell you want and sync the button's position in case the user scrolls the view.

I might be doing something like this already. I have subclassed QStyledItemDelegate as something I called ButtonDelegate. This class has both a QLabel and a QPushButton. I then did the following:


ui->treeView->setItemDelegateForColumn(0, new ButtonDelegate);

Shouldn't I be able to affect the button aspect of ButtonDelegate? Say, make it invisible or disabled?

wysota
30th November 2010, 10:05
I might be doing something like this already. I have subclassed QStyledItemDelegate as something I called ButtonDelegate.
Then you are not doing something like what I said. The only role of the delegate in my solution is to make space for the button. The button itself is handled entirely in the view class.

This class has both a QLabel and a QPushButton.
What do you mean it "has" a label and a button? Show us the code and I'll show you why your approach is wrong.

ajax
30th November 2010, 16:25
Here is the code.

I am attempted to adapt the code I found in the "Implementing Custom Delegates" section of C++ GUI Programming with Qt 4, pp 266-271. In that code a QTimeEditor was used in a QTableWidget's cell.

In contrast, I need to have a pushbutton in a cell of a treeView.

After reflecting on your comments I reviewed that section of the book and I noticed that the book's example instantiated a QTimeEditor in the openEditor() function. I do not desire for my pushbutton's state to be affected by the openEditor() function because:


by design, the items of this treeview are read-only, the user cannot edit them, and
the concept of 'editing' a pushbutton makes no sense



What do you mean it "has" a label and a button? Show us the code and I'll show you why your approach is wrong.


It is the ButtonDelegate class that has a label and a button.


// ButtonDelegate.h
class ButtonDelegate : public QStyledItemDelegate
{
Q_OBJECT

public:
explicit ButtonDelegate(QObject *parent=0);
~ButtonDelegate();

void paint( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;

QSize ButtonDelegate::sizeHint( const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void unpolish(QWidget *widget);

void polish(QWidget *widget);

void paintWidget( QPainter *painter,
const QRect &rect,
const QString &cacheKey,
QWidget *widget ) const;
protected:

void enterEvent ( QEvent * );
void leaveEvent ( QEvent * );

private:

mutable QTextDocument document;
QPushButton *button;
QLabel *label;

};

// ButtonDelegate.cpp
ButtonDelegate::ButtonDelegate(QObject *parent) : QStyledItemDelegate(parent)
{
button = new QPushButton( "Wild!");
button->setFixedWidth( 50); // I'm setting this in pixels. There has to be a better way.

QPalette pal = button->palette();
pal.setColor(QPalette::ButtonText, QColor(255, 0, 0));
pal.setColor(QPalette::Button, QColor(255, 255, 0));
button->setPalette(pal);

label = new QLabel;
label->setTextFormat(Qt::RichText);
label->setWordWrap(false);
}


//HistoryTreeView.h
class HistoryTreeView : public QTreeView
{
public:
HistoryTreeView(QWidget *parent = 0);

protected:
virtual void mouseMoveEvent(QMouseEvent *event);
void enterEvent( QEvent * event );
void leaveEvent( QEvent * event );

private:
int m_currentRow;
};

//HistoryTreeView.cpp
void HistoryTreeView::mouseMoveEvent(QMouseEvent *event)
{
QAbstractItemModel *m(model());

// Only do something when a model is set.
if (m)
{
QModelIndex index = this->indexAt(event->pos());
if (index.isValid())
{
// if the mouse has moved to another row
if (index.row() != m_currentRow)
{
m_currentRow = index.row();

// TODO: clear all rows

QModelIndex itemIndex = m->sibling(m_currentRow, 0, index );
QWidget * item = indexWidget(itemIndex);
Q_ASSERT(item != NULL );
}
}
else // model is invalid
{
m_currentRow = -1;
}
}
QTreeView::mouseMoveEvent(event);
}

//HistoryWindow.h
class HistoryWindow : public QMainWindow
{
Q_OBJECT

public:
explicit HistoryWindow(QWidget *parent = 0);
~HistoryWindow();

void setupModel();
void addFile( QStandardItemModel *model,
const QString &anAttribute,
const QString &anotherAttrib,
const QString &path,
const QString &datestamp,
const QString &text );

void SetMouseTransitSignals(QWidget *parent);

protected:
void enterEvent(QEvent*);
void leaveEvent(QEvent*);

private:
Ui::HistoryWindow *ui;
HistoryItemModel *model;
HistoryTreeView *treeView;
void setupData();
void createModelAndView();

};

//HistoryWindow.cpp
void HistoryWindow::createModelAndView()
{
setupModel();
ui->treeView->setItemDelegateForColumn(0, new ButtonDelegate);
ui->treeView->setAllColumnsShowFocus(true);
ui->treeView->setModel(model);
SetMouseTransitSignals(this);
}


void HistoryWindow::SetMouseTransitSignals(QWidget *parent)
{
// enable mouse tracking for the treeview.
ui->treeView->setMouseTracking(true);

}

void HistoryWindow::enterEvent(QEvent*e)
{
QWidget::enterEvent(e);
}

wysota
30th November 2010, 16:43
Look into your code. The two widgets you create have nothing to do with the model-view architecture - none of QAbstractItemDelegate methods do anything with them. If you look closely you will notice your delegate class doesn't customize anything from its baseclass nor does it access any of its base class fields or methods - your "ButtonDelegate" class might not have a base class at all - it doesn't provide any delegate functionality its base class wouldn't already be providing. The delegate has three basic functions: 1) it paints the item, 2) it handles editing the item, 3) it handles input events for the item (which is a special case of editing the item). All this is done to bridge the view and the model whereas your "button" has no relation to the model at all and the "label" is completely useless as the item is painted by the delegate's paint() method.

ajax
30th November 2010, 17:07
When do you sleep? :) You were active pretty late last night. I'm impressed.


- your "ButtonDelegate" class might not have a base class at all

Uh, ButtonDelegate is derived from QStyledItemDelegate, wouldn't that be its base class?


your "button" has no relation to the model at all

True. The intent of the button is to pop a context sensitive menu. The content of this context sensitive menu would be affected by the data in the other cells on the same row. As of this writing I have not worked out how I am going to access this data; I am still trying to get the button-appearing-on-hover functionality working.


the "label" is completely useless as the item is painted by the delegate's paint() method.


The label's text is appearing so it looks like the paint() is doing the job.

Ok, all of this is just a quick response to your post. I am going to look into QAbstractItemDelegate a see if what it has that I should be using.

wysota
30th November 2010, 17:19
Uh, ButtonDelegate is derived from QStyledItemDelegate, wouldn't that be its base class?
It is but nothing of its functionality will change if you derive it from... say... QPoint or std::vector. Your delegate class may be derived from a delegate class but it doesn't provide any delegate functionality.


The label's text is appearing so it looks like the paint() is doing the job.
Remove the label and it will still be appearing because that's what QStyleDelegate::paint() provides. There is one delegate (hence one label of yours) and say.... 1000 items in the model so is your label showing one text or 1000 different texts?


Ok, all of this is just a quick response to your post. I am going to look into QAbstractItemDelegate a see if what it has that I should be using.
The only use for a delegate in your situation that I see is either to reimplement sizeHint() to push the contents of the item aside for the button or to draw the button in the delegate's paint() method using QStyle::drawControl().

ajax
30th November 2010, 17:44
The label's text is appearing so it looks like the paint() is doing the job.
Remove the label and it will still be appearing because that's what QStyleDelegate::paint() provides. There is one delegate (hence one label of yours) and say.... 1000 items in the model so is your label showing one text or 1000 different texts?


The buttonDelegate is only used in column 0. Each label for each buttonDelegate shows different text when I run the application because every element of the model is populated with distinct data.

When I comment-out the QLabel from ButtonDelegate and comment-out all references to it in the code none of the text appears in column 0 when I run the app.

Added after 6 minutes:

for what it is worth, this thread (http://www.qtcentre.org/archive/index.php/t-16647.html) touches on some of my concerns.

wysota
30th November 2010, 19:37
The buttonDelegate is only used in column 0. Each label for each buttonDelegate shows different text when I run the application because every element of the model is populated with distinct data.
Yet you only have one label because you have only one delegate and this single label shows multiple texts simoultaneously in different places. Wow... I wonder what are the values of its "text" and "geometry" properties in this situation. How exactly do you display data on this label?


When I comment-out the QLabel from ButtonDelegate and comment-out all references to it in the code none of the text appears in column 0 when I run the app.
What if you also comment out existance of the paint() reimplementation in your delegate or call the base class implementation?

ajax
30th November 2010, 22:07
Evidently I must be doing something weird. Not unusual. :)

In the meantime I was tinkering with some code. I had to #if 0 my changes to get back to where I was before the changes. Anyway, I got it back. Here is the code:




class ButtonDelegate : public QStyledItemDelegate
{
Q_OBJECT

public:
explicit ButtonDelegate(QObject *parent=0);
~ButtonDelegate();

void paint( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;

QSize ButtonDelegate::sizeHint( const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void unpolish(QWidget *widget);

void polish(QWidget *widget);

void paintWidget( QPainter *painter,
const QRect &rect,
const QString &cacheKey,
QWidget *widget ) const;

protected:

void enterEvent ( QEvent * );
void leaveEvent ( QEvent * );

private:

mutable QTextDocument document;
QPushButton *button;
QLabel *label;

};


ButtonDelegate::ButtonDelegate(QObject *parent) : QStyledItemDelegate(parent)
{
button = new QPushButton( "Wild!");
button->setFixedWidth( 50); // I'm setting this in pixels. There has to be a better way.

QPalette pal = button->palette();
pal.setColor(QPalette::ButtonText, QColor(255, 0, 0));
pal.setColor(QPalette::Button, QColor(255, 255, 0));
button->setPalette(pal);
#if 1
label = new QLabel;
label->setTextFormat(Qt::RichText);
label->setWordWrap(false);
#endif
}

ButtonDelegate::~ButtonDelegate()
{
delete button;
delete label;
}


void ButtonDelegate::paint( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
bool selected = option.state & QStyle::State_Selected;
int yOffset = button->height() < option.rect.height()
? (option.rect.height() - button->height()) / 2 : 0;


QRect labelRect(option.rect.x(),
option.rect.y() + yOffset,
option.rect.width() - button->width(),
option.rect.height());

QRect buttonRect( option.rect.x() + labelRect.width(),
option.rect.y(),
option.rect.width() - labelRect.width(),
option.rect.height());

label->setFixedSize(qMax(0, labelRect.width()),
labelRect.height());

QString html = index.model()->data(index, Qt::DisplayRole).toString();

label->setText(html);

QString buttonKey("PUSHBUTTON");
paintWidget(painter, buttonRect, buttonKey, button);

QString labelKey = QString("LABEL:%1.%2.%3x%4").arg(selected)
.arg(html)
.arg(labelRect.width())
.arg(labelRect.height());
paintWidget(painter, labelRect, labelKey, label);
}



void ButtonDelegate::paintWidget( QPainter *painter,
const QRect &rect,
const QString &cacheKey,
QWidget *widget ) const
{
QPixmap pixmap(widget->size());

if (!QPixmapCache::find(cacheKey, &pixmap))
{
widget->render(&pixmap);
QPixmapCache::insert(cacheKey, pixmap);
}

painter->drawPixmap(rect, pixmap);
}



void HistoryWindow::createModelAndView()
{
setupModel();
ui->treeView->setItemDelegateForColumn(0, new ButtonDelegate);
ui->treeView->setAllColumnsShowFocus(true);
ui->treeView->setModel(model);
SetMouseTransitSignals(this);
for ( int i = 0; i < model->rowCount(); ++i )
{
ui->treeView->openPersistentEditor( model->index(i, 0, QModelIndex()) );
}

}



The openPersistentEditor() is something I added since your last post. This is something that I want to discuss afterward.

Also, the buttons I had in each delegate vanished. I don't what I missed when I was restoring the code. But you were interested in the labels not the buttons so I didn't pursue it.

In any case, this code assigns a delegate to the cells in column zero. The model is populated with unique data in every element and that causes the delegates to each have unique data.

I don't know if this is responsive to your interest in how I accomplished this. Is there something else you would like to see?

Added after 8 minutes:

Oh, I overlooked your question

What if you also comment out existance of the paint() reimplementation in your delegate or call the base class implementation?

I commented out paint() and it had no effect. I was surprised.

Added after 4 minutes:

Update: calling QStyledItemDelegate::paint() from within ButtonDelegate::paint()--and doing nothing else--had no effect.

Added after 46 minutes:

Let me change the subject a little. Using openPersistentEditor() I found I could cause another way to make the button appear. Now I needed some way to cause all buttons to disappear except for the button in the mouse's current row.

currently the createModelAndView() function is called from the HistoryWindow's constructor. It causes a button to appear in column zero for every row.


void HistoryWindow::createModelAndView()
{
setupModel();
ui->treeView->setItemDelegateForColumn(0, new ButtonDelegate);
ui->treeView->setAllColumnsShowFocus(true);
ui->treeView->setModel(model);
SetMouseTransitSignals(this);

for ( int i = 0; i < model->rowCount(); ++i )
{
ui->treeView->openPersistentEditor( model->index(i, 0, QModelIndex()) );
}
}

I would have thought the following code would have caused a button to appear in on only the row where the mouse is hovering. It doesn't work as I expected. closePersistentEditor(), which is called from within a 'for loop', does a fine job of making all buttons disappear but, for some reason the following call to openPersistentEditor() unexpectedly fails to cause any buttons to reappear. Why would the call to openPersistentEditor() not have an effect?



void HistoryTreeView::mouseMoveEvent(QMouseEvent *event)
{
QAbstractItemModel *m(model());

// Only do something when a model is set.
if (m)
{
QModelIndex index = this->indexAt(event->pos());
if (index.isValid())
{

// if the mouse has moved to another row
if (index.row() != m_currentRow)
{
m_currentRow = index.row();

// clear buttons from all rows

for ( int i = 0; i < m->rowCount(); ++i )
{
this->closePersistentEditor( m->index(i, 0, QModelIndex()) );
}

// create button in mouse's current row
this->openPersistentEditor(m->index(m_currentRow, 0, index ));
}
}
else // model is invalid
{
m_currentRow = -1;
}
}

QTreeView::mouseMoveEvent(event);
}

wysota
30th November 2010, 22:57
Your code doesn't make much sense, your label is not even visible! Try calling show() on it and you'll see where it appears. I suggest you take a look at examples bundled with Qt that provide a custom delegate. You'll see how a delegate should be implemented. But again, in my opinion you can scrap your whole delegate because it does nothing useful, I don't know who suggested you it should be implemented this way. All you need is a floating button in the view and place it in the right position as required.

Added after 13 minutes:

Here is a quick example of what I mean regarding what you could do:


#include <QtGui>


class View : public QTableView {
Q_OBJECT
public:
View(QWidget *parent = 0) : QTableView(parent) {
viewport()->installEventFilter(this);
viewport()->setMouseTracking(true);
buttonRow = -1;
button = new QPushButton("Click me", viewport());
button->hide();
connect(button, SIGNAL(clicked()), this, SLOT(_q_buttonClicked()));
}
protected:
bool eventFilter(QObject *o, QEvent *e){
if(o==viewport()){
if(e->type() == QEvent::MouseMove){
QMouseEvent *ev = (QMouseEvent*)e;
if(!ev->buttons()) {
QModelIndex idx = indexAt(ev->pos());
if(idx.isValid()){
buttonRow = idx.row();
QRect r = visualRect(idx);
button->show();
button->setGeometry(r.right()+2, r.top(), button->sizeHint().width(), r.height());
} else {
button->hide();
buttonRow = -1;
}
return true;
}
}
}
return QTableView::eventFilter(o, e);
}
private slots:
void _q_buttonClicked() {
QMessageBox::information(this, "click", QString("Clicked on row %1").arg(buttonRow+1));
}
private:
QPushButton *button;
int buttonRow;
};

#include "main.moc"

int main(int argc, char **argv){
QApplication app(argc, argv);
QStringListModel model;
model.setStringList(QStringList() << "abc" << "def" << "ghi" << "jkl" << "mno" << "pqr" << "stu" << "vwx");
View view;
view.setModel(&model);
view.show();
return app.exec();
}

As you can see, no custom delegate and items still get painted (without any labels).

ajax
30th November 2010, 23:02
your label is not even visible

But I see the label text! It is visible!

Regarding why I'm using a delegate: nobody suggested it to me. Our company just started with Qt for the first time a couple of weeks ago. There is nobody else where with any more experience here than I have.

I arrived at the idea for using a delegate because the spec essentially calls for a button in the same cell as some text, where said cell is in in column zero of a table. I didn't see anything standard that supported that requirement. I read a recommendation in the book C++ GUI Programming with Qt 4 that stated I could have better control over the rendering of items if I used a delegate. After examining some examples I conclude that a delegate would do the job. Of course there is more than one way to do most things, but since I am a newbie I couldn't see another way to get it done.

Changing the subject back to the question I asked regarding openPersistentEditor(), do you have an idea why it cannot make the button reappear?

Put another way, I believe that I should be able to make a given button to repeatedly appear and disappear by calling openPersistentEditor() and closePersistentEditor(). Am I wrong?

wysota
30th November 2010, 23:12
But I see the label text! It is visible!
No, you see the text, not the label text. As an experiment you can change the background or font of the label and see if it is reflected in all your items.

Edit: Actually it will be because you are then rendering the label to a pixmap and rendering the pixmap to the item which should already make you suspicious that the label part is not needed here as you can paint the text directly.


Regarding why I'm using a delegate: nobody suggested it to me. Our company just started with Qt for the first time a couple of weeks ago. There is nobody else where with any more experience here than I have.
So you came up with the polish() and unpolish() methods yourself?


I read a recommendation in the book C++ GUI Programming with Qt 4 that stated I could have better control over the rendering of items if I used a delegate.
You are not trying to have more control over rendering items. You want to stuff a button in the view.


Changing the subject back to the question I asked regarding openPersistentEditor(), do you have an idea why it cannot make the button reappear?
To be honest I didn't read that part of your post. This is not a correct way to go so I didn't bother to read about it.


Put another way, I believe that I should be able to make a given button to repeatedly appear and disappear by calling openPersistentEditor() and closePersistentEditor(). Am I wrong?
"It depends on".

And see the example from my last post.

ajax
1st December 2010, 00:59
But I see the label text! It is visible!
No, you see the text, not the label text. As an experiment you can change the background or font of the label and see if it is reflected in all your items.


You're correct. I was wrong. I commented out the label stuff from the code and the text was still there. So the label was not containing the text.



Regarding why I'm using a delegate: nobody suggested it to me. Our company just started with Qt for the first time a couple of weeks ago. There is nobody else where with any more experience here than I have.
So you came up with the polish() and unpolish() methods yourself?

Yes. I found them in the book C++ GUI Programming with Qt 4, around pp. 458. They seemed like they might be what I wanted. These methods are leftover from earlier experimentation. At this point I doubt that they are what I need.



But I see the label text! It is visible!
No, you see the text, not the label text. As an experiment you can change the background or font of the label and see if it is reflected in all your items.


You're correct. I was wrong. I commented out the label stuff from the code and the text was still there. So the label was not containing the text.



Regarding why I'm using a delegate: nobody suggested it to me. Our company just started with Qt for the first time a couple of weeks ago. There is nobody else where with any more experience here than I have.
So you came up with the polish() and unpolish() methods yourself?

Yes. I found them in the book C++ GUI Programming with Qt 4, around pp. 458. They seemed like they might be what I wanted. These methods are leftover from earlier experimentation. At this point I doubt that they are what I need.

Added after 32 minutes:

OK: now I have the button appearing and disappearing as I need them to do. Not sure what I did but I made sure to commit the code while it is still working.

Also, wysota, thank you for your patience.

Now I need to cause the button to float against the right border of its cell. My initial unsuccessful attempt was:


void ButtonDelegate::setEditorData( QWidget * editor ,
const QModelIndex & /* index */) const
{
editor->setMaximumWidth( 50 ); // this worked
editor->move(-50,0); // this had no apparent effect
}

My next unsuccessful attempt was:


void ButtonDelegate::setEditorData( QWidget * editor ,
const QModelIndex & /* index */) const
{
editor->setMaximumWidth( 50 ); // this worked
editor->setStyleSheet("float:right;"); // this had no apparent effect
}

What do you recommend?

wysota
1st December 2010, 08:40
Yes. I found them in the book C++ GUI Programming with Qt 4, around pp. 458.
So you didn't come up with it yourself but rather the authors of the book suggested it.


What do you recommend?
How did you implement the button?

ajax
1st December 2010, 15:14
Yes. I found them in the book C++ GUI Programming with Qt 4, around pp. 458.
So you didn't come up with it yourself but rather the authors of the book suggested it.


Well the authors described how to solve various scenarios using Qt. Nothing I have found, in that book or elsewhere on the Internet, exactly matches what I am attempting to do. Also, while I have years of programming experience (e.g., embedded, databases, telephony, and so on) I have little GUI programming experience and no prior Qt experience.

So I see myself as parachuting into unfamiliar terrain and casting around for anything that appears to fit the problem I am trying to solve. The risk, of course, is that the stuff I notice might be a much poorer solution than other things that I just haven't yet found. Consequently, I could naively select an inappropriate approach just because I found the inappropriate approach before I happened on a much more suitable one.

The polish()/unpolish() stuff is an example of something that seemed to me at one time to be a possible part of the solution. Later abandoned that idea but I didn't delete the code. (Hey, who knows, it might be useful later!) By the time you asked about the polish()/unpolish() stuff I'd practically forgotten about it



Yes. I found them in the book C++ GUI Programming with Qt 4, around pp. 458.
So you didn't come up with it yourself but rather the authors of the book suggested it.


Well the authors described how to solve various scenarios using Qt. Nothing I have found, in that book or elsewhere on the Internet, exactly matches what I am attempting to do. Also, while I have years of programming experience (e.g., embedded, databases, telephony, and so on) I have little GUI programming experience and no prior Qt experience.

So I see myself as parachuting into unfamiliar terrain and casting around for anything that appears to fit the problem I am trying to solve. The risk, of course, is that the stuff I notice might be a much poorer solution than other things that I just haven't yet found. Consequently, I could naively select an inappropriate approach just because I found the inappropriate approach before I happened on a much more suitable one.

The polish()/unpolish() stuff is an example of something that seemed to me at one time to be a possible part of the solution. Later abandoned that idea but I didn't delete the code. (Hey, who knows, it might be useful later!) By the time you asked about the polish()/unpolish() stuff I'd practically forgotten about it

Added after 12 minutes:


How did you implement the button?



QWidget * ButtonDelegate::createEditor( QWidget * parent,
const QStyleOptionViewItem & option,
const QModelIndex & index ) const
{
if (index.column() == 0 )
{
return new QPushButton("Share", parent);
}
else
{
return QStyledItemDelegate::createEditor(parent, option, index );
}
}

void ButtonDelegate::setEditorData( QWidget * editor ,
const QModelIndex & /* index */) const
{
editor->setMaximumWidth( 50 ); // in px
editor->setStyleSheet("float:right;"); // didn't affect button location
//editor->move(-50,0); // this didn't work either
}

wysota
1st December 2010, 15:53
So you have to click the cell for the button to appear and at the same time the content of the cell disappears. Is that really what you wanted?

ajax
1st December 2010, 16:17
So you have to click the cell for the button to appear and at the same time the content of the cell disappears. Is that really what you wanted?


Uh, no that is not what I desire. The spec requires:

that the button normally be invisible
That the button be visible so long as the mouse is hovering somewhere over the same row where the button resides
and that the button vanish after the mouse departs from the button's row


As of this writing I am happy to say that the buttons are exhibiting this exact behavior. My current issue is that the button is located on the left edge of the cell. I desire to make it right-aligned. I found a Qt::AlignRight enum that would seem to specify the desired button location but I cannot figure out how to use it.

Anyway, getting back to the behavior the button is to show when it is pressed: The button is to pop a context sensitive dialog when the user clicks it. The exact text displayed in the pop-up dialog will be determined by the data contained within the clicked button's row.

wysota
1st December 2010, 16:36
Ok, if you want to go this way I will not stop you although this all implicitly makes the column of your model that the view displays the buttons in read-only and is in general the wrong approach. So that you know...


My current issue is that the button is located on the left edge of the cell.
Reimplement QAbstractItemDelegate::updateEditorGeometry().

ajax
1st December 2010, 22:04
Reimplement QAbstractItemDelegate::updateEditorGeometry().

OK, I have googled around quite a bit and it appears that I need to do something like:


QStyleOptionViewItem opt = viewOptions();
opt.decorationAlignment = Qt::AlignRight;


My problem is that I cannot figure out how to do something like this from within the context of updateEditorGeometry().

How would I approach this?

wysota
1st December 2010, 22:29
OK, I have googled around quite a bit and it appears that I need to do something like:


QStyleOptionViewItem opt = viewOptions();
opt.decorationAlignment = Qt::AlignRight;

No, not really. Just call setGeometry on the widget with the parameters you want based on the rectangle provided by the style option.

ajax
2nd December 2010, 15:25
Thanks wysota!

as a public service to the other newbies, here is what I did:


void ButtonDelegate::updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index ) const
{
QStyleOptionViewItem opt( option );

//subtract the width of the button from the width of its containing cell
const int x = opt.rect.width() - SELECT_BUTTON_WIDTH;

// right align the button in the cell
opt.rect.setX( x );
editor->setGeometry(opt.rect);
}

wysota
2nd December 2010, 16:47
Also as a public service there is a note for all newbies that the functionality desired by the OP should not be implemented this way (with "this way" meaning to use createEditor() and family to place a button or any other widget into a model item with existing data).