I am using a QTreeWidget to display a tree of parent nodes with their leave
nodes. Each parent can have various leaf nodes but leaf nodes should have no
childs. The user should be able to move leaves between parents by dragging them
to the new position. To avoid leaves from being dropped on other leaves, I have
only set ItemIsDragEnabled on leaves while having ItemIsDropEnabled on
parent nodes. This works fine if the QTreeWidget is set to "SingleSelection".
However, if the SelectionMode is set to ExtendedSelection you are able to
select a leave and a parent node together and drop them both on a leaf: http://i.stack.imgur.com/Kil3y.jpg (Screenshot)
Here is the sample code:
QTreeWidget *tree = this->ui->treeWidget;
QTreeWidgetItem *item;
QTreeWidgetItem *child;
tree->setSelectionMode(QAbstractItemView::ExtendedSelection);
tree->setDefaultDropAction(Qt::MoveAction);
tree->setDragEnabled(true);
tree->setAcceptDrops(true);
tree->setDropIndicatorShown(true);
// disable dropping of leaves as top level items
tree->invisibleRootItem()->setFlags( Qt::ItemIsSelectable |
Qt::ItemIsUserCheckable | Qt::ItemIsEnabled );
for (int i = 0; i < 2; i++) {
// create top level item
item = new QTreeWidgetItem();
item->setText(0, "parent");
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable
| Qt::ItemIsDropEnabled | Qt::ItemIsEnabled );
// add 3 child items
for (int j = 0; j < 3; j++) {
child = new QTreeWidgetItem();
child->setText(0, "child");
child->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable
| Qt::ItemIsDragEnabled | Qt::ItemIsEnabled );
item->addChild(child);
}
// add item to tree
tree->addTopLevelItem(item);
}
I googled quite a lot but could not come up with a solution. How can I keep child and parent nodes on their respective levels while using ExtendedSelection?
Do I have to
subclass QTreeWidget and override insertRows()? Is there any way to intercept
drag'n'drop actions on QTreeWidget so I could check if the action is ok? (If
there is a way to get this to work with QStandardItemModel/QTreeView I would be
happy too)
One easy workaround is to connect a little function to the signal itemSelectionChanged that removes all items from the selection that are not of the same type as the last selected item. Works just perfect for me (and for other programs like the Warcraft 3 trigger editor)
Related
I created labels and added them to the layout. How to get the elements from the layout? I tried to use method children(), but it gives empty list... Is there a way to get them? Some sample code below.
QGridLayout* layout = new QGridLayout();
QLabel* test = new QLabel();
test->setPixmap(m_staticStorage->getFirstImg());
test->setScaledContents(true);
QLabel* test2 = new QLabel();
test2->setMaximumSize(50,50);
test2->setPixmap(m_staticStorage->getSecondImg());
tes2->setScaledContents(true);
layout->addWidget(worker, 0, 0);
layout->addWidget(farmer, 0, 1);
ui->verticalLayout->addLayout(layout);
//layout->children() ->>>> empty
This will iterate over any QLayout subclass to find the items which have been added so far:
for (int i=0; i < layout->count(); ++i) {
QLayoutItem *item = layout->itemAt(i);
if (!item || !item->widget())
continue;
QLabel *label = qobject_cast<QLabel*>(item->widget());
if (label) {
// .... do stuff with label
}
}
One can also iterate in a similar fashion over each row or column of a QGridLayout using QGridLayout::columnCount() or QGridLayout::rowCount() and then QGridLayout::itemAtPosition() to get the actual QLayoutItem.
If you need to uniquely identify the QLabel after finding it, you could for example give each label a unique objectName or do a setProperty() on them with a unique ID when creating them.
QLabel *test1 = new QLabel(this);
test1->setObjectName(QStringLiteral("test1"));
....
if (label) {
if (!label->objectName().compare(QLatin1String("test1")))
// this is "test1" label
}
QLabel *test1 = new QLabel(this);
test1->setProperty("id", 1);
....
if (label) {
if (label->property("id").toInt() == 1)
// this is "test1" label
}
Better to use the function QObject::findChild
Qt is returning all children of a given Type and Objectname. You can decide to get only direct children or also all recursively.
this->findChild<QLabel*>(QString(), Qt::FindDirectChildrenOnly);
This will return all direct children of this (where this is your parent widget, not your layout) with any name and of type QLabel*
Your approach do not work because the layout do not take the ownership of the labels:
From Layout Management:
Tips for Using Layouts
When you use a layout, you do not need to pass a parent when
constructing the child widgets. The layout will automatically
reparent the widgets (using QWidget::setParent()) so that they are
children of the widget on which the layout is installed.
Note: Widgets in a layout are children of the widget on which the
layout is installed, not of the layout itself. Widgets can only have
other widgets as parent, not layouts.
You can nest layouts using addLayout() on a layout; the inner layout
then becomes a child of the layout it is inserted into.
BTW: Don't forget to set a parent for your layout
QGridLayout* layout = new QGridLayout(this);
and for your labels too
QLabel* test2 = new QLabel(this);
I tried to create a QTreeView as example from Qt
http://doc.qt.io/qt-5/qtwidgets-itemviews-editabletreemodel-example.html
When we click on the triangle before each "parent" item, the "children" items will be shown.
In my case I add this line to tree view
myTreeView->setRootIsDecorated(false);
As the result, there is no more triangle before each "parent" item.
But the "children" items are also not shown any more.
My requirements are:
disable the triangle before each "parent" item
show ALL items in the tree, both "parent" and "children"
How can I do it?
As per the comment you can programmatically ensure all items in the tree are visible by calling QTreeView::expandAll...
myTreeView->expandAll();
Note that it may need to be called again as/when child items are added to the model which, depending on the size of the model, could become a performance bottleneck.
As an alternative, it might be better to inherit from QTreeView and override the QTreeView::rowsInserted member.
virtual void MyTreeView::rowsInserted (const QModelIndex &parent, int start, int end) override
{
/*
* Let the base class do what it has to.
*/
QTreeView::rowsInserted(parent, start, end);
/*
* Make sure the parent model index is expanded. If it already
* is expanded then the following should just be a noop.
*/
expand(parent);
}
This should give better performance for large models.
Per this thread, I am attempting to do the following... (simplified for discussion sake).
TreeItem<Step> item = new TreeItem<>(step);
parent.getChildren().add(0, item);
int row = treeView.getRow(item); //row here is -1?
The reason for this is I am trying to select the added item in it's absolute position in the tree using...
TreeTableViewSelectionModel<Step> msm = treeView.getSelectionModel();
msm.select(row);
So, the first child doesn't get selected. But, this works for all added Tree items except the very first child item of any parent. Following TreeItems have the correct row number and are properly selected. Not understanding why the position isn't correct. The other odd thing is I tried the following..
if (row == -1)
{
TreeItem<Step> parent = newStep.getParent(); //First parent at 0
row = treeView.getRow(parent) + 1; //Now row = 1
}
msm.select(row);
The parent TreeItem is STILL selected instead of the child. Again, all this works after adding the first child. Any help would be greatly appreciated.
EDIT: After further testing, it seems as the the item is technically selected. That is to say the application behaves as if the correct item is selected, but the parent TreeItem still graphically appears to be selected in the TreeView (i.e. it is highlighted). Hope this makes sense.
I have created a QGraphicsScene scene and added some graphcis items (lines, rectangles) etc to the scene.
I can loop through them using this list :
QList<QGraphicsItem*> all = items();
I enabled movement for these items and I am able to drag them by click selecting them. But after an element has been dragged, it stops showing up in the call to items() function of the QGraphicsScene.
QList<QGraphicsItem*> all = items();
None of the dragged items show up in the above list, while non-dragged ones do show up.
Does dragging the QGraphicScene elements change their parent ? or any other reason somebody could suggest for such an issue ?
{P.S. Code is too very big to share}
Edit 1 :
I am using the flags QGraphicsItem::ItemIsSelectable and QGraphicsItem::ItemIsMovable for making the items movable.
foreach(QGraphicsItem* itemInVisualScene, items())
{
itemInVisualScene->setFlag(QGraphicsItem::ItemIsSelectable, itemsMovable);
itemInVisualScene->setFlag(QGraphicsItem::ItemIsMovable, itemsMovable);
}
By default I add few rectangle to the scene. Then in the 'move mode' I drag them around. Then in the 'add mode' I click on screen to add new rectangles. I have written a logic to check if I am clicking on any existing drawn rectangle :
void Scene::mousePressEvent(QGraphicsSceneMouseEvent * event)
{
if(eDrawLines == sceneMode)
{
dragBeginPoint = event->scenePos();
dragEndPoint = dragBeginPoint;
QList<QGraphicsItem*> all = items();
for (int i = 0; i < all.size(); i++)
{
QGraphicsItem *gi = all[i];
// Clicked point lies inside existing rect
if( QGraphicsRectItem::Type == gi->type() && gi->contains(dragBeginPoint))
{
std::cout << "Pressed inside existing rect" << std::endl;
return;
}
}
std::cout << "Point not found, add new rectangle" << std::endl;
}
QGraphicsScene::mousePressEvent(event);
}
This adding of rectangles work fine for rects which were not dragged in the 'move mode'. But rects which were moved do not seem to recognize the click anymore. My control comes out of the loop even when I click on an existing rectangle which was dragged earlier.
QGraphicsItem's transform is changed after dragging and therefore need to transform the point to item's local coordinates.
gi->contains(gi->mapFromScene(dragBeginPoint))
To convert or get item's position in scene coordinates, use
gi->mapToScene(0,0) or gi->scenePos()
in my application I have a tree structure made out of treeitems.
What I wanted to do was change the background of certain tree items if their userObject satisfies certain conditions. The problem I have is when a root tree item is getting its background changed (only tested it on criteria being satisfied at tree items at the root level), all child treeitems of that tree item also have their background changed despite me going in and removing that style sheet on the children.
Long story short: I want it to only change the background on the tree item itself, and not its children.
code:
if(item.getUserObject() != null && ((Device)item.getUserObject()).getDeviceType() == type)
{
item.setStyleName("labelHighlight");
}
else
{
item.removeStyleName("labelHighlight");
}
for(int i = 0; i < item.getChildCount(); i++)
{
highlightNodes(type, item.getChild(i));
}
Use a widget instead of directly styling treeItem. Like this, you can change background of your widget and not the background of all your tree item