How to repace an element in a Qt tree widget? - qt

I have a Qt application that contains a QTreeWidget.
I want to replace one of the items.
I have a pointer:
QTreeWidgetItem *elementToSubstitute;
and a function that returns a QTreeWidgetItem*.
I want to overwrite the previous one with this new element, in the same place.
If I delete the previous element and insert the new one after that, the new item is placed at the bottom of the tree.
How should I proceed to have the new branch replace exactly the previous one?
EDIT for example
void MainWindow::addRoot(QString name ,QString description)
{
QTreeWidgetItem *itm = new QTreeWidgetItem(ui->tree);
itm->setText(0,name);
itm->setText(1,description);
QTreeWidgetItem *child1 = addChild(itm,"one","hello");
QTreeWidgetItem *child2 = addChild(itm,"two","hello");
QTreeWidgetItem *child3 = addChild(itm,"tree","hello");
QTreeWidgetItem *child4 = addChild(itm,"four","hello");
QTreeWidgetItem *parent = child2->parent();
int itemIndex = parent-indexOfChid(child2);
parent->removeChild(child2);
parent->insertChild(itemIndex, child4 );
}
QTreeWidgetItem MainWindow::addChild(QTreeWidgetItem *parent,QString name ,QString description)
{
QTreeWidgetItem *itm = new QTreeWidgetItem();
itm->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled );
itm->setText(0,name);
itm->setText(1,description);
addChild2(itm,"sub-one","subchild2");
addChild2(itm,"sub-two","subchild2");
addChild2(itm,"sub-tree","subchild2");
parent->addChild(itm);
return itm;
}
void MainWindow::addChild2(QTreeWidgetItem *parent,QString name ,QString description)
{
QTreeWidgetItem *itm = new QTreeWidgetItem();
itm->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsEditable);
itm->setText(0,name);
itm->setText(1,description);
itm->setText(2,"test third coloumn");
parent->addChild(itm);
}
.....
Now i have a pointer to one of the chiled. ( let's sa of level 1) ans i want to replace it with another. for instance i want to replace child "2" with child "4" but i want this to be put in the place f child 2.

You could try doing something like this:
QTreeWidgetItem *parent = elementToSubstitute->parent();
int itemIndex = parent->indexOfChid(elementToSubstitute);
parent->removeChild(elementToSubstitute);
parent->insertChild(itemIndex, yourNewTreeItem);
Note: if yourNewTreeItem is already in the tree, this will have no effect on its position, i.e. it will not duplicate a node.

If your item is top-level then you could use indexOfTopLevelItem to get it's index, then remove it from tree widget, then insert replacement item using insertTopLevelItem passing index you obtained. If you item is not top-level then you could use indexOfChild and insertChild.

Why do you need to replace the whole item? Just change the contents of the current item, or copy the new item into the current one.
//assuming you have a pointer to the new and the current Item
*currentItem = *newItem; //done

Related

QListView with QStandardItemModel does not show selection highlight through code

I have a QListView that is populated with either a QStandardItemModel or a QStringListModel (based on simplicity of contents... number of columns).
On load, or switching between widgets, I search for the item that should be selected, and try to highlight it.
if (first)
{
m_myListView.setModel(m_standardItemModel);
QList<QStandardItem*> lst = m_standardItemModel->findItems(m_value1, Qt::MatchExactly, 1);
if(!lst.isEmpty())
{
QModelIndex index = lst.at(0)->index();
qDebug() << index.row(); // tells me correct row
//m_myListView.setCurrentIndex(index); // no change if I use
m_myListView.selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
m_myListView.scrollTo(index);
}
}
else
{
m_myListView.setModel(m_stringListModel);
int i = m_stringListModel->stringList().indexOf(m_value2);
if (i >= 0)
{
QModelIndex index = m_stringListModel->index(i);
m_myListView.selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
m_myListView.scrollTo(index);
}
}
The m_stringListModel version correctly highlights (and scrolls to item).
The m_standardItemModel version does not highlight row, and does not scroll to item. But in the uses afterwards, it correctly provides the data for selected index:
QModelIndexList indexList = m_myListView.selectionModel()->selectedIndexes();
if (!indexList.isEmpty())
{
QModelIndex index = indexList.first();
if (index.isValid())
{
row = index.row();
data1 = m_standardItemModel->index(row, 1).data().toString();
...
So... it seems that the selection works, but if it does, why do I not see a highlight ? (and the scrollTo() )
Note - the code is pretty giant but I verified for the possibility of reloading the model and possibly losing the selection - and besides, the QStringListModel version works correctly.
Is that a typical behavior of QStandardItemModel, or is there something I must do, like setting a BackgroundRole type data ?
How can I highlight the selection of the list view with the QStandardItemModel applied ?
I see your code, probably you want to select the first element of your model? Let's try:
void MyClass::selectFirstElement() {
const QModelIndex firsIndex = _myModel->index(0,0);
if (index.isValid())
ui->listView->setCurrentIndex(firstIndex);
ui->listView->scrollTo(firstIndex);
}
}
Could you share the m_standardItemModel implementation? Also configure your list correctly:
ui->listView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->listView->setSelectionBehavior(QAbstractItemView::SelectRows); // Or Columns
Check if your QStandarItem has the selection flag enable. See http://doc.qt.io/qt-4.8/qt.html#ItemFlag-enum for more info.
Finally, you could ensure that the index is stored in the correct model by getting the index in the same row & column directly from the model, something like this:
QModelIndex index = lst.at(0)->index();
index = _model->index(index.row(), index.column());
Sorry, for my poor english :S
Because the item found is different than the display item, the list view is unable to select it...
2 solutions: either create a different QModelIndex from the one found, pointing to the display column, or select an entire row containing the desired index:
m_myListView.selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);

Get The Row,Column values from a cell in QT

I have a problem, i want to return the selected Rows values, and the columns separately, i found a method to return both of them using the function cell(row, column), but i want to get them separately
Here is my code :
QTableWidgetItem *c = new QTableWidgetItem();
QMap<QString,int> lists;
for(i=0;i<range.rowCount();++i){
for(int j=0;j<range.columnCount();++j){
c=item(i,j);// here i can return the Rows, Columns Data
QMessageBox::information(this,"",c->text());
}
}
As you can see this code is working, but i just want to return the Rows and the Columns separately so i can put them in my QMap<QString,int> list.
And the purpose of all this is to try to draw a piechart from the selected rows and columns
So Any help please
Here is what I understood from the comments, feel free to correct me and I'll update my answer if necessary.
COL1 | COL2
NAME | VALUE
So when you select a cell, you actually care about the whole row, a.k.a the name of the row and the value associated. If this is the case, it would make more sense to only allow the user to select whole rows, instead of cells. setSelectionBehavior(QAbstractItemView::SelectRows); should do the trick.
Provided that the name of the dataset is always in column 1, and the value in column 2, you should update your code with the snippet:
QTableWidgetItem *c; //Deleted memory leak in your code.
QMap<QString,double> myMap; //Don't name it a list if it is explicitly a map.
for(i=0;i<range.rowCount();++i){
QString dataName = item(i,0)->text();
int dataValue;
for(int j=1;j<range.columnCount();++j){
c=item(i,j);// here i can return the Rows, Columns Data
dataValue += c->text().toDouble();
//If you always have 2 columns only, dataValue will be the value you are looking for.
//If you can have more than 2 columns, dataValue will be the sum of all the cells located after the column 0, on the same row.
//Change this depending on how you want to treat those values.
QMessageBox::information(this,dataName,c->text());
}
myMap[dataName]=dataValue;
}
EDIT for QPieSeries, following this example:
QPieSeries *series = new QPieSeries();
QMap<QString,double>::iterator it = myMap.begin();
QMap<QString,double>::iterator end = myMap.end();
for(; it!=end; ++it){
series->append(it->key(), it->value());
}
QPieSlice *slice = series->slices().at(1);
slice->setExploded();
slice->setLabelVisible();
slice->setPen(QPen(Qt::darkGreen, 2));
slice->setBrush(Qt::green);
QChart *chart = new QChart();
chart->addSeries(series);
chart->setTitle("My Data");
chart->legend()->hide();
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
/*change with your window here*/
yourWindow.setCentralWidget(chartView);

one item for 5 positions

I have a table. And an item.
How can I use only one Item to fill part of the table?
If I set item to one position and then take item, I lose text at that position.
And use lots of items is not comfortable.
QTableWidgetItem *Type = new QTableWidgetItem;
if( line.contains("some"))
{
Type->setText("some");
ui->tableWidget->setItem(i, 0, Type);
}
else if( line.contains("shi"))
{
Type->setText("shi");
ui->tableWidget->setItem(i, 0, Type);
}
ui->tableWidget->takeItem(i, 0);
You can create copies of the item to insert at different cells. This can be done using clone :
QTableWidgetItem *Type1 = new QTableWidgetItem;
Type1->setText("some");
ui->tableWidget->setItem(row1, col1, Type1);
QTableWidgetItem *Type2 = Type1->clone(); // create a copy
Type2->setText("shi");
ui->tableWidget->setItem(row2, col2, Type2);

Qt - Tricky search algorithm - duplicates or "mirrored" duplicates in a list

Picture this: I have a JSON file which has a list of Node objects that contain a list of Link types. Something like:
node1:
links: node1,node2
node3,node1
node1,node6
node2:
links: node2,node1
node2,node9
node7,node2
I need to identify unique pairs of links - ie a (node_a,node_b) pair. Note that (node_b,node_a) represents the same thing.
The Link class has getter methods that return a pointer to either the source/destination Node. In the file, the information about the links is stored as a string that has the source node's name and the destination node name, like: (source,destination).
When I build my structure from file, I first create the Nodes and only then I create the Links. The Link constructor is as follows:
Link::Link(Node *fromNode, Node *toNode)
And my code to create the links:
QList<Link*> consumedLinks; // list where I was trying to place all the non-duplicate links
// for each node in the file
foreach(QVariant nodesMap, allNodesList){
QVariantMap node1 = nodesMap.toMap();
QList<QVariant> nodeDetails = node1["node"].toList();
QVariantMap allLinksMap = nodeDetails.at(9).toMap();
// extract all the links of the node to a list of QVariant
QList<QVariant> linksList = allLinksMap["links"].toList();
// for each Link in that list
foreach(QVariant linkMap, linksList){
QVariantMap details = linkMap.toMap();
Node *savedFromNode;
Node *savedToNode;
// get all the Items in the scene
QList<QGraphicsItem*> itemList = scene->items();
// cast each item to a Node
foreach(QGraphicsItem *item, itemList){
Node* tempNode = qgraphicsitem_cast<Node*>(item);
if(tempNode){
// check what the node name matches in the link list
if(tempNode->text()==details["fromNode"]){
savedFromNode = tempNode;
}
if(tempNode->text()==details["toNode"]){
savedToNode = tempNode;
}
}
}
// create the link
Link *linkToCheck = new Link(savedFromNode,savedToNode);
// add it to the links list (even if duplicate)
consumedLinks.append(linkToCheck);
}
}
// add all the links as graphics items in the scene
foreach(Link *linkToCheck, consumedLinks){
scene->addItem(linkToCheck);
}
So right now this doesn't check for duplicates in the consumedLinks list (obviously). Any ideas on how to achieve this?
NOTE: I know that the pseudo-JSON above isn't valid, it's just to give you an idea of the structure.
NOTE2: I rephrased and added detail and code to the question to make it clearer to understand what I need.
1. Normalize links i.e. replace (a, b) to (b, a) when b < a. Each link should be represented as (a, b), a < b.
2. Create a QSet< QPair<Node*, Node *> > variable and put all links into it. Each link object can be maked using qMakePair(node1, node2). Since QSet is an qnique container, all duplicates will be removed automatically.
Well, I didn't exactly do the same as #Riateche suggested, but something similar.
I basically created a QList<QPair<QString,QString> > so I could check for duplicates easily and then I created a list of Link based on the items in the QPair list. Something like:
QList<Node*> existingNodes;
QList<QPair<QString,QString> > linkPairList;
QList<QGraphicsItem*> itemList = scene->items();
foreach(QGraphicsItem *item, itemList){
Node* tempNode = qgraphicsitem_cast<Node*>(item);
if(tempNode){
existingNodes.append(tempNode);
}
}
foreach(QVariant nodesMap, allNodesList){
QVariantMap node1 = nodesMap.toMap();
QList<QVariant> nodeDetails = node1["node"].toList();
QVariantMap allLinksMap = nodeDetails.at(9).toMap();
QList<QVariant> linksList = allLinksMap["links"].toList();
foreach(QVariant linkMap, linksList){
QVariantMap details = linkMap.toMap();
QPair<QString,QString>linkPair(details["fromNode"].toString(),details["toNode"].toString());
QPair<QString,QString>reversedLinkPair(details["toNode"].toString(),details["fromNode"].toString());
if(!linkPairList.contains(linkPair)){
// !dupe
if(!linkPairList.contains(reversedLinkPair)){
// !reversed dupe
linkPairList.append(linkPair);
}
}
qDebug()<<"number of pairs: "<<linkPairList.size();
}
}
QPair<QString,QString> linkPairInList;
foreach(linkPairInList, linkPairList){
Node *savedFromNode;
Node *savedToNode;
foreach(Node* node, existingNodes){
if(node->text()==linkPairInList.first){
savedFromNode=node;
}
if(node->text()==linkPairInList.second){
savedToNode=node;
}
}
Link *newLink = new Link(savedFromNode,savedToNode, "input");
scene->addItem(newLink);
}
This is the correct answer because it solves what I needed to solve. Since I only got there because of #Riateche's comments, I +1'd his answer.

How to simply delete row in QFormLayout programatically

I have this code:
myEdit = QLineEdit()
myQFormLayout.addRow("myLabelText", myEdit)
Now I have to remove the row by reference to myEdit only:
myQformLayout.removeRow(myEdit)
But there is no API for that. I can use .takeAt(), but how can I get the argument? How do I find the label index, or the index of myEdit?
You can just schedule the widget and its label (if it has one) for deletion, and let the form adjust itself accordingly. The label for the widget can be retrieved using labelForField.
Python Qt code:
label = myQformLayout.labelForField(myEdit)
if label is not None:
label.deleteLater()
myEdit.deleteLater()
my solution...
in header file:
QPointer<QFormLayout> propertiesLayout;
in cpp file:
// Remove existing info before re-populating.
while ( propertiesLayout->count() != 0) // Check this first as warning issued if no items when calling takeAt(0).
{
QLayoutItem *forDeletion = propertiesLayout->takeAt(0);
delete forDeletion->widget();
delete forDeletion;
}
This is actually a very good point... there is no explicit reverse function for addRow().
To remove a row you can do the following:
QLineEdit *myEdit;
int row;
ItemRole role;
//find the row
myQFormLayout->getWidgetPosition( myEdit, &row, &role);
//stop if not found
if(row == -1) return;
ItemRole otheritemrole;
if( role == QFormLayout::FieldRole){
otheritemrole = QFormLayout::LabelRole;
}
else if( role == QFormLayout::LabelRole){
otheritemrole = QFormLayout::FieldRole;
}
//get the item corresponding to the widget. this need to be freed
QLayoutItem* editItem = myQFormLayout->itemAt ( int row, role );
QLayoutItem* otherItem = 0;
//get the item corresponding to the other item. this need to be freed too
//only valid if the widget doesn't span the whole row
if( role != QFormLayout::SpanningRole){
otherItem = myQFormLayout->itemAt( int row, role );
}
//remove the item from the layout
myQFormLayout->removeItem(editItem);
delete editItem;
//eventually remove the other item
if( role != QFormLayout::SpanningRole){
myQFormLayout->removeItem(otherItem);
delete otherItem
}
Note that I retrieve all the items before removing them. That's because I don't know if their role will change when an item is removed. This behavior is not specified so I am
playing safe. In qt designer, when you remove an item from a form, the other item on
the row take all the space (which means his role changes...).
Maybe there is a function somewhere and not only I reinvented the wheel but I made a broken one...

Resources