Hierarchial QTreeView mapping with QStackedWidget - c++

I am creating a UI with QTreeView representing list of items(which may or may not have children) and displaying corresponding widget on right side using QStackedWidget. Previously the behaviour of my data or the list of items mentioned above was flat, hence I was using QListView but when the data had to have children I replaced the QListView with QTreeView so I can add children as well. I have achieved the above in the following manner:
for(std::vector<AWidget* >::iterator it=widgets.begin(); it!=widgets.end(); ++it)
{
AWidget* w = *it;
QStandardItem *parent = new QStandardItem(w->Name());
model->setItem(i, 0, parent);
QWidget *PageWidget = w->getWidget();
QScrollArea * pScrollArea = new QScrollArea();
pScrollArea->setWidget(PageWidget);
m_ui.AStackedWidget->addWidget(pScrollArea);
if(!w->hasChildren())
{
std::vector<AWidget*> children = w->getChildren();
for(std::vector<AWidget*>::iterator iChild=children.begin(); iChild!=children.end(); ++iChild)
{
AWidget* childWidget = *iChild;
QStandardItem *child = new QStandardItem(childWidget->Name());
parent->appendRow(child);
QWidget *childPageWidget = childWidget->getWidget();
QScrollArea * pChildScrollArea = new QScrollArea();
pChildScrollArea->setWidget(childPageWidget);
pChildScrollArea->setWidgetResizable(true);
m_ui.AStackedWidget->addWidget(pChildScrollArea);
}
}
i++;
}
m_ui.ATreeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_ui.ATreeView->setTabKeyNavigation(true);
m_ui.ATreeView->setModel(model);
QModelIndex index = m_ui.ATreeView->model()->index(0,0);
QItemSelectionModel * selModel = m_ui.ATreeView->selectionModel();
if(selModel)
{
connect(selModel, SIGNAL( currentChanged(const QModelIndex &, const QModelIndex &) ), this , SLOT(slotOptionSelectedForChangeUI(const QModelIndex & )) );
selModel->select(index,QItemSelectionModel::Select);
}
slotOptionSelectedForChangeUI(index);
Definition of slotOptionSelectedForChangeUI() is as follows:
void slotOptionSelectedForChangeUI(const QModelIndex & indx)
{
int rowNum = indx.row();
if(m_ui.AStackedWidget)
m_ui.AStackedWidget->setCurrentIndex(rowNum);
}
For eg: following is the view:
-A -> widget1
-B -> widget2
-C -> widget3
-D -> widget4
-E -> widget5
-E1 -> widget6
-E2 -> widget7
-E3 -> widget8
-E4 -> widget9
-E5 -> widget10
-E6 -> widget11
- E7 -> widget12
A,B,C,D,E show correct corresponding widgets on the right side in the QStackedWidget. However E1, E2, E3, E4,E5,E6,E7 show widgets widget1,2,3,4,5,6,7 respectively. This means index of E1 starts again from 0 and stacked widget shows widget0 for index 0 and so on. How should E1-E7 be mapped with widget6-12 so that proper widgets are displayed on the right side stacked widget?

The proper way of doing this is by not using a QStackedWidget, but only displaying one. To do this, all you have to do is:
Add the widget to the item using QStandardItem::setData
In the ui, instead of a QStackedWidget, just use a simple QWidget (with a horizontal/vertical layout without margins)
Connect the selection model index changed signal to a slot that removes all children from the simple widget, and adds the one stored with the item. You can retrieve it by using the QStandardItem::data function (or the models data function)
Here a minimal example of those steps:
#define MyRole Qt::UserRole + 42
//...
//adding the item
AWidget* w = *it;
QStandardItem *parent = new QStandardItem(w->Name());
parent->setData(QVariant::fromValue(w), MyRole);
model->setItem(i, 0, parent);
//...
//the index changed slot
void slotOptionSelectedForChangeUI(const QModelIndex & indx)
{
AWidget *w = model->data(index, MyRole).value<AWidget*>();//will internally call QStandardItem::data
//remove old children
QList<QWidget*> children = m_ui.containerWidget->findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly);
foreach(QWidget *child, children)
child->deleteLater();
//add the new one
QWidget *PageWidget = w->getWidget();
ScrollArea * pScrollArea = new QScrollArea(m_ui.containerWidget);
pScrollArea->setWidget(PageWidget);
m_ui.containerWidget->layout()->addWidget(pScrollArea);
}

Related

Can't get item from QTreeView by QModelIndex

