Swing y JTree: Seleccionar hijos al dar click en checkbox

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í:

Edición avanzada con Editor visual de NetBeans
Ejemplo funcionando

  • Cursos de Código Facilito

    Te comparto aquí mis recomendaciones de cursos de Código Facilito. Codigofacilito.com es una plataforma virtual de cursos en idioma exclusivamente español que inició como un canal de YouTube. Opinión de la plataforma En mi opinión la plataforma aún es incipiente y los cursos necesitan más trabajo, ya que es muy común hallarse con clases de…


  • Cursos de LinkedIn Learning

    Te comparto aquí mis recomendaciones de cursos de Linkedin Learning. LinkedIn Learning es una plataforma virtual de cursos propiedad de Microsoft. Hallarás mucho material en inglés, pues prácticamente todos los cursos vienen con subtítulos en castellano y en la parte técnica son muy buenos debido a la adquisición de Lynda.com. En mi opinión la plataforma…


  • Mockito: Snippets de código

    Estos snippets de código para entrevista acerca de mockito espero te sean de mucha utilidad. Claro que también los puedes usar para tu desarrollo en el día a día. Mock básico Si te llegan a mostrar algo como esto es mejor que sospeches, mockito se usa principalmente para pruebas de integración. Si llegas a ver…


javapro.org