How to set a string property to QTreeWidgetItem? - c++

I have a list of application specific items uniquely identified by an id. Their names are displayed in a QTreeWidget (one item corresponds to a single QTreeWidgetItem). I would like to somehow attach the corresponding ids to these QTreeWidgetItems so that upon selection changed I can access the id of the corresponding item and do some processing.
QTreeWidgetItem does not inherit from QObject so I cannot use its setProperty function. How could I do this?

Just create some user defined roles for the properties...
typedef enum {
id_1_role = Qt::UserRole,
id_2_role,
id_N_role,
} property_id_role;
Then you can use the normal means of getting/setting the data associated with a QTreeWidgetItem.
QTreeWidgetItem *item = ...
/*
* Set the property value.
*/
item->setData(column, property_id_role::id_2_role, id_2_value);
/*
* Get the property value.
*/
auto id_2_value = item->data(column, property_id_role::id_2_role).value<id_2_type>();

Do you know that QTreeWidgetItem has a setData method?
setData(int column, int role, const QVariant &value)
You can use it with your roles. For example:
int your_id = 123;
ui->treeWidget->currentItem()->setData(0,Qt::UserRole,your_id);
qDebug() << ui->treeWidget->currentItem()->data(0,Qt::UserRole);

Related

Qt - Hash table contains incorrect values

I'm doing my controller. When I click on an item in my ListWidget, the following method is called :
void ContactBookController::setCurrentlySelectedItem(QListWidgetItem *item)
{
setCurrentItem(*item);
m_window.setEditButtonsState(true);
}
And the setCurrentItem() method is as follows : (m_current_item is a private variable):
void ContactBookController::setCurrentItem(const QListWidgetItem &current_item)
{
m_current_item = current_item;
}
Now, when I create a contact, I add an item to the QListWidget, but I also create a Contact object, but I also bind them together in a QHash so I know what QListWidgetItem corresponds to what Contact. In short, here is what I did :
void ContactBookController::createContact()
{
auto new_contact = m_contact_book.createContact();
if(new_contact != nullptr)
{
new_contact->setName(tr("New contact"));
}
auto list_item = m_window.createContact(new_contact->getName());
m_window.clearSelection();
m_contact_map.insert(list_item, new_contact);
}
Now, when clicking on a QListWidgetItem, I activate the edit button and I would like to retrieve the corresponding Contact object. However, when doing this, the Contact object doesn't seem to be correct. Indeed, if I use a method on him (like getName()), my application instantly crashes.
Here is what I did :
void ContactBookController::editContact()
{
auto list_item = m_current_item;
auto contact = m_contact_map.value(&list_item); /* doesn't work */
}
My hash table is declared as such :
QHash<QListWidgetItem*, Contact*> m_contact_map;
Any idea what I did wrong ?
Your hash has the type QHash<QListWidgetItem*, Contact>. So, mapping a item pointer to a contact value.
When you save the mapping for a specific item with m_contact_map.insert(list_item, new_contact), you add a mapping from the item pointer to the contact. But when you try to retrieve the contact with m_contact_map.value(&list_item), you look up the value for the pointer to the local list_item variable, which points to somewhere on the stack, and has nothing to do with the pointer to the item that is shown in the view.
Either you need to save to pointer to the selected value, i.e. make m_current_item a QListWidgetItem *m_current_item, or you simply use QListWidget::currentItem() to retrieve the current item without the need for an additional member variable.
(Side note: You should check for currentItem() != nullptr, since a list widget does not necessarily has an item selected.)

QTreeView: maintaining mapping between QModelIndex and underlying data

