使用 radio/check 个框实现 JTree 节点
Implementing JTree nodes with radio/check boxes
我正在尝试实现一种优雅的树形表示,其中某些类型的节点显示为包含文本、单选按钮和复选框的面板。下面是我目前拥有的图片以及生成它的代码。然而,有一些问题让它感觉很脏,我不确定解决这些问题的最佳方法。
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) buttons/boxes 仅在通过单击节点一次启用编辑后才响应。在此之前,button/box 不会在鼠标悬停时发光。
(2)父节点下每组节点的单选按钮都在一个按钮组中。但是当我 select 一个时,另一个的视觉表示不会更新以反映它已被删除 select,直到我点击它的某个地方 'edit' 它。
(3) 一般来说,这种标准类型的树,其中的节点只是虚拟对象,而不是实际组件,似乎不适合这个,但我想不出一个更好的选择来让我分组这些对象,select 个单独的节点(叶节点或父节点),并让每个叶节点包含检查 boxes/buttons 正常工作。
我乐于接受替代解决方案的建议。
编辑:
尝试使用 Outline,这似乎更接近我想要的,但存在技术问题。我按照示例 here 进行了操作。这是我得到的:
如您所见,按钮显示不正确。这是我的行模型:
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
}
}
好的,所以我终于想出了如何根据 JTable 处理布尔单元格的方式实现这一点。我创建了一个独占布尔选择渲染器来绘制 JRadioButton 并设置树节点以确保保留独占选择。如果编辑了其中一个单元格,我还覆盖了 editingStopped 以更新列中的所有单元格。可能有一些方法可以改进这一点,但它可以满足我的需要。感谢指导。
这是我的代码:
数据集大纲class
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 class
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 class(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
我正在尝试实现一种优雅的树形表示,其中某些类型的节点显示为包含文本、单选按钮和复选框的面板。下面是我目前拥有的图片以及生成它的代码。然而,有一些问题让它感觉很脏,我不确定解决这些问题的最佳方法。
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) buttons/boxes 仅在通过单击节点一次启用编辑后才响应。在此之前,button/box 不会在鼠标悬停时发光。
(2)父节点下每组节点的单选按钮都在一个按钮组中。但是当我 select 一个时,另一个的视觉表示不会更新以反映它已被删除 select,直到我点击它的某个地方 'edit' 它。
(3) 一般来说,这种标准类型的树,其中的节点只是虚拟对象,而不是实际组件,似乎不适合这个,但我想不出一个更好的选择来让我分组这些对象,select 个单独的节点(叶节点或父节点),并让每个叶节点包含检查 boxes/buttons 正常工作。
我乐于接受替代解决方案的建议。
编辑:
尝试使用 Outline,这似乎更接近我想要的,但存在技术问题。我按照示例 here 进行了操作。这是我得到的:
如您所见,按钮显示不正确。这是我的行模型:
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
}
}
好的,所以我终于想出了如何根据 JTable 处理布尔单元格的方式实现这一点。我创建了一个独占布尔选择渲染器来绘制 JRadioButton 并设置树节点以确保保留独占选择。如果编辑了其中一个单元格,我还覆盖了 editingStopped 以更新列中的所有单元格。可能有一些方法可以改进这一点,但它可以满足我的需要。感谢指导。
这是我的代码:
数据集大纲class
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 class
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 class(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