PDA

View Full Version : Custom context menu in QTreeView



ttvo
1st April 2009, 16:48
Hi all,

I have a QTreeView and QStandardItemModel and want to add a context menu so that new child node(s) can be added to the current node.

At construction, I'd like to have three empty tree nodes
<checkbox><icon>Rectangles
<checkbox><icon>Circles
<checkbox><icon>Squares

and to enable a right click on each of those nodes to add/delete/modify. I started with an ActionsContextMenu but then I need the node's index, so I successfully created a custom context menu.


...
// set context menu policy
projectTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
...

// initialize the model with three empty nodes
QStandardItemModel *model = new QStandardItemModel();
model->setHorizontalHeaderItem(0, new QStandardItem());
m_dm[0] = new QStandardItem(QIcon(":/images/myIcon.png"), "Rectangles");
m_dm[1] = new QStandardItem(QIcon(":/images/myIcon.png"), "Circles");
m_dm[2] = new QStandardItem(QIcon(":/images/myIcon.png"), "Squares");
for (int i=0; i<3; ++i) {
model->appendRow(m_dm[i]);
}
m_ui.projectTreeView->setModel(model);
...

// create an add action and connect it to a signal
m_addAction = new QAction(tr("Add new"), m_ui.projectTreeView);
connect(m_addAction, SIGNAL(triggered()), this, SLOT(addObject()));

// connect custom context menu
connect(m_ui.projectTreeView, SIGNAL(customContextMenuRequested( const QPoint& )), this, SLOT(showContextMenu(const QPoint &)));

...
void showContextMenu(const QPoint& pnt)
{
QList<QAction *> actions;
if (m_ui.projectTreeView->indexAt(pnt).isValid()) {
actions.append(m_addAction);
}
if (actions.count() > 0)
QMenu::exec(actions, m_ui.projectTreeView->mapToGlobal(pnt));
}

...
void MainWindow::addObject()
{
// HOW am I going to get access to the node's index to insert a new child row???
}



I got the code to compile and the addObject() method is invoked properly. My question is how am I going to get access to the corresponding node index to insert a child node appropriately?

Thanks in advance.

spirit
1st April 2009, 16:54
try to use QAbstractItemView::currentIndex.

ttvo
1st April 2009, 18:44
Thanks. Follow-up questions,
1) I want to setCheckable on the tree top level nodes to true when it has children + when check/uncheck a parent's checkbox, all children will be checked/unchecked accordingly. Do I have to subclass QStandardItem?

2) Children node always has a checkbox or radiobox, and toggling child node checkbox/radiobox won't change its parent checked state. Do I have to subclass QStandardItem?

3) when create a child node, I want to use a different default icon and/or naming convention by the parent's type (i.e. Squares, Circles, ...). I don't want to just rely on the name of the parent's node if I don't have to. If so, can I use QStandardItem then somehow utilizing Qt role/type OR do I need to subclass QStandardItem?

In general, I want to avoid subclassing QStandardItem, QStandardItemModel, and QTreeView unless I have to.

spirit
1st April 2009, 18:47
Thanks. Follow-up questions,
1) I want to setCheckable on the tree top level nodes to true when it has children + when check/uncheck a parent's checkbox, all children will be checked/unchecked accordingly. Do I have to subclass QStandardItem?

try this code


...
QTreeWidget *tree = new QTreeWidget;
tree->setColumnCount(1);

QTreeWidgetItem *root = new QTreeWidgetItem(QStringList(QString("root")));
root->setCheckState(0, Qt::Unchecked);
tree->insertTopLevelItem(0, root);

for (int i = 0; i < 10; ++i) {
QTreeWidgetItem *item = new QTreeWidgetItem(root, QStringList(QString("item: %1").arg(i)));
item->setCheckState(0, Qt::Unchecked);
for (int j = 0; j < 10; ++j) {
QTreeWidgetItem *itm = new QTreeWidgetItem(item, QStringList(QString("item: %1").arg(i)));
itm->setCheckState(0, Qt::Unchecked);
}
}

connect(tree, SIGNAL(itemChanged(QTreeWidgetItem *, int)), SLOT(itemChanged(QTreeWidgetItem *, int)));
....
void MyWidget::itemChanged(QTreeWidgetItem *item, int)
{
changeItemState(item);
}

void MyWidget::changeItemState(QTreeWidgetItem *item)
{
if (!item)
return;

for (int i = 0; i < item->childCount(); ++i) {
QTreeWidgetItem *child = item->child(i);
child->setCheckState(0, item->checkState(0));
changeItemState(child);
}
}

I don't test it, but hope you got idea.
PS. I don't see any reason to subclass QStandardItem in second and third points.

ttvo
1st April 2009, 22:48
I added



connect(model, SIGNAL(itemChanged(QStandardItem * item)), SLOT(myItemChanged(QStandardItem *item)));

// where QStandardItemModel *model = new QStandardItemModel();


from my MainWindow (inherits from QMainWindow) and


void MainWindow::myItemChanged(QStandardItem *item)
{
changeItemState(item);
}

void MainWindow::changeItemState(QStandardItem *item)
{
if (!item)
return;
for (int i=0; i<item->rowCount(); ++i) {
QStandardItem *child = item->child(i);
child->setCheckable(item->checkState());
changeItemState(child);
}
}

similar to your example but the two methods weren't invoked at all when I toggle the checkbox.

ttvo
3rd April 2009, 23:29
I added



connect(model, SIGNAL(itemChanged(QStandardItem * item)), SLOT(myItemChanged(QStandardItem *item)));

// where QStandardItemModel *model = new QStandardItemModel();


The correct code is:


connect(model, SIGNAL(itemChanged(QStandardItem * )), SLOT(myItemChanged(QStandardItem *)));