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.)