Updating the view in model/view - maybe I am breaking the model/view architecture.
-
I have a model/view built and displayed in a QTableView. This all works, in that the table view does display my data - which is a linked list of an application data structure. Now, I am using a delegate to create a QComboBox to edit two of the columns; I use the setItemDelegateForColumn method to set up the columns that use this delegate/combobox. Again this all works as expected - almost.
Now the crunch: In the view, I am displaying a little more than just the raw contents of the data structure; two of the columns show 'resultant' data based on the selection made in the columns which use the delegate/combobox combination.
To explain, imagine that the data structure contains four data; A B C and D. Columns C and D are delegated to edit using a QComboBox. However, in the QTableView I am displaying A, B, C, D, E and F where E and F depend on the selections made in C and D. What I am finding is that the contents of columns E and F are not changing ( indeed, the model setData method is not being called) until I make a selection on the QTableView outside of the row that I am editing. So I can keep editing columns C or D and the changes are not reflected in columns E and F; the values E and F are set using the models 'data' method.
I guess what I am asking is: what do I need to do, to link the currentIndexChanged signal on the delegated combo box to the model so that the data is updated as soon as the selection in the combobox is made?
Thanks in Advance.
John T.
-
[quote author="Gerolf" date="1331212682"]Hi,
where do you calculate the columns E and F?
[/quote]Thanks for the very quick response - I calculate E and F inside the model - in the model::data method.
[quote author="Gerolf" date="1331212682"]
If they are calculated inside the model, just emit a dataChanged for the cells. That will do the trick.
[/quote]The problem is that, as the selection in the QComboBox changes, the model is not being called (the model::setData) until I click outside the current cell that I am editing with the combobox. So I think I need a method of connecting the currentIndexChanged signal from the combobox to the dataChanged signal. And there is the problem: they are both signals.
John T.
-
Hi,
principally, you can connect two signals.
In your case there is a mapper function neccessary because of the additional parameters in the dataChanged signal. Simplest: add a this mapping function to your model and make a connection with currentIndexChanged.
-
[quote author="DeVeL-2010" date="1331285554"]Hi,
principally, you can connect two signals.
In your case there is a mapper function neccessary because of the additional parameters in the dataChanged signal. Simplest: add a this mapping function to your model and make a connection with currentIndexChanged.[/quote]
To be honest I don't know what you mean by 'a this mapping function'. But this is what I have done and it appears to work (there are some aspects of this I do not like):
I subclassed the QComboxBox:
@class QTableViewComboBox : public QComboBox
{
Q_OBJECTpublic: QTableViewComboBox( MyModel *pModel, const QModelIndex &index, QWidget * parent = 0 ): QComboBox( parent ),m_pModel( pModel ), m_index( index ){} public slots: void notifyModelIndexChanged( const QString &string ); private: MyModel *m_pModel; const QModelIndex m_index;
};@
In the delegate, I then created the above sub classed combobox rather than a QComboBox (I don't like the cast in this method as it is taking a const pointer and assigning it to a non const pointer) and connect the currentIndexChanged signal to the notifyModelIndexChanged slot:
@QWidget ComboBoxDelegate::createEditor(QWidget parent,
const QStyleOptionViewItem &/ option/ ,
const QModelIndex &index) const
{
MyModel pModel =(MyModel ) index.model(); // I don't like this cast but it works
QTableViewComboBox editor = new QTableViewComboBox(pModel , index , parent);
/....../
connect( editor , SIGNAL( currentIndexChanged(QString)),editor , SLOT(notifyModelIndexChanged(QString)));
/......*/
return editor;
};@The notifyModelIndexChanged looks like this:
@void QTableViewComboBox::notifyModelIndexChanged( const QString &string )
{
if ( m_pModel )
m_pModel->comboBoxChanged( string , m_index );
}@and the code in the MyModel is:
@void MyModel::comboBoxChanged( const QString &string , const QModelIndex &index )
{
QModelIndex indexE = createIndex( index.row() , 4 );
QModelIndex indexF = createIndex( index.row() , 5 );setData( index , string ); data( indexE , Qt::EditRole ); data( indexF , Qt::EditRole ); emit dataChanged( index, right );
}@
I have to explicitly call the setData and data methods otherwise nothing happens.
The only aspect I do not really like is the cast to a non const, from the mode.index() method. I am using an older compiler that lets me do this, no doubt other compilers will complain.
I value anyones comments on this solution.
John T.
-
[quote author="Gerolf" date="1331301071"]Hi,
from my POV, this is not really a good solution. Do you want that cha nging something in the combo biox directly triggers a model update? What happens if the user clicks ESC afterwards (reject editing)? E anf F stay changed.[/quote]
With the above code, E and F only change if a selection (the mouse button is clicked on text in the combobox) is made. I have just tried it, if I bring up one of the TableViewComboBoxes and move the mouse over the available list without clicking the mouse button and then press escape, E and F remain unchanged.
John T.
-
[quote author="theCyclist" date="1331299379"]
To be honest I don't know what you mean by 'a this mapping function'.
[/quote]
Sorry, just misspelling. I just recommended to have a function which maps your currentIndex into a QModelIndex.If you use QAbstractItemModel instead of MyModel for m_pModel in the class QTableViewComboBox, you get rid of the casting. Of course, in this case you must use signal/slot mechanism. In this case it is better to direct connect currentIndexChanged with the slot(!) comboBoxChanged().
On the other hand, it should be possible to overwrite setModelData() of ComboBoxDelegate and call in this function setData(). Advantage: it works also with QSortFilterProxyModel.
Calling data() functions in comboBoxChanged() shouldn't be necessary, because the view will do this after receiving the dataChanged signal.
-
[quote author="DeVeL-2010" date="1331308421"]
I just recommended to have a function which maps your currentIndex into a QModelIndex.If you use QAbstractItemModel instead of MyModel for m_pModel in the class QTableViewComboBox, you get rid of the casting. Of course, in this case you must use signal/slot mechanism. In this case it is better to direct connect currentIndexChanged with the slot(!) comboBoxChanged().
[/quote]
Yep! That works - and no nasty casts from const * to * and no connecting of signals and slots on the same object. Thanks for the advice. So the createEditor method in my subclassed combo box now looks like this:@
QWidget ComboBoxDelegate::createEditor(QWidget parent,
const QStyleOptionViewItem &/ option/ ,
const QModelIndex &index) const
{
const QAbstractItemModel *pModel = index.model();
QTableViewComboBox *editor = new QTableViewComboBox(pModel , index , parent);/*......*/ connect( editor , SIGNAL( currentIndexChanged(QString)),pModel, SLOT(comboBoxChanged(QString)));
/...../
return editor;
}
@I was not sure I could 'connect()' with the comboBoxChanged using only a pointer to the base class - but obviously I can; it is taking a while for me to get used to this signal/slot mechanism.
[quote author="DeVeL-2010" date="1331308421"]On the other hand, it should be possible to overwrite setModelData() of ComboBoxDelegate and call in this function setData(). Advantage: it works also with QSortFilterProxyModel.
Calling data() functions in comboBoxChanged() shouldn't be necessary, because the view will do this after receiving the dataChanged signal.[/quote]
Again you are correct, the data() methods in the comboxBoxChanged slot were not needed after all.
Many thanks - I am more than happy with the code now.
John T.