扩展SwingX的自定义模型AbstractTreeTableModel:GUI

时间:2015-09-03 15:24:24

标签: java swingx jxtreetable

我要做的是实现一个自定义控件来编辑列表属性(1:n关系)。由于我没有足够的声誉来发布截图,我将尽我所能来描述它。它包含一个记录列表作为值,每个记录由一个RecordPanel表示,它基本上是一个记录的编辑表单。

控制菜单提供添加,删除和复制记录的可能性,以及浏览RecordPanels。仅显示活动的RecordPanel。在活动RecordPanel的左侧,使用我的自定义TreeTableModel(这是我的问题的原因)的JXTreeTable用于显示控件中包含的记录列表。值记录构成模型的叶子,父项可以从相应子项的属性派生。该树可用于直接跳转到特定记录。

为了构建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;
        }

    }

}

最后,以下是用于绘制控件和更新treetable的方法。

    /**
     * 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();

}

只要我从数据库中获得给定值,一切都按预期工作。正确构建模型,控件和GUI。但是当我试图向控件添加新记录时,一个新因素开始起作用:

如上所述,树中叶子的父亲来自叶子的属性。在上面的XML模板中给出的示例中,药物的父母将是药物特征化。然而,当我创造一种新药时,药物特征当然不为人所知。为了在树中显示它,我为这些新记录创建了虚拟父项(标记为-undefined-)。只要在编辑过程中定义了drugCharacterization,就会更新树,并将记录放在正确的父级下。要创建dummyParents,请在NodeTreeTableModel中查看方法linkToParent和getDummyParent。

现在我的问题:据我所知,dummyParents在创建新记录时被正确创建和链接,新记录被添加到值中,并且可以通过浏览面板来访问相应的RecordPanel。但是,它们实际上从未在GUI中的树中显示。

应该有类似的东西:

-undefined-
  new drug

但它不存在。我完全不知道为什么会这样。

2015九月08: 如果我从数据库加载一个带有未定义父对象的对象,它将在GUI中正确显示。如果我在运行时添加一个,则不是。

我在树表节点上注册了几个关键侦听器,如果节点名称发生更改,则会更新树。这适用于现有节点。但是,在由关键侦听器触发的重新绘制后,新的未定义节点不会显示。

问题似乎位于buildTreeTable方法中。我在代码中看到了一个新的树形表,其中包含一个新的treetablemodel。但显然旧结构用于这个新表。

如何使用新模型的结构使TreeTable正确重绘。我试图使(),repaint(),updateUI()无效。一切都徒劳无功。

0 个答案:

没有答案