indexWidget() unexpectedly returns a NULL pointer.
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.
Re: indexWidget() unexpectedly returns a NULL pointer.
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.
Re: indexWidget() unexpectedly returns a NULL pointer.
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:
Code:
void HistoryTreeView
::mouseMoveEvent(QMouseEvent *event
) {
// Only do something when a model is set.
if (m)
{
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;
}
}
}
You meant the sibling() member of QAbstractItemModel, right?
Re: indexWidget() unexpectedly returns a NULL pointer.
And (just to be sure) you did use setIndexWidget() to set the widgets?
Re: indexWidget() unexpectedly returns a NULL pointer.
Uh....no. I'm looking up setIndexWidget() right now. It looks like I need to understand what that does.
Re: indexWidget() unexpectedly returns a NULL pointer.
It sets the widget you were expecting to get... It is paired up with indexWidget() by name (and functionality).
Re: indexWidget() unexpectedly returns a NULL pointer.
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:
Code:
ui->treeView->setItemDelegateForColumn(0, new ButtonDelegate);
The documentation gives as an example for using setIndexWidget():
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.
Re: indexWidget() unexpectedly returns a NULL pointer.
Quote:
Originally Posted by
ajax
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.
Quote:
The delegate, named ButtonDelegate, inherits from QStyledItemDelegate and is comprised of a QPushButton and a QLabel.
That's the editor, not the delegate.
Quote:
To my mind this doesn't make sense to me.
And you are correct as using this method usually doesn't make much sense...
Quote:
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.
Quote:
I think I want a pointer to the widget that is located in column zero
There is no widget in column 0.
Quote:
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).
Re: indexWidget() unexpectedly returns a NULL pointer.
Added after 6 minutes:
Quote:
Quote:
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
Quote:
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.
Re: indexWidget() unexpectedly returns a NULL pointer.
Quote:
Originally Posted by
ajax
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).
Quote:
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.
Re: indexWidget() unexpectedly returns a NULL pointer.
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?
Re: indexWidget() unexpectedly returns a NULL pointer.
Quote:
Originally Posted by
ajax
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.
Quote:
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.
Re: indexWidget() unexpectedly returns a NULL pointer.
Thank you for you commentary, wysota.
Regarding you statements:
Quote:
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:
Code:
ui->treeView->setItemDelegateForColumn(0, new ButtonDelegate);
Shouldn't I be able to affect the button aspect of ButtonDelegate? Say, make it invisible or disabled?
Re: indexWidget() unexpectedly returns a NULL pointer.
Quote:
Originally Posted by
ajax
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.
Quote:
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.
Re: indexWidget() unexpectedly returns a NULL pointer.
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
Quote:
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.
Code:
// ButtonDelegate.h
class ButtonDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit ButtonDelegate
(QObject *parent
=0);
~ButtonDelegate();
protected:
private:
};
// ButtonDelegate.cpp
ButtonDelegate
::ButtonDelegate(QObject *parent
) : QStyledItemDelegate
(parent
){
button->setFixedWidth( 50); // I'm setting this in pixels. There has to be a better way.
button->setPalette(pal);
label->setTextFormat(Qt::RichText);
label->setWordWrap(false);
}
//HistoryTreeView.h
{
public:
HistoryTreeView
(QWidget *parent
= 0);
protected:
void enterEvent
( QEvent * event
);
void leaveEvent
( QEvent * event
);
private:
int m_currentRow;
};
//HistoryTreeView.cpp
void HistoryTreeView
::mouseMoveEvent(QMouseEvent *event
) {
// Only do something when a model is set.
if (m)
{
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;
}
}
}
//HistoryWindow.h
{
Q_OBJECT
public:
explicit HistoryWindow
(QWidget *parent
= 0);
~HistoryWindow();
void setupModel();
void SetMouseTransitSignals
(QWidget *parent
);
protected:
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
) {
}
Re: indexWidget() unexpectedly returns a NULL pointer.
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.
Re: indexWidget() unexpectedly returns a NULL pointer.
When do you sleep? :) You were active pretty late last night. I'm impressed.
Quote:
- your "ButtonDelegate" class might not have a base class at all
Uh, ButtonDelegate is derived from QStyledItemDelegate, wouldn't that be its base class?
Quote:
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.
Quote:
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.
Re: indexWidget() unexpectedly returns a NULL pointer.
Quote:
Originally Posted by
ajax
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.
Quote:
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?
Quote:
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().
Re: indexWidget() unexpectedly returns a NULL pointer.
Quote:
Quote:
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 touches on some of my concerns.
Re: indexWidget() unexpectedly returns a NULL pointer.
Quote:
Originally Posted by
ajax
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?
Quote:
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?