简体   繁体   中英

Qt itemChanged signal with QTreeView model works only on first level items

I don't know if I do something wrong in my qt code. I just need that itemChanged signal is emitted every time when item data changed. I use following code to make the model:

QStandardItemModel* model = new QStandardItemModel;
QStandardItem *parentItem = model->invisibleRootItem();
QList<QStandardItem*> itemList1;
QList<QStandardItem*> itemList2;
QList<QStandardItem*> itemList3;
QStandardItem* item1;
QStandardItem* item2;
QStandardItem* item3;

for (int i = 0; i < 3; ++i)
{
    item1 = new QStandardItem;
    item1->setText("item1-" + QString::number(i));

    for (int i = 0; i < 3; ++i)
    {
        item2 = new QStandardItem;
        item2->setText("item2-" + QString::number(i));

        for (int i = 0; i < 3; ++i)
        {
            item3 = new QStandardItem;
            item3->setText("item3-" + QString::number(i));
            itemList3 << item3;
        }
        item2->appendRows(itemList3);
        itemList3.clear();
        itemList2 << item2;
    }
    item1->appendRows(itemList2);
    itemList2.clear();
    itemList1 << item1;
}
parentItem->appendRows(itemList1);
itemList1.clear();

ui.treeView->setModel(model);

QObject::connect(model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(onChanged(QStandardItem*)));

and I want, that onChanged will be called every time when item changed - for example, item text edited or checkbox clicked. But in each case I have triggered itemChanged signal only on "item1-..." level items (first level items), not on item2/3 level items. Why? And how can I make it correct?

PS: same code with QTreeWidget works perfectly, but I use multithreading in my app and I need to divide model and view. QTreeWidget items can't be created in non-gui thread and qtreewidget can't use self-created model. That's the reason why I must to use QTreeView with QStandardItem.

I just debugged the situation, the reason why you don't get the signal is as follows: When the item's data is changed, you have this in the Qt source:

void QStandardItem::setData(...)
{
   /* ... */
   if (d->model)
       d->model->d_func()->itemChanged(this);
}

On the other hand, when adding a child item to a parent, you have

bool QStandardItemPrivate::insertRows(int row, const QList<QStandardItem*> &items)
{
   /* ... */
   for (int i = 0; i < items.count(); ++i) {
     /* ... */
       item->d_func()->model = model;
   }
}

So this means, items need a pointer to the model to notify the model about the change, and the model of a child item is set to the parent's item at the time of insertion . Now you add the children to the parent first, and then the parents to their parents, with the invisible root as the parent of level-1 items. As the invisible root has a model, the level-1 items send the signal, but the others do not.

A simple change fixes this: Just add the item to its parent first, and then append the children, like this:

for (int i = 0; i < 3; ++i)
{
    item1 = new QStandardItem;
    item1->setText("item1-" + QString::number(i));
    parentItem->appendRow(item1);

    for (int i = 0; i < 3; ++i)
    {
        item2 = new QStandardItem;
        item2->setText("item2-" + QString::number(i));
        item1->appendRow(item2);

        for (int i = 0; i < 3; ++i)
        {
            item3 = new QStandardItem;
            item3->setText("item3-" + QString::number(i));
            item2->appendRow(item3);
        }
    }
}

I am not sure why the default is for child item mods not being causing the parent item to be "changed". I suppose as a workaround you could connect each childs DataChanged() signal to its parents DataChanged() signal, so that the signal propagates up the hierarchy, something like:

for (int i = 0; i < 3; ++i)
{
    item1 = new QStandardItem;
    item1->setText("item1-" + QString::number(i));

    for (int i = 0; i < 3; ++i)
    {
        item2 = new QStandardItem;
        item2->setText("item2-" + QString::number(i));

        for (int i = 0; i < 3; ++i)
        {
            item3 = new QStandardItem;
            item3->setText("item3-" + QString::number(i));
            itemList3 << item3;
            connect(item3, SIGNAL(DataChanged()), item2, SIGNAL(DataChanged()));
        }
        item2->appendRows(itemList3);
        itemList3.clear();
        itemList2 << item2;
        connect(item2, SIGNAL(DataChanged()), item1, SIGNAL(DataChanged()));
    }
    item1->appendRows(itemList2);
    itemList2.clear();
    itemList1 << item1;
}

What should happen is that if you change an item3 level record the signal should be forwarded all the way up to item1 (which you suggest is working) and then continue as normal:

item3 ---DataChanged()---> item2
item2 ---DataChanged()---> item1
item1 ---DataChanged()---> model
model ---itemChanged()---> onChanged()

I have not tested this, but assuming the item1 dataChanged() signal is working for you (which your comments suggest) then this should work.

Edit

Ah, I think this may not actually work as you want it. I think you will always get an item1 pointer sent to your onChanged() slot. If you want the item3 pointer sent then you may have to sub class QStandardItem (make a class that inherits QStandardItem) and then do the following:

  • Add a signal that emits: itemChanged(QStandardItem*)
  • add a slot: void itemHasChanged() {emit itemChanged(this);}
  • connect dataChanged() to itemHasChanged() in the constructor.

Then in your loop you can item1 = new myQStandardItem; And then for each new item you add directly connect them to your onChanged() slot:

QObject::connect(itemX, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(onChanged(QStandardItem*)));

It's a bit of an extra effort (but not too much), if you can't get the model to do it for you (ie a plan-B)...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM