Рендеринг JTree с узлами JCheckBox

Я пытаюсь изменить стандартный Swing JTree для объединения узлов с флажками и без них. Это пример:

alt text

Когда я пытаюсь установить / снять один из флажков (узел «Пользователь 01» в этом примере), дерево теряет узлы:

alt text

Мой код является адаптацией этого примера: http://forums.sun.com/thread.jspa?threadID=5321084&start=13.

Вместо того, чтобы встраивать JCheckBox в DefaultMutableTreeNode следующим образом:

new DefaultMutableTreeNode(new CheckBoxNode("Accessibility", true));

Я подумал, что имеет смысл создать узел модели, производный от DefaultMutableTreeNode, который я называю JTreeNode. Этот класс автоматически устанавливает для UserObject DefaultMutableTreeNode значение JCheckBox. Свойство класса ShowCheckBox используется TreeCellRenderer для определения того, используется ли JCheckBox или DefaultTreeCellRenderer. JTreeNode используется следующим образом:

    JTreeNode user01 = new JTreeNode("User 01");
    user01.setShowCheckBox(true);
    user01.setSelected(true);

Я считаю, что проблема связана с классом, который реализует TreeCellEditor, в частности, в методах getCellEditorValue () или getTreeCellEditorComponent (). Я подозреваю, что проблема связана с тем, что getCellEditorValue () возвращает производную от DefaultMutableTreeNode, а не более простой экземпляр модели.

public Object getCellEditorValue() {

    JCheckBox checkBox = renderer.getCheckBoxRenderer();

    JTreeNode node = new JTreeNode(checkBox.getText());
    node.setShowCheckBox(true);
    node.setSelected(checkBox.isSelected());
    return node;

}

public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {

    Component editor = renderer.getTreeCellRendererComponent(tree, value, true, expanded, leaf, row, true);

    // editor always selected / focused
    ItemListener itemListener = new ItemListener() {
        public void itemStateChanged(ItemEvent itemEvent) {
            if (stopCellEditing()) {
                fireEditingStopped();
            }
        }
    };

    if (editor instanceof JCheckBox) {
        ((JCheckBox) editor).addItemListener(itemListener);
    }

    return editor;

}

Вот метод getTreeCellRendererComponent () TreeCellRender:

public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {

    Component component;

    //if object being passed for rendering is a JTreeNode that should show a JCheckBox, attempt to render it so
    if (((JTreeNode) value).getShowCheckBox()) {

        String stringValue = tree.convertValueToText(value, selected, expanded, leaf, row, false);

        //set default JCheckBox rendering
        checkBoxRenderer.setText(stringValue);
        checkBoxRenderer.setSelected(false);    //not checked
        checkBoxRenderer.setEnabled(tree.isEnabled());

        if (selected) {
            //removed colorization
            //checkBoxRenderer.setForeground(selectionForeground);
            //checkBoxRenderer.setBackground(selectionBackground);
        }
        else {
            checkBoxRenderer.setForeground(textForeground);
            checkBoxRenderer.setBackground(textBackground);
        }

        //DefaultMutableTreeNode
        if ((value != null) && (value instanceof JTreeNode)) {

            //userObject should be a JTreeNode instance
            //DefaultMutableTreeNode
            //Object userObject = ((JTreeNode) value).getUserObject();

            //if it is
            //if (userObject instanceof JTreeNode) {
                //cast as a JTreeNode
                //JTreeNode node = (JTreeNode) userObject;
                JTreeNode node = (JTreeNode) value;

                //set JCheckBox settings to match JTreeNode's settings
                checkBoxRenderer.setText(node.getText());
                checkBoxRenderer.setSelected(node.isSelected());

            //}

        }

        component = checkBoxRenderer;

    }

    //if not, render the default
    else {

        component = defaultRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);

    }

    return component;

}

Любые мысли приветствуются.


person craig    schedule 13.01.2010    source источник
comment
Это чистая догадка, основанная на скриншотах, но может быть проблема с рендерингом. Вы можете попробовать использовать SwingUtilities.updateComponentTreeUI после того, как узлы исчезнут. Если это помогает, значит, дерево просто не перерисовывается должным образом.   -  person Carlos    schedule 13.01.2010
comment
Да ладно, ребята, почему никто не голосует за этот вопрос? 26 просмотров, и никто из вас не думает, что этот парень постарался создать хороший вопрос? Блин, вы все мерзавцы. / rant   -  person Epaga    schedule 20.01.2010


