El JTree es un componente avanzado, al igual que los árboles en los que se basa, son también una estructura de datos avanzada. Recientemente me encontré una duda acerca de como en un componente JTree seleccionar hijos de un nodo mientras navegaba en stackoverflow en español.
Manejo seguro de conversiones
Con un JTree la única forma de manipularlo es mediante un editor, un editor de celda, y en el editor personalizado debí realizar algunos cambios para facilitárme la tarea.
El editor de cada elemento tenía un método inneesariamente público que estaba así:
public static TrueFalseTreeNodeData getQuestionFromValue(Object value) {
if (value instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
Object userObject = node.getUserObject();
if (userObject instanceof TrueFalseTreeNodeData) {
return (TrueFalseTreeNodeData) userObject;
}
}
return null;
}
Lo primero que hice fue pasarlo a java 8 usando la semi mónada Optional, que aparte de permitirnos manejar nulos, nos permite hacer nuestro código con estilo funcional al poder meterle lambdas y referencias estáticas. El método quedó como una variable de la siguiente manera:
final Optional<DefaultMutableTreeNode> mutableNode = Optional.of(value)
.filter(DefaultMutableTreeNode.class::isInstance)
.map(DefaultMutableTreeNode.class::cast);
nodeData = mutableNode
.map(DefaultMutableTreeNode::getUserObject)
.filter(TrueFalseTreeNodeData.class::isInstance)
.map(TrueFalseTreeNodeData.class::cast)
.orElse(null);
Podría haber quedado en una única línea pero lo dejé en dos debido a que necesitaba el nodo para poder modificarlo y a sus hijos.
El editor es siempre un componente, así que como en este caso se usa un checkbox regresaremos el componente de tipo checkbox y simplemente manipularemos el valor para el elemento actual como sigue:
editor = (JCheckBox) (super.getComponent());
final boolean isSelected = nodeData.isBooleanValue();
editor.doClick(); // este es para generar el evento de modificación
editor.setText(nodeData.getValue()); // cuando el editor modifica no lanza eventos
editor.setSelected(isSelected);
updateChilds(mutableNode.get()); // spoiler alert llamamos a nuestro método para modificar los hijos
Pero la dificultad viene con los hijos. Como los componentes JTree son anteriores a los genéricos tendremos que hacer conversiones desde el tipo Object, pues la el desarrollo de la librería Swing fue descontinuado hace tiempo debido a que la industria se movía en otra dirección.
Como vimos hay dos maneras esencialmente de lidiar con el casting de manera segura, una es usando optionals, y la otra es usando instance of, acá usaremos instance of solo para variar un poco.
Object userObject = node.getUserObject();
if (userObject instanceof TrueFalseTreeNodeData) {
// aquí ya es seguro hacer el casting
TrueFalseTreeNodeData uo = (TrueFalseTreeNodeData) userObject;
Primeramente, si el valor antes de darle click al nodo no estaba seleccionado, quiere decir que lo vamos a seleccionar
if (!uo.isBooleanValue()) {
Seleccionar hijos del nodo en un JTree
Para recorrer cada uno de los hijos del nodo deberemos de usar un tradicional ciclo for con índices, debido a que los iteradores y streams no existían en ese momento… por lo que será más natural comparado con armar un stream.
for (int i = 0; i < node.getChildCount(); i++) {
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); // indeguramente asumimos que no va a haber otra cosa que nodos como hijos
Para asegurarnos de que los hijos se actualicen, que es la parte importante hacemos aquí una llamada recursiva pero pasándole el hijo si es que no es un nodo terminal u hoja.
if(!childNode.isLeaf()){
updateChilds(childNode); // llamada recursiva uuuuu
}
finalmente reemplazamos cada nodo del nivel hijo
TrueFalseTreeNodeData childTreeNodeData = (TrueFalseTreeNodeData) childNode.getUserObject();
childTreeNodeData.setBooleanValue(true);
childNode.setUserObject(childTreeNodeData);
El método completo quedará de la siguiente manera:
private void updateChilds(DefaultMutableTreeNode node) {
Object userObject = node.getUserObject();
if (userObject instanceof TrueFalseTreeNodeData) {
TrueFalseTreeNodeData uo = (TrueFalseTreeNodeData) userObject;
if (!uo.isBooleanValue()) {
for (int i = 0; i < node.getChildCount(); i++) {
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i);
if(!childNode.isLeaf()){
updateChilds(childNode);
}
TrueFalseTreeNodeData childTreeNodeData = (TrueFalseTreeNodeData) childNode.getUserObject();
childTreeNodeData.setBooleanValue(true);
childNode.setUserObject(childTreeNodeData);
System.out.println("cn->"+childNode);
}
}
}
}
Mi configuración
IDE: Apache NetBeans IDE 12.1
Java: 13.0.1; Eclipse OpenJ9 VM openj9-0.17.0
Java Runtime: OpenJDK Runtime Environment 13.0.1+9
El código fuente
Puedes hallar el código fuente en mi cuenta GitHub, con todo el código para que funcione. Quisiera destacar que para la clase TreePermisos me encargué de usar Matisse, el editor visual de Netbeans para asegurarme de tener un buen órden cuando creara los componentes debido a su gran capacidad de personalización. El proyecto además cuenta con su fina dotación de lambdas y streams para que veas ejemplos de como usarlos en tus proyectos.
El resultado corriendo se verá así:
