2015-02-19 3 views
0

Я пытаюсь достичь элегантного древовидного представления, в котором определенные типы узлов отображаются в виде панелей, содержащих текст, переключатель и флажок. Ниже приведено изображение того, что у меня есть, и код, который его генерирует. Однако есть несколько проблем, которые только заставляют его чувствовать себя грязным, и я не уверен, что лучший способ обойти их.Реализация узлов JTree с радио/флажками

Example Tree

public class DatasetTree extends JTree { 

    public DatasetTree(String name) { 
    super(new DatasetTreeModel(name)); 
    getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 
    DatasetTreeCellRenderer renderer = new DatasetTreeCellRenderer(); 
    renderer.setOpenIcon(null); 
    renderer.setClosedIcon(null); 
    renderer.setLeafIcon(null); 
    setCellRenderer(renderer); 
    setEditable(true); 
    PanelCellEditor editor = new PanelCellEditor(this, renderer); 
    setCellEditor(editor); 
    setShowsRootHandles(true); 
    setRootVisible(false); 
    } 

    public DatasetTreeModel getDatasetModel() { 
    return (DatasetTreeModel) treeModel; 
    } 

    public static class DatasetTreeCellRenderer extends DefaultTreeCellRenderer { 

    public DatasetTreeCellRenderer() { 

    } 

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

     if ((value != null) && (value instanceof DatasetHandle)) { 
     DatasetHandle h = (DatasetHandle) value; 
     DatasetCellPanel line = new DatasetCellPanel(h); 
     if (sel) { 
      line.setBackground(getBackgroundSelectionColor()); 
      line.setForeground(getTextSelectionColor()); 
     } else { 
      line.setBackground(getBackgroundNonSelectionColor()); 
      line.setForeground(getTextNonSelectionColor()); 
     } 
     return line; 
     } 
     return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); 
    } 
    } 

    public static class DatasetCellPanel extends JPanel { 

    private final JLabel lblName, lblType, lblom, lbldata, lblimages, lblspectra; 
    private boolean observable; 
    private boolean orientable; 

    private JRadioButton omButton; 
    private JCheckBox dataSelectBox; 

    /** 
    * Create the panel. 
    */ 
    public DatasetCellPanel(DatasetHandle h) { 
     super(); 
     setBackground(Color.WHITE); 
     FileData fd = h.getFileData(); 
     String name = fd.getFileName(); 
     boolean observable = (fd instanceof ObservableData); 
     boolean orientable = (fd instanceof Orientable); 
     String typeName = fd.getClass().getSimpleName(); 
     lblName = new JLabel(""); 
     lblType = new JLabel(""); 
     lblom = new JLabel("[om]"); 
     lbldata = new JLabel("[data]"); 
     lblimages = new JLabel("[images]"); 
     lblspectra = new JLabel("[spectra]"); 

     JRadioButton omButton = new JRadioButton(""); 
     JCheckBox dataSelectBox = new JCheckBox(""); 

     setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); 

     lblName.setText(name); 
     lblName.setMinimumSize(new Dimension(100, 8)); 
     lblName.setPreferredSize(new Dimension(100, 16)); 
     lblName.setMaximumSize(new Dimension(100, 64)); 
     add(lblName); 
     add(Box.createRigidArea(new Dimension(5, 0))); 

     lblType.setText(typeName); 
     lblType.setMinimumSize(new Dimension(100, 8)); 
     lblType.setPreferredSize(new Dimension(100, 16)); 
     lblType.setMaximumSize(new Dimension(100, 64)); 
     add(lblType); 
     add(Box.createRigidArea(new Dimension(5, 0))); 

     if (orientable) { 
     omButton = h.getLatticeButton(); 
     } else { 
     lblom.setForeground(UIManager.getColor("Label.disabledForeground")); 
     omButton.setEnabled(false); 
     } 
     add(lblom); 
     add(omButton); 
     add(Box.createRigidArea(new Dimension(5, 0))); 

     if (observable) { 
     dataSelectBox = h.getDataButton(); 
     } else { 
     lbldata.setForeground(UIManager.getColor("Label.disabledForeground")); 
     dataSelectBox.setEnabled(false); 
     } 
     add(lbldata); 
     add(dataSelectBox); 
     add(Box.createRigidArea(new Dimension(5, 0))); 

     add(lblimages); 
     add(Box.createRigidArea(new Dimension(5, 0))); 
     add(lblspectra); 

    } 

    public void addListeners(EventListener l) { 

    } 

    @Override 
    public void setForeground(Color fg) { 
     if (lblName != null) { 
     lblName.setForeground(fg); 
     } 
     if (lblType != null) { 
     lblType.setForeground(fg); 
     } 
     if (observable && (lbldata != null)) { 
     lbldata.setForeground(fg); 
     } 
     if (orientable && (lblom != null)) { 
     lblom.setForeground(fg); 
     } 
     if (lblimages != null) { 
     lblimages.setForeground(fg); 
     } 
     if (lblspectra != null) { 
     lblspectra.setForeground(fg); 
     } 
     super.setForeground(fg); 
    } 

    @Override 
    public void setBackground(Color bg) { 
     if (omButton != null) { 
     omButton.setBackground(bg); 
     } 
     if (dataSelectBox != null) { 
     dataSelectBox.setBackground(bg); 
     } 
     super.setBackground(bg); 
    } 

    } 

    public static class PanelCellEditor extends AbstractCellEditor implements TreeCellEditor { 

    Object value; 
    private JTree tree; 
    private DefaultTreeCellRenderer renderer; 

    public PanelCellEditor(JTree tree, DefaultTreeCellRenderer renderer) { 
     this.tree = tree; 
     this.renderer = renderer; 
    } 

    @Override 
    public Object getCellEditorValue() { 
     return value; 
    } 

    // FIXME: Redraw all in group when one is edited 
    @Override 
    public Component getTreeCellEditorComponent(JTree tree, Object value, boolean sel, 
     boolean expanded, boolean leaf, int row) { 
     this.value = value; 
     if ((value != null) && (value instanceof DatasetHandle)) { 
     DatasetHandle h = (DatasetHandle) value; 
     DatasetCellPanel line = new DatasetCellPanel(h); 
     if (sel) { 
      line.setBackground(renderer.getBackgroundSelectionColor()); 
      line.setForeground(renderer.getTextSelectionColor()); 
     } else { 
      line.setBackground(renderer.getBackgroundNonSelectionColor()); 
      line.setForeground(renderer.getTextNonSelectionColor()); 
     } 
     return line; 
     } 
     return renderer.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, false); 
    } 
    } 

} 

(1) Кнопки/коробки только после того, как реагировать, если редактирование разрешено, нажав на узел один раз. До этого кнопка/окно не светится при наведении мыши.

(2) Переключатели для каждой группы узлов под родителем находятся в одной группе кнопок. Но когда я выбираю один, визуальное представление другого не обновляется, чтобы отразить, что оно было отменено, пока я не щелкнул где-нибудь в нем, чтобы «отредактировать» его.

(3) Как правило, этот стандартный тип дерева, где узлы являются просто пустыми объектами, а не фактическими компонентами, кажется для этого неприемлемым, но я не могу придумать лучшую альтернативу, которая позволяет мне группировать эти объекты, выберите отдельные узлы (листья или родители), и каждый лист содержит флажки/кнопки, которые работают правильно.

Я открыт для предложений об альтернативных решениях.

EDIT:

Пробовал с помощью Outline, который, кажется, ближе к тому, что я хочу, но возникли технические проблемы. Я следовал примеру here. Это то, что я получаю:

Outline attempt 1

Как вы можете видеть, кнопки не отображаются должным образом. Вот мой RowModel:

public class DatasetOutlineRowModel implements RowModel { 

    @Override 
    public Class getColumnClass(int column) { 
    switch (column) { 
     case 0: 
     return JRadioButton.class; 
     case 1: 
     return JCheckBox.class; 
     case 2: 
     return String.class; 
     case 3: 
     return String.class; 
     default: 
     assert false; 
    } 
    return null; 
    } 

    @Override 
    public int getColumnCount() { 
    return 4; 
    } 

    @Override 
    public String getColumnName(int column) { 
    switch (column) { 
     case 0: 
     return "OM"; 
     case 1: 
     return "Data"; 
     case 2: 
     return "Images"; 
     case 3: 
     return "Spectra"; 
     default: 
     assert false; 
    } 
    return null; 
    } 

    @Override 
    public Object getValueFor(Object node, int column) { 
    if (!(node instanceof DatasetHandle)) 
     return null; 
    DatasetHandle handle = (DatasetHandle) node; 
    switch (column) { 
     case 0: 
     return handle.getLatticeButton(); 
     case 1: 
     return handle.getDataButton(); 
     case 2: 
     return ""; 
     case 3: 
     return ""; 
     default: 
     assert false; 
    } 
    return null; 
    } 

    @Override 
    public boolean isCellEditable(Object arg0, int arg1) { 
    return false; 
    } 