I create a QTreeView in a window, and I want to get the text of selected items when double clicking them. I try to use the signal "doubleClicked(const QModelIndex &)" to get the index of selected item.
But when I received the signal and wanted to do something with the index passed in, I can't get the item properly.
I found the index passed in is something like that:
root
|...item1 (0, 0)
|...|...subItem1 (0, 0)
|...|...subitem2 (1, 0)
|...item2 (1, 0)
There are two (0, 0) and (1, 0) items???
EDIT: I got this result by
qDebug(QString::number(index.row()).toLatin1().data()); // row
qDebug(QString::number(index.column()).toLatin1().data()); // column
Here is my code, create QTreeView and QStandardItemModel:
mTree = new QTreeView(this); // mTree is a class member
treeModel = new QStandardItemModel(); // also a class member
proxymodel = new MySortFilterProxyModel(); // for sorting
proxymodel->setSourceModel(treeModel);
mTree->setModel(proxymodel);
and a custom slot to receive the signal:
private slots:
void getSelectedIP(const QModelIndex &);
connect signal and slot:
connect(mTree, SIGNAL(doubleClicked(const QModelIndex &)),
this, SLOT(getSelectedIP(const QModelIndex &)));
implementation of the slot, and the program crashed in this code:
void HostTreeFrame::getSelectedIP(const QModelIndex &index)
{
QStandardItem *selectedItem = treeModel->itemFromIndex(index);
qDebug(QString::number(index.row()).toLatin1().data());
qDebug(QString::number(index.column()).toLatin1().data());
qDebug("1");
QString selectedIPString = selectedItem->text(); // program crashed here, selectedItem == nullptr
qDebug("2");
}
EDIT:
The selectedItem is nullptr, that's why the program crashed, but why it is nullptr?
Consider the code...
void HostTreeFrame::getSelectedIP(const QModelIndex &index)
{
QStandardItem *selectedItem = treeModel->itemFromIndex(index);
The problem is that index is associated with the model being used by the view but that's the proxy model -- not the QStandardItemModel.
You need to map the model index index to the correct model. So something like...
void HostTreeFrame::getSelectedIP(const QModelIndex &index)
{
auto standard_item_model_index = proxymodel->mapToSource(index);
QStandardItem *selectedItem = treeModel->itemFromIndex(standard_item_model_index);
/*
* Check selectedItem before dereferencing.
*/
if (selectedItem) {
...
The code above assumes proxymodel is a member of (or is directly visible to) HostTreeFrame.

How to select item in QComboBox with QTreeView

I was trying to select item "leaf2" from QComboBox with QTreeView in the below code.
I just want to select items with no child from text by code. (if there is a child, it won't be selectable)
How can I select item or index with no child item?
Can anyone help me with this problem?
Thank you.
void Main::initView()
{
QStandardItemModel *model = new QStandardItemModel;
QStandardItem *root_item = model->invisibleRootItem();
// Build Model Items
QStandardItem *node_item = NULL;
node_item = new QStandardItem("Node");
node_item->setFlags(Qt::ItemIsEnabled);
root_item->appendRow(node_item);
QStandardItem *leaf_item = new QStandardItem("leaf1");
leaf_item ->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
node_item->appendRow(leaf_item );
leaf_item = new QStandardItem("leaf2");
leaf_item ->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
node_item->appendRow(leaf_item );
// Set Model to TreeViewComboBox
ui.cb_treevew->setModel(model);
ui.cb_treeview->setCurrentIndex(0); // "Node" is selected.
ui.cb_treeview->setCurrentIndex(1); // Nothing is selected.
ui.cb_treeview->setCurrentIndex(2); // Nothing is selected.
ui.cb_treeview->setCurrentIndex(3); // Nothing is selected.
}
Here is my code for CTreeViewComboBox.
CTreeViewComboBox::CTreeViewComboBox(QWidget *parent) : QComboBox(parent)
{
QTreeView* treeView = new QTreeView(this);
treeView->setEditTriggers(QTreeView::NoEditTriggers);
treeView->setSelectionBehavior(QTreeView::SelectRows);
treeView->setSelectionMode(QTreeView::SingleSelection);
treeView->setItemsExpandable(true);
treeView->header()->setVisible(false);
treeView->setWordWrap(true);
setView(treeView);
}
PS: I tried to select items as following, but not working. :(
ui.cb_treeview->treeView()->setCurrentIndex(getModelIndexFromText("leaf2"));
If the nodes with children will never have the same text with the nodes without children then the following method is appropriate.
QModelIndexList modelIndexes = model->match(
model->index(0, 0),
Qt::DisplayRole,
"leaf2",
-1,
Qt::MatchRecursive);
QModelIndex index = modelIndexes.first();
ui.cb_treeview.setRootModelIndex(index.parent());
ui.cb_treeview.setCurrentIndex(index.row());
On the other hand, if the nodes with children can have the same text as the nodes without children, you should use the following method.
QModelIndexList modelIndexes = model->match(
model->index(0, 0),
Qt::DisplayRole,
"leaf2",
-1,
Qt::MatchRecursive);
QModelIndexList::iterator tstIt = std::find_if(modelIndexes.begin(),
modelIndexes.end(),
[] (const QModelIndex & index) {
return !index.model()->hasChildren(index);
});
ui.cb_treeview.setRootModelIndex(tstIt->parent());
ui.cb_treeview.setCurrentIndex(tstIt->row());
In both cases I am assuming that nodes without children always have different texts. If nodes without children match the name, choose one of them.

Qt QTreeWidget alternative to IndexFromItem?

I have derived the class QTreeWidget and creating my own QtPropertyTree. In order to populate the tree with widgets (check boxes, buttons, etc) I am using the following code:
// in QtPropertyTree.cpp
QTreeWidgetItem topItem1 = new QTreeWidgetItem(this);
QTreeWidgetItem subItem = new QTreeWidgetItem(this);
int column1 = 0
int Column2 = 1;
QPushButton myButton = new QPushButton();
this->setIndexWidget(this->indexFromItem(this->subItem,column1), myButton);
QCheckBox myBox = new QCheckBox();
this->setIndexWidget(this->indexFromItem(this->subItem,column2), myBox);
This works fine, but the problem is that i want to avoid using the "indexFromItem" function since it is protected, and there are other classes that are populating the tree and need access to that funcion. Do you know any alternative to using that function?
You can try to use your QTreeWidget's model (QAbstractItemModel) to get the right index by the column and row numbers:
// Row value is 1 because I want to take the index of
// the second top level item in the tree.
const int row = 1;
[..]
QPushButton myButton = new QPushButton();
QModelIndex idx1 = this->model()->index(row, column1);
this->setIndexWidget(idx1, myButton);
QCheckBox myBox = new QCheckBox();
QModelIndex idx2 = this->model()->index(row, column2);
this->setIndexWidget(this->indexFromItem(idx2, myBox);
UPDATE
For sub items, the same approach can be used.
QModelIndex parentIdx = this->model()->index(row, column1);
// Get the index of the first child item of the second top level item.
QModelIndex childIdx = this->model()->index(0, column1, parentIdx);
The obvious solution is to de-protect indexFromItem like this:
class QtPropertyTree {
...
public:
QModelIndex publicIndexFromItem(QTreeWidgetItem * item, int column = 0) const
return indexFromItem (item, column) ;
}
} ;

Get back QWidget after using in QItemEditorCreatorBase

I have a numeric editor that extends the QSpinBox
NumericEditor::NumericEditor(QWidget *widget): QSpinBox(widget)
I use this editor to edit the type QVariant::Int in the QTableWidget
QItemEditorCreatorBase *numericEditor = new QStandardItemEditorCreator<NumericEditor>();
factory->registerEditor(QVariant::Int, numericEditor);
Data is entered in the table as normal. Ignore the usage of the word "color". Its based off the color editor example.
QTableWidgetItem *nameItem2 = new QTableWidgetItem(QString("label2"));
QTableWidgetItem *colorItem2 = new QTableWidgetItem();
colorItem2->setData(Qt::DisplayRole, QVariant(int(4)));
table->setItem(1, 0, nameItem2);
table->setItem(1, 1, colorItem2);
The spin box appears and works fine in the QTableWidget.
My desire is to get access to the instance of QSpinBox that the table uses when it edits QVariant::Int cells so I can set the min and max values.
How can I do this?
You can install a delegate on the column with QTableWidget::setItemDelegateForColumn, and when an editor is opened, its createEditor method will be called.
class MyDelegate : public QStyledItemDelegate {
public:
QWidget *createEditor ( QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index ) const {
// You could create the widget from scratch or call
// the base function which will use the QItemEditor you already wrote
QWidget * editor = QStyledItemDelegate::createEditor(parent, option, index);
// do whatever you need to do with the widget
editor->setProperty("minimum", 100);
editor->setProperty("maximum", 100);
return editor;
}
};

Can’t make child in Qtreeview using QStandardItemModel

After reading some examples I am still missing here something.
I have Qtreeview for view and QStandardItemModel for the data interface, also using QSortFilterProxyModel subclass but I don't know if its relevant.
This is my logic:
First I create the model with the QWidget as the parent:
QStandardItemModel m_model = new QStandardItemModel(0,4,parent);
then setSourceModel(m_model) for the widget
Set the treeview with QSortFilterProxyModel. something like this:
GroupProxyModel = new GroupSortFilterProxyModel;
GroupProxyModel->setDynamicSortFilter(true);
setSourceModel(createSubjectModel(parent));
ui.treeView_mainwindow->setModel(GroupProxyModel);
ui.treeView_mainwindow->setSortingEnabled(true);
Then later I fill the first row like this:
QList<QStandardItem *> items;
items.insert(0,new QStandardItem("Test 0"));
items.at(0)->setEditable(false);
m_model->insertRow(0,items);
Until now every thing working fine and I see the row with the data. But when I like to
add child to the row like this:
QModelIndex parentQModelIndex = m_model->item(0,0)->index();
m_model->insertRows(0,1,parentQModelIndex);
m_model->insertColumns(0,1,parentQModelIndex);
QModelIndex indexB = m_model->index(0, 0, parentQModelIndex);
m_model->setData(indexB,"Child test",Qt::DisplayRole);
But I don't see the child, why?
That's not how QStandardItemModel works - to add a child, call appendRow(s)/insertRow(s) on the parent QStandardItem:
QStandardItem* child = new QStandardItem( tr("Child test") );
...
QStandardItem* parentItem = m_model->item( 0, 0 );
parentItem->appendRow( child );