I don't have enough experience with Qt yet to make a good design choice. Any help by experienced Qt programmers would be very appreciated.
I'm trying to find out which model to subclass, which view to use, what delegate subclassing / extending I should do...
My problem is similar to: I have these zones I would like to display, 1 per row:
class Zone{
//inputs
string country; //edited with a QComboBox
string city; //edited with a QComboBox
int ageMin;
//stored result
int nbInhabitantsOlderThanMin;
}
Here's what I'd like to do, and the design choices each requirements makes me think of:
- I would like to display a list of them (--> QListView )
- But to display 1 item I need several columns (--> QTableView )
I would like a double click on a row to trigger editing in a custom widget, since nbInhabitantsOlderThanMin can not be edited, and choosing a country restricts the list of cities that can be chosen in the QComboBox (and vice versa in my real example) (--> I should probably use a QDataWidgetMapper (or subclass?) somewhere...)
So whereas the edition of a row should happen in a widget, the display is simple / not custom, and subclassing a delegate (QStyledItemDelegate for instance) (I'm not so sure about this one) doesn't seem to be the right way to have 1 custom widget with many child input widget to edit the 3 fields at the same time.
I think the data to model would favor a model subclassing QAbstractListModel, but the display with many columns compatible with default delegate viewing favors a QAbstractTableModel..
So I don't really know which design to go for. Any experienced help connecting the dots is very welcome :)
QDataWidgetMapper is a slightly different thing. It is a way to display one item from a Model (ex. QStandardItemModel), using custom controls. You can read more about it here, with accompanying snapshots and an example of how to implement one.
While it is certainly cool, I don't think it is what you want here. Mostly because you specified that you want to view your items in a list format. However, you could display all your items in a simple list, double-click which would open a dialog using the QDataWidgetMapper. In which case all you would need to do with a QListView/QListWidget is implement the double-click event.
Still, I personally don't like the added burden of the extra window on a user. I prefer to use popups sparingly. But if you like that approach, then go ahead. This is another example of the QDataWidgetMapper which is pretty nice.
My preferred approach is still to use the QTableView, and provide delegates for the columns that need specialized editing. Here is a great walk-through of all things Model/View. So if you decide to use the QListView or QTableView it will give you a great start. It also talks about how you can create delegates to edit fields however you want.
So, how do you create a custom delegate? Basically, you just inherit from QItemDelegate. There are some examples in the link above, but I'll highlight a few salient points.
QWidget *ComboBoxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &/* option */,
const QModelIndex &index) const
{
QComboBox *editor = new QComboBox (parent);
// Add items to the combobox here.
// You can use the QModelIndex passed above to access the model
// Add find out what country was selected, and therefore what cities
// need to be listed in the combobox
return editor;
}
void ComboBoxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int value = index.model()->data(index, Qt::EditRole).toInt();
QComboBox *comboBox= static_cast<QComboBox *>(editor);
int _SelectedItem = // Figure out which is the currently selected index;
comboBox->setCurrentIndex(_SelectedItem);
}
void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QComboBox *comboBox= static_cast<QComboBox *>(editor);
comboBox->interpretText();
int value = comboBox->currentIndex();
// Translate the current index to whatever you actually want to
// set in your model.
model->setData(index, value, Qt::EditRole);
}
Fill in the gaps that I left in my example and you have your Delegate.
Now, how to use this in your QTableView:
You can set a delegate for a particular column of your table as follows:
setItemDelegateForColumn(_ColumnIndex, new ComboBoxDelegate(_YourTableModel));
And, if you want to prevent certain columns from being editable:
_YourTableModel->setColumnEditable(_ColumnIndex, false);
Once you have your model set up, everything else should take care of itself.
Hope that helps.
First, you should subclass QAbstractItemDelegate
(or QItemDeleage
, that could be more convenient), where reimplement createEditor
, setEditorData
and setModelData
.
Than, you should set your own itemDelegate (see QAbstractItemView::setItemDelegate
).
It's commonly no difference, what widget to use to present data: It could be either QTreeWidet
, or QTreeView
, or QTableWidget
, or QTableView
. Note, that "widgets" are easier to use, than "views", but they are not so powerfull
I just finished something very similar to this in that I needed multiple fields for each object which I wanted in a row. The way I did it, which worked out very well, was to subclass QAbstractListmodel and use a custom ListItem. I kind of faked the columns by having a custom delegate and using some javascript to figure out the size of the largest thing in each field, and then set the column size to that width. I think this is the easiest way of doing it.
For the comment below, zone would inherit from:
class ListItem: public QObject {
Q_OBJECT
public:
ListItem(QObject* parent = 0) : QObject(parent) {}
virtual ~ListItem() {}
virtual QString id() const = 0;
virtual QVariant data(int role) const = 0;
virtual QHash<int, QByteArray> roleNames() const = 0;
signals:
void dataChanged();
};