    @Override 
    public void setValueFor(Object arg0, int arg1, Object arg2) { 
    // TODO Auto-generated method stub 

    } 

} 
+2

Для примера [http://codereview.stackexchange.com/a/4447/6692]. – trashgod

+1

@trashgod - Это похоже на гораздо лучшее решение! Спасибо! Я попробую. – ipetrik

+0

@trashgod - Мне очень нравится, как выглядит ваш контур. :-) У меня с некоторыми техническими проблемами (см. Править выше). Мне также интересно, как вы избавились от уродливых линий сетки. – ipetrik

ответ

1

ОК, так что я, наконец, понял, как добиться этого основываясь на пути JTable обрабатывает логические ячейки. Я создал эксклюзивный булевский рендеринг выделения, чтобы нарисовать JRadioButton и настроить узел дерева, чтобы обеспечить исключительный выбор. Я также переопределил editStopped, чтобы обновить все ячейки в столбце, если была отредактирована одна из ячеек. Вероятно, есть способы улучшить это, но он работает для того, что мне нужно. Спасибо за руководство.

enter image description here

Вот мой код:

DatasetOutline класс

public class DatasetOutline extends Outline { 

    public DatasetOutline(DatasetTreeModel mdl) { 
    setRenderDataProvider(new DatasetRenderProvider()); 
    setRootVisible(false); 
    setShowGrid(false); 
    setIntercellSpacing(new Dimension(0, 0)); 
    setModel(DefaultOutlineModel.createOutlineModel(mdl, new DatasetOutlineRowModel(), true, 
     "Dataset")); 
    getColumnModel().getColumn(1).setCellRenderer(new ExclusiveBooleanRenderer()); 
    getColumnModel().getColumn(1).setCellEditor(new ExclusiveBooleanEditor()); 
    // [snip] 
    getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 
    } 

    // Update the entire column of the conditional boolean if one is changed 
    @Override 
    public void editingStopped(ChangeEvent e) { 
    super.editingStopped(e); 
    if (e.getSource() instanceof ExclusiveBooleanEditor) { 
     tableChanged(new TableModelEvent(getModel(), 0, getRowCount(), 1, TableModelEvent.UPDATE)); 
    } 
    } 
} 

класс DatasetOutlineRowModel

public class DatasetOutlineRowModel implements RowModel { 

    @Override 
    public Class getColumnClass(int column) { 
    switch (column) { 
     case 0: 
     return Boolean.class; 
     case 1: 
     return Boolean.class; 
     case 2: 
     return String.class; 
     case 3: 
     return String.class; 
     default: 
     assert false; 
    } 
    return null; 
    } 

// [snip] 

    @Override 
    public Object getValueFor(Object node, int column) { 
    if (!(node instanceof DatasetHandle)) 
     return null; 
    DatasetHandle handle = (DatasetHandle) node; 
    switch (column) { 
     case 0: 
     return handle.isLatticeSelected(); 
     case 1: 
     return handle.isSelected(); 
     case 2: 
     return ""; 
     case 3: 
     return ""; 
     default: 
     assert false; 
    } 
    return null; 
    } 

    @Override 
    public boolean isCellEditable(Object node, int column) { 
    if (column > 1) 
     return false; 
    if (node instanceof DatasetHandle) 
     return true; 
    return false; 
    } 

    @Override 
    public void setValueFor(Object node, int column, Object value) { 
    if (!(node instanceof DatasetHandle)) 
     return; 
    DatasetHandle handle = (DatasetHandle) node; 
    if (column == 0) { 
     handle.setLatticeSelected((Boolean) value); 
    } 
    if (column == 1) { 
     handle.setSelected((Boolean) value); 
    } 

    } 

} 

ExclusiveBooleanEditor класс (модифицированная копия DefaultCellRenderer)

