Пользовательская модель, расширяющая SwingX AbstractTreeTableModel: некоторые узлы не отображаются в графическом интерфейсе
Я пытаюсь реализовать пользовательский элемент управления для редактирования атрибутов списка (отношения 1:n). Поскольку у меня недостаточно репутации, чтобы опубликовать скриншот, я постараюсь описать его. Он содержит список записей в качестве значения, каждая запись представлена RecordPanel, которая в основном является формой редактирования для записи.
Меню управления предлагает возможность добавлять, удалять и копировать записи, а также просматривать RecordPanels. Отображается только активная панель RecordPanel. Слева от активной RecordPanel JXTreeTable, использующий мой пользовательский TreeTableModel (который является причиной моей проблемы), используется для отображения списка записей, содержащихся в элементе управления. Записи значений составляют листья модели, родители могут быть получены из атрибута соответствующего потомка. Дерево можно использовать для прямого перехода к определенной записи.
Для построения TreeTableModel я использую шаблон XML. Пример:
<?xml version="1.0" encoding="UTF-8"?>
<treetable prefix="Drug" columns="name">
<branch entity="JpaDrugCharacterization" children="drugs" displayFields="name"/>
<branch entity="JpaDrug" parent="drugCharacterization" displayFields="name"/>
</treetable>
Модель использует TreeTableNodes, которые содержат фактические записи, а также их родительские узлы и дочерние узлы. Таким образом, можно построить полную древовидную структуру из узлов.
Определения ветвей, содержащиеся в шаблоне XML, будут использоваться для создания объектов TreeTableBranch, которые знают, как извлечь родительские и дочерние ветви, а также какие поля отображать в таблице дерева.
Вот ключевые методы, которые используются для создания NodeTreeTableModel. Я не включал переопределенные методы AbstractTreeTableModel.
/**
* Builds a tree table model based on {@link TreeTableNode}s and {@link TreeTableBranch}es.
* Can be initialized using an XML template or by using the root node of a TreeTableNode structure
* together with a branch definition created by other means (e.g. programmatically).
*
* @author Lellebebbel
*
*/
public class NodeTreeTableModel extends AbstractTreeTableModel implements DebugLogger {
/**
* The root node of the tree table serving as the master parent.
* Entry point for {@link JXTreeTable} to build the tree. Not shown in GUI.
*/
private TreeTableNode rootNode;
/**
* Contains a hashtable mapping each branch definition
* to the class name of the corresponding class in the tree.
*/
private Hashtable<String, TreeTableBranch> branches = new Hashtable<>();
/**
* List of the column names given in the template.
* Used for initialization of the {@link fieldMapPool}
* and creation of the {@link JXTreeTable}.
*/
private List<String> columnNames = new ArrayList<>();
/**
* Contains a hashtable mapping the id of the contained objects to the respective TreeTableNode.
* Used to prevent duplicate creation of TreeTableNodes containing the same object.
*/
private Hashtable<Integer, TreeTableNode> nodeList = new Hashtable<>();
/**
* Contains a hashtable mapping the child class name to the corresponding dummy parent.
*/
private Hashtable<String, NamedPositionable> dummyParents = new Hashtable<>();
NodeTreeTableModel(TreeTableNode rootNode, Document template, List<? extends NamedPositionable> valueList) {
super(rootNode);
this.rootNode = rootNode;
this.initialize(template);
this.createTreeStructure(valueList);
}
/**
* Create a tree table structure based on the values set for the tree table.
*
* @param valueList the values of the tree table
*/
private void createTreeStructure(List<? extends NamedPositionable> valueList) {
// browse through the valueList (containing the designated leaves for the tree)
for (NamedPositionable leaf : valueList) {
// create the tree table node for the leaf
TreeTableNode leafNode = new TreeTableNode(leaf);
// link all parents up to the tree rootNode to the leaf
TreeTableNode linkNode = leafNode;
while (!linkNode.equals(this.rootNode)) {
linkNode = this.linkToParent(linkNode);
}
nodeList.put(leaf.getId(), leafNode);
}
}
/**
* Evaluate the template, initialize translation prefix, column names and branch definitions.
*
* @param template The template to be evaluated
*/
private void initialize(Document template) {
// start parsing of template
Element root = template.getDocumentElement();
// initialize the translation prefix
this.prefix = XMLTools.getAttribute(root, "prefix", true);
// initialize the column names
String[] columnNameArray = XMLTools.getAttribute(root, "columns", true).split(",");
for (String columnName : columnNameArray) {
this.columnNames.add(Translator.get(this.prefix + "." + columnName));
}
// initialize the branches
NodeList branchNodes = root.getElementsByTagName("branch");
for (int i = 0; i < branchNodes.getLength(); i++) {
Node branchNode = branchNodes.item(i);
// retrieve the class name to be used as key
String className = XMLTools.getAttribute(branchNode, "entity", true);
// create the tree table branch to be used as value
TreeTableBranch branch = new TreeTableBranch();
String[] displayFields = XMLTools.getAttribute(branchNode, "displayFields", true).split(",");
for (int j = 0; j < this.columnNames.size(); j++) {
branch.addDisplayField(columnNames.get(j), displayFields[j]);
}
String parentField = XMLTools.getAttribute(branchNode, "parent", false);
branch.setParentField(parentField);
String childField = XMLTools.getAttribute(branchNode, "children", false);
branch.setChildField(childField);
this.branches.put(className, branch);
}
}
/**
* Links a given node to its parentNode an vice versa.
* Part of the tree table structure building routine.
*
* @param linkNode the childNode to be linked
* @return parentNode the linked parentNode
*/
private TreeTableNode linkToParent(TreeTableNode linkNode) {
// find the branch of the linkNode
TreeTableBranch branch = branches.get(linkNode.getContainedClassName());
// if branch does not specify a parent, use rootNode
if (branch.getParentField() == null) {
linkNode.setParent(this.rootNode);
this.rootNode.addChild(linkNode);
return this.rootNode;
}
// get the parent object
NamedPositionable parent =
(NamedPositionable) ReflectionTools.getValue(linkNode.getContainedObject(), branch.getParentField());
if (parent == null) {
parent = this.getDummyParent(linkNode.getContainedClassName());
}
TreeTableNode parentNode;
// if parent node has already been created
if (this.nodeList.containsKey(parent.getId())) {
// take it
parentNode = this.nodeList.get(parent.getId());
// establish the link
linkNode.setParent(parentNode);
parentNode.addChild(linkNode);
// return root node, as the parent is already connected to root
return this.rootNode;
} else {
// create it
parentNode = new TreeTableNode(parent);
// establish the link
linkNode.setParent(parentNode);
parentNode.addChild(linkNode);
// put the new parent in the nodeList and return it
this.nodeList.put(parent.getId(), parentNode);
return parentNode;
}
}
/**
* Returns the proper dummy parent for a given class name of a child.
* If the dummy parent does not exist, it will be created.
*
* @param childClassName name of the child class
* @return the dummy parent
*/
@SuppressWarnings("unchecked")
private NamedPositionable getDummyParent(String childClassName) {
NamedPositionable dummyParent = this.dummyParents.get(childClassName);
if (dummyParent != null) {
return dummyParent;
}
/*
* As we do not have a fitting dummy parent, we have to create it...
* We start by finding out, of which class the parent should be.
*/
TreeTableBranch childBranch = this.branches.get(childClassName);
/*
* As IdRecord should be the most common case for tree children,
* the short version of the class name may be used in the template.
*/
Class<? extends NamedPositionable> childClass;
if (!childClassName.contains(".")) {
childClass = ReflectionTools.getClassByEntityName(childClassName);
} else {
childClass = (Class<? extends NamedPositionable>) ReflectionTools.getClassByName(childClassName);
}
String methodName = ReflectionTools.getGetterName(childBranch.getParentField());
try {
Class<? extends NamedPositionable> parentClass = (Class<? extends NamedPositionable>) childClass
.getMethod(methodName, (Class<?>[]) null).getReturnType();
// as we know the class now, we can create the parent object
dummyParent = parentClass.newInstance();
// now we need to set the initial values of the parent object
dummyParent.initializeId();
TreeTableBranch parentBranch = this.branches.get(parentClass.getSimpleName());
for (String fieldName : parentBranch.getDisplayFields()) {
ReflectionTools.setValue(dummyParent, fieldName, Translator.get("NodeTreeTableModel.undefinedNode"));
}
// finally, we can add the parent to the dummy parent list and return it
this.dummyParents.put(childClassName, dummyParent);
return dummyParent;
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException e) {
Monitor.capture(e);
return null;
}
}
}
Наконец, вот методы, используемые для рисования элемента управления и обновления древовидной таблицы.
/**
* Rebuilds the tree table. Should be called, when value has changed.
*/
public void buildTreeTable() {
this.treeTable = new RecordPaginationTreeTable(this.createTreeTableModel());
this.treeTable.setAutoResizeMode(JXTreeTable.AUTO_RESIZE_ALL_COLUMNS);
// Calculate max width of the tree table
this.treeTable.expandAll();
Dimension treeTableSize = this.treeTable.getPreferredSize();
Integer totalColumnWidth = TableTools.getCalculatedTableWidth(treeTable);
this.treeTable.setPreferredSize(new Dimension(totalColumnWidth, treeTableSize.height));
// Notify the tree updaters that the table has been rebuild
this.pagination.notifyTreeUpdaters();
NodeTreeTableModel nttm = (NodeTreeTableModel) this.getTreeTable().getTreeTableModel();
nttm.dumpTreeStructure();
}
/*
* (non-Javadoc)
*
* @see de.stada.pvapp.client.gui.interfaces.FormComponent#drawComponent()
*/
@Override
public void drawComponent() {
// Basic UI settings
this.removeAll();
this.setOpaque(false);
this.buildTreeTable();
// Set selection mode to single selection and opacity
this.treeTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
this.treeTable.setOpaque(false);
this.treeTable.setTableHeader(null);
Color alphaColor = new Color(0, 0, 0, 0);
treeTable.setBackground(alphaColor);
// Build pagination
this.pagination = new RecordPagination();
for (IdRecord record : this.value) {
RecordPanel rp = module.createRecordPanel(record, this.editMode);
rp.setOpaque(false);
pagination.addPanel(rp);
}
// Set the layout as follows
//
// ___________________________________
// | _____________| |
// | | | |
// | | | |
// | | | |
// | | tree | pagination |
// | | | |
// | | | |
// | |_____________| |
// |_______________|___________________|
//
String layout;
String[] layoutParams = this.getParameters("layout");
if (layoutParams != null && layoutParams.length > 0) {
layout = layoutParams[0];
} else {
layout = DEFAULT_LAYOUT;
}
this.setLayout(new FormLayout(LayoutConfig.getLayout(layout), "$vBorder,fill:d:grow,$vBorder"));
// Build split pane
this.add(this.treeTable, new CellConstraints(2, 2));
this.add(this.pagination, new CellConstraints(3, 1, 1, 3));
// add listeners
this.addDefaultListeners();
}
Пока у меня есть заданное значение из базы данных, все работает так, как задумано. Модель, элемент управления и графический интерфейс собраны правильно. Но как только я пытаюсь добавить новую запись в элемент управления, в игру вступает новый фактор:
Как упомянуто выше, родители листа в дереве получены из атрибутов листа. В примере, приведенном в приведенном выше шаблоне XML, родительским элементом лекарственного средства будет характерная особенность лекарства. Однако, когда я создаю новое лекарство, характеристика лекарства, конечно же, неизвестна. Чтобы показать это в дереве, я создаю фиктивных родителей (помеченных -undefined-) для этих новых записей. Как только в процессе редактирования будет определена характеристика персонажа, дерево будет обновлено, и запись будет помещена под соответствующим родителем. Для создания dummy Parents смотрите методы linkToParent и getDummyParent в NodeTreeTableModel.
Теперь к моей проблеме: насколько я могу судить, dummy Parents созданы и правильно связаны при создании новой записи, новая запись добавляется к значению, и соответствующая RecordPanel может быть достигнута, просматривая панели. Тем не менее, они никогда не отображаются в дереве в графическом интерфейсе.
Там должно быть что-то вроде:
-undefined-
new drug
но его просто нет Я понятия не имею, почему это так.
2015-Sep-08: Если я загружаю объект с неопределенным родителем из базы данных, он отображается правильно в графическом интерфейсе. Если я добавлю один во время выполнения, это не так.
У меня есть несколько ключевых слушателей, зарегистрированных на узлах таблицы дерева, которые обновляют дерево, если имя узла изменяется. Это прекрасно работает для существующих узлов. Тем не менее, новые неопределенные узлы не отображаются после тех перерисовок, которые запускаются ключевыми слушателями.
Проблема, кажется, находится в методе buildTreeTable. Я создаю новую таблицу дерева с новой моделью treetable, как вы можете видеть в коде. Но, очевидно, старая структура используется для этой новой таблицы.
Как я могу заставить TreeTable правильно перерисовать, используя структуру новой модели. Я пытался сделать его недействительным (), repaint(), updateUI(). Все бесполезно.