Home > OS >  Qt Jambi: QTreeView with Custom Model?
Qt Jambi: QTreeView with Custom Model?

Time:12-22

I'm attempting to create a tree view in QT using Java to display something similar to

  Category A
|   Category AB
| | Item AB
| \ Item A
  Category B
| \ Item B
\ Item

with a custom QAbstractItemModel while following the guide.

There seems to be some issues with how Qt Jambi has implemented the wrapper for QModelIndex. There doesn't appear to be a way to create new instances of it like you can in the Qt guide and it appears to be missing function wrappers (at least isValid and internalPointer).

Here is the code I currently have based on the guide: TreeModel

package playground;

import io.qt.core.*;

import java.util.stream.Stream;

public final class TreeModel extends QAbstractItemModel {
    private final TreeItem rootItem;

    public TreeModel(QString data, QObject parent) {
        super(parent);

        rootItem = new TreeItem(Stream.of(tr("Title"), tr("Summary")).map(QVariant::new).toList(), null);
        setupModelData(data.split('\n'), rootItem);
    }

    private void setupModelData(QStringList split, TreeItem rootItem) {
        //TODO
    }

    @Override
    public QModelIndex index(int row, int column, QModelIndex parent) {
        if(!hasIndex(row, column, parent)) {
            return new QModelIndex();
        }

        var parentItem = !parent.isValid() ? rootItem : parent.internalPointer();
        var child = parentItem.child(row);

        return child != null ? createIndex(row, column, childItem) : new QModelIndex();
    }

    @Override
    public QModelIndex parent(QModelIndex index) {
        if(!index.isValid()) {
            return new QModelIndex();
        }

        var childItem = index.internalPointer();
        var parentItem = childItem.parentItem();

        if(parentItem == rootItem) {
            return new QModelIndex();
        }

        return createIndex(parentItem.row(), 0, parentItem);
    }

    @Override
    public int rowCount(QModelIndex parent) {
        if(parent.column() > 0) {
            return 0;
        }

        var parentItem = !parent.isValid() ? rootItem : parent.internalPointer();
        return parentItem.childCount();
    }

    @Override
    public int columnCount(QModelIndex parent) {
        return (parent.isValid() ? parent.internalPointer() : rootItem).columnCount();
    }

    @Override
    public Object data(QModelIndex index, int role) {
        if(!index.isValid()) {
            return new QVariant();
        }

        if(role != Qt.ItemDataRole.DisplayRole) {
            return new QVariant();
        }

        var item = index.internalPointer();
        return item.data(index.column());
    }

    @Override
    public Qt.ItemFlags flags(QModelIndex index) {
        return !index.isValid() ? new Qt.ItemFlags() : index.flags();
    }

    @Override
    public Object headerData(int section, Qt.Orientation orientation, int role) {
        if(orientation == Qt.Orientation.Horizontal && role == Qt.ItemDataRole.DisplayRole) {
            return rootItem.data(section);
        }

        return new QVariant();
    }
}

TreeItem:

package playground;

import io.qt.core.QVariant;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public final class TreeItem {
    private final List<TreeItem> childItems = new ArrayList<>();
    private final List<QVariant> itemData = new ArrayList<>();
    private final TreeItem parentItem;

    public TreeItem(List<QVariant> data, TreeItem parent) {
        this.itemData.addAll(data);
        this.parentItem = parent;
    }

    public void appendChild(TreeItem child) {
        Objects.requireNonNull(child);
        childItems.add(child);
    }

    public TreeItem child(int row) {
        if(row < 0 || row >= childItems.size()) {
            return null;
        }
        return childItems.get(row);
    }

    public int childCount() {
        return childItems.size();
    }

    public int columnCount() {
        return itemData.size();
    }

    public QVariant data(int column) {
        if(column < 0 || column >= itemData.size()) {
            return null;
        }
        return itemData.get(column);
    }

    public int row() {
        if(parentItem != null) {
            return parentItem.childItems.indexOf(this);
        }
        return 0;
    }

    public TreeItem parentItem() {
        return parentItem;
    }
}

CodePudding user response:

Alright, I figured this one out after someone pointed me in the correct direction on a different platform.

Turns out you need to go though a QAbstractItemModel method to create an instance of a QModelIndex and you also need to create your own "invalid" indexes instead of using a no-args method.

Instead of using new QModelIndex() you need to use QAbstractItemModel.createIndex(-1, -1, 0). Instead of QModelIndex.isValid you need to do something like the following:

public static boolean isIndexValid(QModelIndex index) {
    return index != null && index.row() >= 0 && index.column() >= 0 && index.model() != null;
}

Finally instead of using the internal ID stuff as if it was a pointer you need to keep your own record of IDs. My basic implementation is as follows:

private final Map<Long, TreeItem> idToItemMap = new HashMap<>();
private final Map<TreeItem, Long> itemToIdMap = new HashMap<>();
private long nextId = 1;

private long getId(TreeItem childItem) {
    long id = itemToIdMap.getOrDefault(childItem, -1L);
    if(id == -1) {
        id = nextId  ;
        itemToIdMap.put(childItem, id);
        idToItemMap.put(id, childItem);
    }
    return id;
}

private TreeItem getItem(QModelIndex index) {
    var item = idToItemMap.get(index.internalId());
    if(item == null) {
        throw new NullPointerException("item was null: "   index);
    }
    return item;
}

(Don't use this in production code that can remove items from the model, it will keep references and leak memory. Append only should be okay.)

  • Related