I have problems migrating from QTreeWidget to QtreeView. Things that were obvious and trivial with QTreeWidget seem to be impossible with view. Specifically: I have a main window with a treeview in it. TreeView uses the model I've implemented, but not directly – through QSortFilterProxyModel that is set as a tree's model. Now, the user activates an item in the tree and main windows receives a signal itemActivated(QModelIndex item). How can I tell which item of the underlying data was activated? Data is a vector, so with TreeWidget I could just store an item's vector index in the QTreeWidgetItem, but QModelIndex doesn’t even have setData API.
How can I tell which item of the underlying data was activated?
By inverting the proxy model:
// supposing to connect this to your itemActivated signal
void onItemActivated(const QModelIndex &index)
{
QModelIndex originalIndex = proxyModel->mapToSource(index);
originalModel->doSomething(originalIndex);
}
You can define custom roles in your source model, returning the underlying data or an identifier (if there's one) as variant. This has the advantage it works with any number of proxy models in between, as the data will be passed through the models unaltered and now mapping of indexes is required.
Assuming a model listing contacts, with a value struct/class Contact holding the data.
This requires Contact to be registered via Q_DECLARE_METATYPE.
class ContactModel ... {
...
enum Role {
ContactRole=Qt::UserRole,
ContactIdRole
};
QVariant data(...) const {
...
const Contact& contact = ...get from datastructure...
...
switch (role) {
...
case ContactRole:
return QVariant::fromValue( contact );
case ContactIdRole:
return contact.id;
}
}
...
And in the code receiving the index:
void SomeWidget::indexSelected(const QModelIndex& index)
{
const int id = index.data(ContactModel::ContactIdRole).toInt();
// look up Contact, do something with it
//or:
const Contact contact = index.data(ContactModel::ContactRole).value<Contact>();
// do something with the contact
...
}
The index can be from the contact model itself, or any proxy on top of it - the code here doesn't have to care.
The model to stores your data. The data is no longer owned by items/QModelIndex in the view. QModelIndex is only a unique identifier passed between the view and the model (in this case via QSortFilterProxyModel). The model should inherit QAbstractItemModel which has some pure virtual functions that need to be defined (you can copy boilerplate from http://qt-project.org/doc/qt-4.8/itemviews-simpletreemodel.html). You will e.g. have to define QAbstractItemModel::data( const QModelIndex & index, int role = Qt::DisplayRole) which defines which data that corresponds to a particular QModelIndex.
The QSortFilterProxyModel sits between the view and the model, but does not change the principles for the model. See the other answer on this question for how to deal with the QModelIndex conversion.
To conclude: QAbstractItemModel::data( const QModelIndex & index) will give you the data for a particular QModelIndex once you have defined it.

Qt Drag and Drop Treeview : what am I missing

I have a standard treeview which is viewing a subclass of QStandardItemModel.
The items in the model are also subclassed form QStandardItem. The items have an additional object pointer which I use to store a pointer to an instance of my data class, a "stage" (itself a QObject). All items have either a pointer to a stage or subclass of it, or a NULL pointer in the _object.
class MyStandardItem : public QStandardItem
{
public:
MyStandardItem();
MyStandardItem(const QString &text);
MyStandardItem(const QIcon &icon, const QString &text);
~MyStandardItem();
void object(QObject *object) {_object = object;}
QObject *object(){return _object;}
private:
QObject *_object;
};
I want to move items around in the treeview, subject to some restrictions. I have given the treeview the correct policies:
view->setAcceptDrops(true);
view->setDragEnabled(true);
view->setDropIndicatorShown(true);
view->setDragDropMode(QAbstractItemView::InternalMove);
And in my model I provide the following:
Qt::DropActions MyStandardItemModel::supportedDropActions() const
{
return Qt::MoveAction;
}
Qt::ItemFlags MyStandardItemModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
MyStandardItem *item = dynamic_cast<MyStandardItem*>(itemFromIndex(index));
if(!item || !item->object())
{
return defaultFlags;
}
Stage *stage = dynamic_cast<Stage*>(item->object());
switch (stage->type())
{
case Stage::STAGEA:
return Qt::ItemIsDropEnabled | defaultFlags;
break;
case Stage::STAGEB:
case Stage::STAGEC:
return Qt::ItemIsDragEnabled | defaultFlags;
break;
}
return defaultFlags;
}
The dragging behaviour looks ok. But when I then click on a dragged item in the treeview, the object pointer of the selected item is junk:
void Project::model_clicked(const QModelIndex& index)
{
MyStandardItem *item = static_cast<MyStandardItem*>(_tree_model->itemFromIndex(index));
if(!item || !item->isValid())
return;
QObject *object = item->object();
if(!object)
return;
// object is junk
Stage *stage = static_cast<Stage*>(object);
// and of course stage is junk
}
do I need to implement dropMimeData or something similarly special for the drop for my subclassed MyStandardItem? Since I'm only moving I expected the object pointers to be intact.
If I do need to implement dropMimeData, what is the mimetype of the dragged data? I know I can see it using the model selection, but logically I should be able to get the data from the mimedata.
Your help most appreciated!
Well I found the answer to my own question.
the data is "moved" by Qt inserting into the required position in the model and then deleting it.
This means it is necessary to implement a clone() member to be used by dropMimeData (which must be reimplemented too as far as I can see)
This means widgets must be stored as pointers within objects to allow easy movement within the tree (otherwise the data has to be manually copied between widgets as there's no default copy for QObjects (by design)

Model view difficulties

I've treeview in which I'd like to have displayed files selected by user via file_dialog.getOpenFileNames(); file_dialog is QFileDialog.
I did create model class:
class File_Display_Model : public QAbstractItemModel
{
Q_OBJECT
private:
QStringList* selected_files_;
public:
explicit File_Display_Model(QObject *parent = nullptr,QStringList* selected_files = nullptr);
int File_Display_Model::columnCount( const QModelIndex & parent ) const
{
selected_files_->count();
}
QVariant File_Display_Model::data(const QModelIndex & index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
else
{
if (role == Qt::DisplayRole) {
if (index.row() == index.column())
{
return 0;
}
else
{
return selected_files_->at(role);
}
}
return QVariant();
}
}
QModelIndex File_Display_Model::index(int row, int column, const QModelIndex & parent ) const
{
/*DUMMY - HERE I JUST DON'T KNOW WHAT TO RETURN*/
return QModelIndex();
}
QModelIndex File_Display_Model::parent(const QModelIndex & index) const
{
return QModelIndex();
}
int File_Display_Model::rowCount( const QModelIndex & parent ) const
{
selected_files_->count();
}
};
And I also provided this class as a model to tree view. There is a problem with a index method in this class - I don't know what to return.
Could someone please help me and guide me how to make it work so files selected by an user are displayed in a treeview?
First of all there is no reason for using a QStringList*. Qt uses implicit sharing so it is efficient to pass it as an argument (dont forget that QStringList is nothing more than a QList<QString>).
Second you should review the excellent Qt Model/View Programming documentation.
Row and Column Count
You are trying to create a tree model so you should read carefully the tree model example. Notice that the rowCount and columnCount functions have as argument a model index.
The rowCount() function simply returns the number of child items for
the item that corresponds to a given model index, or the number of
top-level items if an invalid index is specified
and for the column count
Since each item manages its own column data, the columnCount()
function has to call the item's own columnCount() function to
determine how many columns are present for a given model index. As
with the rowCount() function, if an invalid model index is specified,
the number of columns returned is determined from the root item
So you have to think how your stringlist will be represented as a tree model. How columns will you have and what will be stored there for every level? How will be the rows hierarchy? Why are you using as column count the number of strings?
Model Index
When you reimplement the index() function you just have to check if the provided row and column are valid and if yes you should call the createIndex function. Again it all depends on what data your model contains and how you have structured them. Since you want to implement a tree model you have to take under consideration the parent item as well.
When reimplementing this function in a subclass, call createIndex() to
generate model indexes that other components can use to refer to items
in your model.

CMFCPropertyGridCtrl add editable string property

I derived a class from CMFCPropertyGridCtrl to add a simple interface for my needs. I want to add a new string value so I can edit it:
void CPropertyGrid::AddString(const char* tag, const char* defaultValue)
{
COleVariant valueType(defaultValue);
CMFCPropertyGridProperty *stringProperty = new CMFCPropertyGridProperty(tag, valueType);
stringProperty->AllowEdit(TRUE);
AddProperty(stringProperty);
}
This adds a new string in the property grid, but I can't edit it. Is there another way to make this editable?
Thanks!
Notify value must be set to true to be able to edit values