Ответы (2)


Я смог решить проблему.

альтернативный текст

Я создал класс модели (TreeNodeModel) для хранения соответствующих данных узла: ключ, значение, hasCheckBox, isSelected:

public class TreeNodeModel {

    int key;
    String value;
    boolean isSelected=false;
    boolean hasCheckBox=false;

    public TreeNodeModel() {
    }
    public TreeNodeModel(int key, String value) {
        this.key=key;
        this.value = value;
    }
    public TreeNodeModel(int key, String value, boolean hasCheckBox) {
        this.key=key;
        this.value = value;
        this.hasCheckBox = hasCheckBox;
    }
    public TreeNodeModel(int key, String value, boolean hasCheckBox, boolean isSelected) {
        this.key=key;
        this.value = value;
        this.hasCheckBox=hasCheckBox;
        this.isSelected = isSelected;
    }

    public boolean isSelected() {
        return this.isSelected;
    }
    public void setSelected(boolean newValue) {
        this.isSelected = newValue;
    }

    public boolean hasCheckBox() {
        return this.hasCheckBox;
    }
    public void setCheckBox(boolean newValue) {
        this.hasCheckBox=newValue;
    }

    public int getKey() {
        return this.key;
    }
    public void setKey(int newValue) {
        this.key = newValue;
    }

    public String getValue() {
        return this.value;
    }
    public void setValue(String newValue) {
        this.value = newValue;
    }

    @Override
    public String toString() {
        return getClass().getName() + "[" + this.value + "/" + this.isSelected + "]";
//        return this.text;
    }

}

Я создал реализацию интерфейса TreeCellEditor:

public class TreeNodeEditor  extends AbstractCellEditor implements TreeCellEditor {

    private JTree tree;
    private TreeNodeModel editedModel = null;

    TreeNodeRenderer renderer = new TreeNodeRenderer();

    public TreeNodeEditor(JTree tree) {
        this.tree=tree;
    }

    @Override
    public boolean isCellEditable(EventObject event) {

        boolean editable=false;

        if (event instanceof MouseEvent) {

            MouseEvent mouseEvent = (MouseEvent) event;
            TreePath path = tree.getPathForLocation(mouseEvent.getX(),mouseEvent.getY());

            if (path != null) {

                Object node = path.getLastPathComponent();

                if ((node != null) && (node instanceof DefaultMutableTreeNode)) {

                    DefaultMutableTreeNode editedNode = (DefaultMutableTreeNode) node;
                    TreeNodeModel model = (TreeNodeModel) editedNode.getUserObject();
                    editable = model.hasCheckBox;

                }   //if (node)
            }   //if (path)
        }   //if (MouseEvent)

        return editable;

    }

    public Object getCellEditorValue() {

        JCheckBox checkbox = renderer.getCheckBoxRenderer();

        TreeNodeModel model = new TreeNodeModel(editedModel.getKey(), checkbox.getText(), true, checkbox.isSelected());
        return model;

    }

    public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {

        if (((DefaultMutableTreeNode) value).getUserObject() instanceof TreeNodeModel) {
            editedModel = (TreeNodeModel) ((DefaultMutableTreeNode) value).getUserObject();
        }

        Component editor = renderer.getTreeCellRendererComponent(tree, value, true, expanded, leaf, row, true);

        // editor always selected / focused
        ItemListener itemListener = new ItemListener() {
            public void itemStateChanged(ItemEvent itemEvent) {
                if (stopCellEditing())
                    fireEditingStopped();
            }
        };

        if (editor instanceof JCheckBox) {
            ((JCheckBox) editor).addItemListener(itemListener);
        }

        return editor;
    }

}

Ключ заключался в захвате модели в методе getTreeCellEditorComponent () и использовании ее ключа в методе getCellEditorValue ().

Строить дерево было легко:

DefaultMutableTreeNode server = new DefaultMutableTreeNode(new TreeNodeModel(0,"Server 01", false));

