i have a qtreewidget with toplevelitems. each toplevelitem has 4 childeren, each child has special value, first child of all toplevelitems is its parrent cost, i want to sort this toplevelitems base on this cost, but i don't know how to do this? my idea is to keep this toplevelitems and their cost in a map and add and take them each time a toplevelitem is added, but i'm looking for a better way.
thanks in advance.
By default, tree widget sorts items according to their texts, however you can change it by overriding the operator<() of the QTreeWidgetItem. Below is the example of custom QTreeWidgetItem with specific operator (see comments):
class TreeWidgetItem : public QTreeWidgetItem
{
public:
// The constructors. Add more, if needed.
TreeWidgetItem(QTreeWidget *parent, const QStringList &strings,
int type = Type)
: QTreeWidgetItem(parent, strings, type)
{}
TreeWidgetItem(QTreeWidgetItem *parent, const QStringList &strings,
int type = Type)
: QTreeWidgetItem(parent, strings, type)
{}
// Compares two tree widget items. The logic can be changed.
bool operator<(const QTreeWidgetItem& other) const
{
// Get the price - the first child node
int price1 = 0;
if (childCount() > 0)
{
QTreeWidgetItem *firstChild = child(0);
price1 = firstChild->text(0).toInt();
}
// Get the second price - the first child node
int price2 = 0;
if (other.childCount() > 0)
{
QTreeWidgetItem *firstChild = other.child(0);
price2 = firstChild->text(0).toInt();
}
// Compare two prices.
return price1 < price2;
}
};
And here is how this class can be used with QTreeWidget:
// The sortable tree widget.
QTreeWidget tw;
tw.setSortingEnabled(true);
QTreeWidgetItem *item1 = new TreeWidgetItem(&tw, QStringList() << "Item1");
QTreeWidgetItem *child1 = new TreeWidgetItem(item1, QStringList() << "10");
QTreeWidgetItem *item2 = new TreeWidgetItem(&tw, QStringList() << "Item2");
QTreeWidgetItem *child2 = new TreeWidgetItem(item2, QStringList() << "11");
tw.show();
Related
I need to retrieve the text in the order in which they appear on the user form. I am trying as below:
QString line = "QLineEdit";
QString combo = "QComboBox";
QList<QWidget *> childWidgets = ui->frame_3->findChildren<QWidget *>();
QStringList data;
for(auto widget : childWidgets){
if(widget->metaObject()->className() == line || widget->metaObject()->className() == combo){
data.append(widget->text()); //append the text of the lineEdits and ComboBoxes to data
}
}
I get the following compile error from the above code:
"no member named 'text' in QWidget
Since you pointed out the QWidget base class does not have a text member function you will need to access the QComboBox and QLineEdit directly to get the current text.
QList<QWidget *> childWidgets = ui->frame_3->findChildren<QWidget *>();
QStringList data;
for(auto widget : childWidgets){
auto combo = dynamic_cast<QComboBox*>(widget);
if (combo) {
data << combo->currentText(); // currentText() returns the text from the combobox
}
else {
auto lineEdit = dynamic_cast<QLineEdit*>(widget);
if (lineEdit) {
data << lineEdit->text(); // A line edit has a text() member.
}
}
}
This code does not handle ordering. I believe the order is in the same order as added to the parent.
In one of my projects I have to manage a list of items, that can be rearranged in their orders by using drag and drop.
Now, all items came with a priority, that cannot be changed by the user. There is an restriction on the order of the elements in the list, namely that elements with a lower priority must come first, but elements with the same priority can be in interchanged.
For example, the following list is sane:
(A,1),(B,1),(C,1),(D,2),(E,3)
whereas the following is broken:
(A,1),(B,1),(E,3),(D,2)
The following code shows a starting point of my problem:
#include <QApplication>
#include <QFrame>
#include <QHBoxLayout>
#include <QListView>
#include <QStandardItemModel>
QStandardItem* create(const QString& text, int priority) {
auto ret = new QStandardItem(text);
ret->setData(priority);
return ret;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
auto frame = new QFrame;
frame->setLayout(new QVBoxLayout);
auto view = new QListView;
frame->layout()->addWidget(view);
auto model = new QStandardItemModel;
view->setModel(model);
model->appendRow(create("1. A", 1));
model->appendRow(create("1. B", 1));
model->appendRow(create("2. X", 2));
model->appendRow(create("2. Y", 2));
model->appendRow(create("2. Z", 2));
view->setDragEnabled(true);
view->viewport()->setAcceptDrops(true);
view->setDropIndicatorShown(true);
view->setDragDropMode(QAbstractItemView::DragDropMode::InternalMove);
view->setDefaultDropAction(Qt::DropAction::MoveAction);
view->setDragDropOverwriteMode(false);
frame->show();
return a.exec();
}
Now, the DefaultDropAction must change context depended on the item going to be moved and also the item where it is going to be dropped.
If the priorities of the two elements are equal, then I have a MoveAction. In case the priorities of the two elements differ, I have a IgnoreAction.
Can this behavior be achieved without implementing my on QListView and what
can be achieved by adapting a custom QAbstractItemModel.
A possible workaround might be even to abandon the drag and drop interface and using the arrow up and down keys to move items around. Or even more general an action with cut and paste operation. But, I really prefer to stick with drag and drop interface.
You could reimplement QStandardItemModel and override the canDropMimeData() method. There are other ways, though they would probably be more involved if you're happy with QStandardItemModel already. Implementing your own model could have performance advantages, especially if your data structure is fairly simple (like a single-column list). This would also let you customize the drag/drop behavior much more in general.
Note that this ignores the action type entirely (QStandardItemModel only allows move and copy by default). Moving an item onto another item will remove the destination item entirely -- which may not be what you want but is a separate issue (see comments in code below).
You could also implement the same logic in dropMimeData() method (before calling the base class method), but I'm not sure I see any advantage. And by using canDropMimeData() the user also gets visual feedback about what is and isn't going to work.
#include <QStandardItemModel>
class ItemModel : public QStandardItemModel
{
public:
using QStandardItemModel::QStandardItemModel;
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override
{
if (!QStandardItemModel::canDropMimeData(data, action, row, column, parent))
return false;
const int role = Qt::UserRole + 1; // what QStandardItem uses for setData() by default
int originPriority;
int destPriority;
// Find destination item priority.
if (parent.isValid()) {
// dropping onto an item
// Note: if you don't want MoveAction to overwrite items you could:
// if (action == Qt::MoveAction) return false;
destPriority = parent.data(role).toInt();
}
else if (row > -1) {
// dropping between items
destPriority = this->data(index(row, 0), role).toInt();
}
else {
// dropping somewhere else onto the view, treat it as drop after last item in model
destPriority = this->data(index(rowCount() - 1, 0), role).toInt();
}
// Need to find priority of item(s) being dragged (encoded in mime data). Could be several.
// This part decodes the mime data in a way compatible with how QAbstractItemModel encoded it.
// (QStandardItemModel includes it in the mime data alongside its own version)
QByteArray ba = data->data(QAbstractItemModel::mimeTypes().first());
QDataStream ds(&ba, QIODevice::ReadOnly);
while (!ds.atEnd()) {
int r, c;
QMap<int, QVariant> v;
ds >> r >> c >> v;
// If there were multiple columns of data we could also do a
// check on the column number, for example.
originPriority = v.value(role).toInt();
if (originPriority != destPriority)
break; //return false; Could exit here but keep going to print our debug info.
}
qDebug() << "Drop parent:" << parent << "row:" << row <<
"destPriority:" << destPriority << "originPriority:" << originPriority;
if (originPriority != destPriority)
return false;
return true;
}
};
For reference, here's how QAbstractItemModel encodes data (and decodes it in the next method down).
ADDED:
OK, it was bugging me a bit, so here is a more efficient version... :-) It saves a lot of decoding time by embedding the dragged item's priority right into the mime data when the drag starts.
#include <QStandardItemModel>
#define PRIORITY_MIME_TYPE QStringLiteral("application/x-priority-data")
class ItemModel : public QStandardItemModel
{
public:
using QStandardItemModel::QStandardItemModel;
QMimeData *mimeData(const QModelIndexList &indexes) const override
{
QMimeData *mdata = QStandardItemModel::mimeData(indexes);
if (!mdata)
return nullptr;
// Add our own priority data for more efficient evaluation in canDropMimeData()
const int role = Qt::UserRole + 1; // data role for priority value
int priority = -1;
bool ok;
for (const QModelIndex &idx : indexes) {
// Priority of selected item
const int thisPriority = idx.data(role).toInt(&ok);
// When dragging multiple items, check that the priorities of all selected items are the same.
if (!ok || (priority > -1 && thisPriority != priority))
return nullptr; // Cannot drag items with different priorities;
priority = thisPriority;
}
if (priority < 0)
return nullptr; // couldn't find a priority, cancel the drag.
// Encode the priority data
QByteArray ba;
ba.setNum(priority);
mdata->setData(PRIORITY_MIME_TYPE, ba);
return mdata;
}
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override
{
if (!QStandardItemModel::canDropMimeData(data, action, row, column, parent))
return false;
if (!data->hasFormat(PRIORITY_MIME_TYPE))
return false;
const int role = Qt::UserRole + 1; // what QStandardItem uses for setData() by default
int destPriority = -1;
bool ok = false;
// Find destination item priority.
if (parent.isValid()) {
// dropping onto an item
destPriority = parent.data(role).toInt(&ok);
}
else if (row > -1) {
// dropping between items
destPriority = this->data(index(row, 0), role).toInt(&ok);
}
else {
// dropping somewhere else onto the view, treat it as drop after last item in model
destPriority = this->data(index(rowCount() - 1, 0), role).toInt(&ok);
}
if (!ok || destPriority < 0)
return false;
// Get priority of item(s) being dragged which we encoded in mimeData() method.
const int originPriority = data->data(PRIORITY_MIME_TYPE).toInt(&ok);
qDebug() << "Drop parent:" << parent << "row:" << row
<< "destPriority:" << destPriority << "originPriority:" << originPriority;
if (!ok || originPriority != destPriority)
return false;
return true;
}
};
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);
}
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) ;
}
} ;
I want to make it so that I can expand a third level (subchild) to a child under the top level item (root). All I've been able to do is make multiple children to a single root.
this is in my .cpp
QStringList string1, string2;
string1 << "xxxxxxxx" << "xxxxxxxxxxx";
string2 << "yyyyyy" << "yy";
m_treeWidget->insertTopLevelItem(0, new QTreeWidgetItem(string1));
m_treeWidget->insertTopLevelItem(1, new QTreeWidgetItem(string2));
//here I add a child
AddChild(m_treeWidget->topLevelItem(0),"hello","world", m_treeWidget);
//here I make two attempts to make a sub child
AddChild(m_treeWidget->itemBelow(m_treeWidget->topLevelItem(0)),"hello_sub1","world_sub1", m_treeWidget);
AddChild(m_treeWidget->itemAt(0,0),"hello_sub2","world_sub2", m_treeWidget);
The following is my Add Child Method also in the same .cpp file:
void Dialog::AddChild (QTreeWidgetItem *parent, QString name, QString Description, QTreeWidget* treeWidget)
{
QTreeWidgetItem *item = new QTreeWidgetItem(treeWidget);
item->setText(0,name);
item->setText(1, Description);
parent->addChild(item);
}
In order to make a tree hierarchy you can use the QTreeWidgetItem's API, especially its constructors. Constructors can accept either QTreeWidget or QTreeWidgetItem as a parent object. In the first case, the top level item will be added to the tree widget and in the second case - child item of another item. This API is easier to use because you don't need to explicitly append items to the tree widget. Here is the sample code that implements the idea:
QStringList string1, string2;
string1 << "xxxxxxxx" << "xxxxxxxxxxx";
string2 << "yyyyyy" << "yy";
QTreeWidget tv;
// The top level items
QTreeWidgetItem *top1 = new QTreeWidgetItem(&tv, string1);
QTreeWidgetItem *top2 = new QTreeWidgetItem(&tv, string2);
// A child item.
QTreeWidgetItem *child1 =
new QTreeWidgetItem(top1, QStringList() << "Hello" << "World");
// The grandchildren.
new QTreeWidgetItem(child1, QStringList() << "Hello_sub1" << "World_sub1");
new QTreeWidgetItem(child1, QStringList() << "Hello_sub2" << "World_sub2");
Actually I was able to solve it another way...
in the .cpp:
//Initialize the QTreeWidget with 2 columns
QTreeWidget m_treeWidget = new QTreeWidget();
m_treeWidget->setColumnCount(2);
//these are the method calls:
AddRoot("Root1_Column1", "Root2_Column2", m_treeWidget);
AddRoot("Root2_Column1", "Root2_Column2", m_treeWidget);
//topLevelItem(0) makes it a child of the first root... topLevelItem(1) makes it a child of the second root
AddChild(m_treeWidget->topLevelItem(0),"Child1_Column1","Child1_Column2");
AddChild(m_treeWidget->topLevelItem(1),"Child2_Column1","Child2_Column2");
AddSubChild(m_treeWidget->itemBelow(m_treeWidget->topLevelItem(0)),"SubChild_Column1", "SubChild_Column2");
With these being the methods I used within the same .cpp file:
void Dialog::AddRoot (QString name, QString Description, QTreeWidget* treeWidget)
{
QTreeWidgetItem *item = new QTreeWidgetItem(treeWidget);
item->setText(0,name);
item->setText(1,Description);
item->setExpanded(true); //expand automatically
treeWidget->addTopLevelItem(item);
}
void Dialog::AddChild (QTreeWidgetItem *parent, QString name, QString Description)
{
QTreeWidgetItem *item = new QTreeWidgetItem();
item->setText(0,name);
item->setText(1, Description);
parent->addChild(item);
}
void Dialog::AddSubChild (QTreeWidgetItem *parent, QString name, QString Description)
{
QTreeWidgetItem *item = new QTreeWidgetItem();
item->setText(0,name);
item->setText(1, Description);
parent->addChild(item);
}