Jtree il rendering con i nodi JCheckBox
Domanda
Sto tentando di modificare il JTree swing standard per mischiarsi nodi con e senza caselle di controllo. Questo è un esempio:
Quando si tenta di selezionare / deselezionare una delle caselle di controllo (l' 'utente 01' nodo in questo esempio), l'albero perde nodi:
I il mio codice è un adattamento di questo esempio: http: // forum. sun.com/thread.jspa?threadID=5321084&start=13 .
Invece di incorporare un JCheckBox in un DefaultMutableTreeNode in questo modo:
new DefaultMutableTreeNode(new CheckBoxNode("Accessibility", true));
ho pensato che aveva più senso per creare un nodo del modello che deriva dalla DefaultMutableTreeNode, che io chiamo JTreeNode. Questa classe imposta automaticamente UserObject del DefaultMutableTreeNode ad un JCheckBox. La proprietà di classe ShowCheckbox viene utilizzata dal TreeCellRenderer per determinare se viene utilizzato il JCheckBox o DefaultTreeCellRenderer. Il JTreeNode è usato in questo modo:
JTreeNode user01 = new JTreeNode("User 01");
user01.setShowCheckBox(true);
user01.setSelected(true);
Credo che il problema è con la classe che implementa l'TreeCellEditor, in particolare nei metodi getCellEditorValue () o getTreeCellEditorComponent (). Ho il sospetto che il problema abbia a che con la getCellEditorValue () restituendo un derivato del DefaultMutableTreeNode, piuttosto che un esempio modello più semplice.
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;
}
Ecco getTreeCellRendererComponent del TreeCellRender () Metodo:
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;
}
Tutti i pensieri sono molto apprezzate.
Soluzione
Sono stato in grado di risolvere il problema.
Ho creato una classe del modello (TreeNodeModel) per contenere i dati rilevanti del nodo: chiave, valore, 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;
}
}
ho creato un'implementazione dell'interfaccia 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;
}
}
La chiave è stata catturando il modello nel metodo getTreeCellEditorComponent () e utilizzando la sua chiave nel metodo getCellEditorValue ().
Costruire l'albero era facile:
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);
Infine, determinare quali nodi in cui verificata era una questione di esaminare insieme di nodi del modello (tramite un pulsante):
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);
}
}
Altri suggerimenti
Ecco alcuni "trucchi" che hanno causato problemi di rendering per me:
-
La TreeCellEditor non può condividere l'istanza TreeCellRenderer passato in
JTree.setCellRenderer()
. SeTreeCellEditor.getTreeCellEditorComponent()
restituisce la stessa istanza comeTreeCellRenderer.getTreeCellRendererComponent()
dell'albero si finirà con problemi di rendering. Potrai provare a modificare una cella, ma il renderer viene eseguito su 5 celle indipendenti. Quando l'editor tenta di recuperare lo stato del JCheckBox otterrà il valore dall'ultima cella reso (in contrasto con l'ultima cella modificata), che è chiaramente sbagliata. -
Usa
TreeCellEditor.getCellEditorValue()
per modificare il valore di una cella, invece di aggiungere ascoltatori del mouse direttamente sulla casella di controllo. Questo metodo solo viene invocata se il valore viene salvato. Se si agisce immediatamente sul eventi del mouse il valore non rotolerà di nuovo inCellEditor.cancelCellEditing()
.