DefaultMutableTreeNode userFolder = new DefaultMutableTreeNode(new TreeNodeModel(1, "User Folders", false));
server.add(userFolder);

DefaultMutableTreeNode user01 =  new DefaultMutableTreeNode(new TreeNodeModel(2, "User 01", true, true));
userFolder.add(user01);

DefaultMutableTreeNode clientA = new DefaultMutableTreeNode(new TreeNodeModel(3, "Client A", true, true));
user01.add(clientA);

DefaultMutableTreeNode clientB = new DefaultMutableTreeNode(new TreeNodeModel(4, "Client B", true, true));
user01.add(clientB);

DefaultMutableTreeNode publicFolder = new DefaultMutableTreeNode(new TreeNodeModel(5, "Public Folders", false));
server.add(publicFolder);

DefaultMutableTreeNode clientC = new DefaultMutableTreeNode(new TreeNodeModel(6, "Client C", true));
publicFolder.add(clientC);
        Tree_Nodes.setCellRenderer(new TreeNodeRenderer());
        Tree_Nodes.setCellEditor(new TreeNodeEditor(Tree_Nodes));
Tree_Nodes.setModel(new DefaultTreeModel(server);

Наконец, определение того, какие узлы были проверены, заключалось в изучении коллекции узлов модели (с помощью кнопки):

private Map<Integer, String> checked = new HashMap<Integer, String>();

private void Button_CheckedActionPerformed(java.awt.event.ActionEvent evt) {

    DefaultTableModel tableModel = ((DefaultTableModel) Table_Nodes.getModel());
    tableModel.getDataVector().removeAllElements();
    tableModel.fireTableDataChanged();

    checked.clear();

    DefaultTreeModel treeModel = (DefaultTreeModel) Tree_Nodes.getModel();
    DefaultMutableTreeNode root = (DefaultMutableTreeNode) treeModel.getRoot();

    getChildNodes(root);

    for (Iterator it=checked.entrySet().iterator(); it.hasNext(); ) {
        Map.Entry entry = (Map.Entry)it.next();
        tableModel.addRow(new Object[] {entry.getKey(), entry.getValue()});
    }

    Button_Checked.requestFocus();


}

private void getChildNodes(DefaultMutableTreeNode parentNode) {

    for (Enumeration e=parentNode.children(); e.hasMoreElements();) {

        DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) e.nextElement();
        TreeNodeModel model = (TreeNodeModel) childNode.getUserObject();

        if (model.hasCheckBox && model.isSelected()) {
            checked.put(model.getKey(), model.getValue());
        }

        //recurse
        if (childNode.getChildCount()>0) getChildNodes(childNode);

    }

}
person craig    schedule 20.01.2010
comment
+1 Для добавления скриншота, потому что я делаю то же самое, когда это возможно (я хотел бы +2 для использования OSX) - person OscarRyz; 21.01.2010
comment
Я хотел бы улучшить это, если JCheckBox сигнализирует о соответствующем механизме в JTree, чтобы иметь возможность перечислять выбранные узлы с помощью JTree.getSelectionPaths (). - person craig; 21.01.2010
comment
вы создали TreeNodeRenderer (), тогда где этот класс этого неработающего чувака. - person Kishan Bheemajiyani; 10.02.2014

Вот несколько "ловушек", которые вызвали у меня проблемы с рендерингом:

  1. TreeCellEditor не может совместно использовать экземпляр TreeCellRenderer, переданный в JTree.setCellRenderer(). Если TreeCellEditor.getTreeCellEditorComponent() возвращает тот же экземпляр, что и TreeCellRenderer.getTreeCellRendererComponent() дерева, вы столкнетесь с проблемами рендеринга. Вы попытаетесь отредактировать одну ячейку, но средство визуализации запускается для 5 несвязанных ячеек. Когда редактор пытается получить состояние JCheckBox, он получит значение из последней отрисованной ячейки (в отличие от последней отредактированной ячейки), что явно неверно.

  2. Используйте TreeCellEditor.getCellEditorValue() для изменения значения ячейки вместо добавления слушателей мыши непосредственно в поле для флажка. Этот метод вызывается только в случае сохранения значения. Если вы действуете немедленно на события мыши, значение не откатится на CellEditor.cancelCellEditing().

person Gili    schedule 12.12.2011