public class ExclusiveBooleanEditor extends AbstractCellEditor implements TableCellEditor, 
    TreeCellEditor { 

    // 
    // Instance Variables 
    // 

    /** The Swing component being edited. */ 
    protected JComponent editorComponent; 
    /** 
    * The delegate class which handles all methods sent from the <code>CellEditor</code>. 
    */ 
    protected EditorDelegate delegate; 
    /** 
    * An integer specifying the number of clicks needed to start editing. Even if 
    * <code>clickCountToStart</code> is defined as zero, it will not initiate until a click occurs. 
    */ 
    protected int clickCountToStart = 1; 

    // 
    // Constructors 
    // 

    public ExclusiveBooleanEditor() { 
    this(new JRadioButton()); 
    JRadioButton radioButton = (JRadioButton) getComponent(); 
    radioButton.setHorizontalAlignment(JRadioButton.CENTER); 
    } 

    public ExclusiveBooleanEditor(final JRadioButton radioButton) { 
    editorComponent = radioButton; 
    delegate = new EditorDelegate() { 
     // FIXME replace 
     @Override 
     public void setValue(Object value) { 
     boolean selected = false; 
     if (value instanceof Boolean) { 
      selected = ((Boolean) value).booleanValue(); 
     } else if (value instanceof String) { 
      selected = value.equals("true"); 
     } 
     radioButton.setSelected(selected); 
     } 

     @Override 
     public Object getCellEditorValue() { 
     return Boolean.valueOf(radioButton.isSelected()); 
     } 
    }; 
    radioButton.addActionListener(delegate); 
    radioButton.setRequestFocusEnabled(false); 
    } 

    /** 
    * Returns a reference to the editor component. 
    * 
    * @return the editor <code>Component</code> 
    */ 
    public Component getComponent() { 
    return editorComponent; 
    } 

    // 
    // Modifying 
    // 

    /** 
    * Specifies the number of clicks needed to start editing. 
    * 
    * @param count an int specifying the number of clicks needed to start editing 
    * @see #getClickCountToStart 
    */ 
    public void setClickCountToStart(int count) { 
    clickCountToStart = count; 
    } 

    /** 
    * Returns the number of clicks needed to start editing. 
    * 
    * @return the number of clicks needed to start editing 
    */ 
    public int getClickCountToStart() { 
    return clickCountToStart; 
    } 

    // 
    // Override the implementations of the superclass, forwarding all methods 
    // from the CellEditor interface to our delegate. 
    // 

    /** 
    * Forwards the message from the <code>CellEditor</code> to the <code>delegate</code>. 
    * 
    * @see EditorDelegate#getCellEditorValue 
    */ 
    @Override 
    public Object getCellEditorValue() { 
    return delegate.getCellEditorValue(); 
    } 

    /** 
    * Forwards the message from the <code>CellEditor</code> to the <code>delegate</code>. 
    * 
    * @see EditorDelegate#isCellEditable(EventObject) 
    */ 
    @Override 
    public boolean isCellEditable(EventObject anEvent) { 
    return delegate.isCellEditable(anEvent); 
    } 

    /** 
    * Forwards the message from the <code>CellEditor</code> to the <code>delegate</code>. 
    * 
    * @see EditorDelegate#shouldSelectCell(EventObject) 
    */ 
    @Override 
    public boolean shouldSelectCell(EventObject anEvent) { 
    return delegate.shouldSelectCell(anEvent); 
    } 

    /** 
    * Forwards the message from the <code>CellEditor</code> to the <code>delegate</code>. 
    * 
    * @see EditorDelegate#stopCellEditing 
    */ 
    @Override 
    public boolean stopCellEditing() { 
    return delegate.stopCellEditing(); 
    } 

    /** 
    * Forwards the message from the <code>CellEditor</code> to the <code>delegate</code>. 
    * 
    * @see EditorDelegate#cancelCellEditing 
    */ 
    @Override 
    public void cancelCellEditing() { 
    delegate.cancelCellEditing(); 
    } 

    // 
    // Implementing the TreeCellEditor Interface 
    // 

    /** Implements the <code>TreeCellEditor</code> interface. */ 
    @Override 
    public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, 
     boolean expanded, boolean leaf, int row) { 
    String stringValue = tree.convertValueToText(value, isSelected, expanded, leaf, row, false); 

    delegate.setValue(stringValue); 
    return editorComponent; 
    } 

    // 
    // Implementing the CellEditor Interface 
    // 
    /** Implements the <code>TableCellEditor</code> interface. */ 
    @Override 
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, 
     int row, int column) { 
    delegate.setValue(value); 
    if ((editorComponent instanceof JCheckBox) || (editorComponent instanceof JRadioButton)) { 
     // in order to avoid a "flashing" effect when clicking a checkbox 
     // in a table, it is important for the editor to have as a border 
     // the same border that the renderer has, and have as the background 
     // the same color as the renderer has. This is primarily only 
     // needed for JCheckBox since this editor doesn't fill all the 
     // visual space of the table cell, unlike a text field. 
     TableCellRenderer renderer = table.getCellRenderer(row, column); 
     Component c = 
      renderer.getTableCellRendererComponent(table, value, isSelected, true, row, column); 
     if (c != null) { 
     editorComponent.setOpaque(true); 
     editorComponent.setBackground(c.getBackground()); 
     if (c instanceof JComponent) { 
      editorComponent.setBorder(((JComponent) c).getBorder()); 
     } 
     } else { 
     editorComponent.setOpaque(false); 
     } 
    } 
    return editorComponent; 
    } 


    // 
    // Protected EditorDelegate class 
    // 

    /** 
    * The protected <code>EditorDelegate</code> class. 
    */ 
    protected class EditorDelegate implements ActionListener, ItemListener, Serializable { 

    /** The value of this cell. */ 
    protected Object value; 

    /** 
    * Returns the value of this cell. 
    * 
    * @return the value of this cell 
    */ 
    public Object getCellEditorValue() { 
     return value; 
    } 

    /** 
    * Sets the value of this cell. 
    * 
    * @param value the new value of this cell 
    */ 
    public void setValue(Object value) { 
     this.value = value; 
    } 

    /** 
    * Returns true if <code>anEvent</code> is <b>not</b> a <code>MouseEvent</code>. Otherwise, it 
    * returns true if the necessary number of clicks have occurred, and returns false otherwise. 
    * 
    * @param anEvent the event 
    * @return true if cell is ready for editing, false otherwise 
    * @see #setClickCountToStart 
    * @see #shouldSelectCell 
    */ 
    public boolean isCellEditable(EventObject anEvent) { 
     if (anEvent instanceof MouseEvent) { 
     return ((MouseEvent) anEvent).getClickCount() >= clickCountToStart; 
     } 
     return true; 
    } 

    /** 
    * Returns true to indicate that the editing cell may be selected. 
    * 
    * @param anEvent the event 
    * @return true 
    * @see #isCellEditable 
    */ 
    public boolean shouldSelectCell(EventObject anEvent) { 
     return true; 
    } 

    /** 
    * Returns true to indicate that editing has begun. 
    * 
    * @param anEvent the event 
    */ 
    public boolean startCellEditing(EventObject anEvent) { 
     return true; 
    } 

    /** 
    * Stops editing and returns true to indicate that editing has stopped. This method calls 
    * <code>fireEditingStopped</code>. 
    * 
    * @return true 
    */ 
    public boolean stopCellEditing() { 
     fireEditingStopped(); 
     return true; 
    } 

    /** 
    * Cancels editing. This method calls <code>fireEditingCanceled</code>. 
    */ 
    public void cancelCellEditing() { 
     fireEditingCanceled(); 
    } 

    /** 
    * When an action is performed, editing is ended. 
    * 
    * @param e the action event 
    * @see #stopCellEditing 
    */ 
    @Override 
    public void actionPerformed(ActionEvent e) { 
     ExclusiveBooleanEditor.this.stopCellEditing(); 
    } 

    /** 
    * When an item's state changes, editing is ended. 
    * 
    * @param e the action event 
    * @see #stopCellEditing 
    */ 
    @Override 
    public void itemStateChanged(ItemEvent e) { 
     ExclusiveBooleanEditor.this.stopCellEditing(); 
    } 
    } 

    public static class ExclusiveBooleanRenderer extends JRadioButton implements TableCellRenderer, 
     UIResource { 
    private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1); 
    private static final JLabel emptyLabel = new JLabel(""); 

    public ExclusiveBooleanRenderer() { 
     super(); 
     setHorizontalAlignment(JRadioButton.CENTER); 
     setBorderPainted(true); 
    } 

    @Override 
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, 
     boolean hasFocus, int row, int column) { 

     // Don't draw if it is not changeable 
     if (value == null) { 
     if (isSelected) { 
      emptyLabel.setForeground(table.getSelectionForeground()); 
      emptyLabel.setBackground(table.getSelectionBackground()); 
     } else { 
      emptyLabel.setForeground(table.getForeground()); 
      emptyLabel.setBackground(table.getBackground()); 
     } 

     return emptyLabel; 
     } 
     if (isSelected) { 
     setForeground(table.getSelectionForeground()); 
     super.setBackground(table.getSelectionBackground()); 
     } else { 
     setForeground(table.getForeground()); 
     setBackground(table.getBackground()); 
     } 
     setSelected((value != null && ((Boolean) value).booleanValue())); 

     if (hasFocus) { 
     setBorder(UIManager.getBorder("Table.focusCellHighlightBorder")); 
     } else { 
     setBorder(noFocusBorder); 
     } 

     return this; 
    } 
    } 

} // End of class JCellEditor 
Смежные вопросы