miércoles, 12 de octubre de 2011

How To: Create Dinamic menu from XML file Java // Crear menu dinamico desde XML en Java

Hola a todos, ahora lo que vamos a ver es como generar un menú y sus opciones hijas desde un xml, esto desde mi humilde punto de vista es bastante útil, dado que estar generando el menú desde código en java lleva mucho tiempo, y si queremos agregar otra opción al menú, se tienen que hacer demasiados cambios al código; con esto no quiero decir que con esta forma que veremos a continuación no se tiene que cambiar nada cuando se quiera cambiar la estructura del código, pero si los cambios son mínimos y el esfuerzo también.

Para empezar veremos el resultado final (para que se animen mas a seguir leyendo ^^).
y la otra opción de menú:


Como podemos ver en la segunda imagen, el menú puede tener tantas opciones anidadas como nosotros deseemos.
Ahora veremos la estructura en el xml:

<?xml version="1.0" encoding="UTF-8"?>
<menu itemLabel="McCuboMenu" key="null">
    <node itemLabel="File" key="file" icon="src/CustomJMenuXML/resources/default.png">
        <leaf itemLabel="Save" key="file.save" icon="src/CustomJMenuXML/resources/save_as.png"/>
        <node itemLabel="Save As..." key="file.save.as" icon="src/CustomJMenuXML/resources/save_as.png" >
            <leaf itemLabel="PDF" key="file.save.as.pdf" icon="src/CustomJMenuXML/resources/pdf_format.png" />
            <leaf itemLabel="HTML" key="file.save.as.html" icon="src/CustomJMenuXML/resources/html_format.png" />
            <leaf itemLabel="PPT" key="file.save.as.ppt" icon="src/CustomJMenuXML/resources/ppt_format.png" />
            <leaf itemLabel="XLS" key="file.save.as.xls" icon="src/CustomJMenuXML/resources/xls_format.png" />
            <node itemLabel="Other" key="file.format.other" icon="src/CustomJMenuXML/resources/default.png">
                <leaf itemLabel="LXDE" key="file.format.other.lxde" icon="src/CustomJMenuXML/resources/default.png" />
                <node itemLabel="Other" key="file.format.other" icon="src/CustomJMenuXML/resources/default.png">
                    <leaf itemLabel="Other_Other" key="file.format.other.other_other" icon="src/CustomJMenuXML/resources/default.png" />
                </node>
            </node>
        </node>
    </node>
    <node itemLabel="O.S and Apps" key="os.app" icon="src/CustomJMenuXML/resources/windows.png">
        <leaf itemLabel="Compiz Fusion" key="os.app.fusion" icon="src/CustomJMenuXML/resources/fusion.png" />
        <leaf itemLabel="Chrome" key="os.app.chrome" icon="src/CustomJMenuXML/resources/chrome.png" />
        <node itemLabel="Debian" key="os.app.debian" icon="src/CustomJMenuXML/resources/debian.png" >
            <leaf itemLabel="Stable" key="os.app.debian.stable" icon="src/CustomJMenuXML/resources/default.png" />
            <leaf itemLabel="Testing" key="os.app.debian.testing" icon="src/CustomJMenuXML/resources/default.png" />
        </node>
    </node>
</menu>

ese archivo lo guardaremos como menuDefinition.xml, como pueden apreciar ese xml no tiene DTD , por motivos de tiempo x). pero seria bueno que ustedes si lo aplicaran x).

La estructura del xml es sencilla, los elementos node, son aquellos elementos que tienen mas elementos dentro (osea son un menú contenedor) y los elementos leaf son los elementos de ultimo nivel.
Cada elemento de la definición del menú tiene los siguientes atributos:
  • itemLabel: texto que mostrara dicho elemento en el menú.
  • key: identificador del elemento (trataremos que este sea único)
  • icon: icono del elemento.
La primera clase que crearemos será ImageUtils.java dicha clase tendrá un método estático el cual nos retornara una imagen re dimensionada para poder incluirla en el elemento del menú.

package CustomJMenuXML.source;

import java.awt.Image;
import javax.swing.ImageIcon;

/**
 *
 * @author McCubo
 */
public class ImageUtils {

    /**
     *
     * @param URL direccion de la imagen dentro del proyecto
     * @return una Imagen redimensionada a 16 x 16
     */
    public static ImageIcon getScaledImage(String URL){
        ImageIcon sourceIcon = new ImageIcon(URL);
        ImageIcon scaledIcon = new ImageIcon(sourceIcon.getImage().getScaledInstance(16, 16, Image.SCALE_DEFAULT));
        return scaledIcon;
    }

}

ahora crearemos una clase que extienda de javax.swing.JMenu y la nombraremos CustomJMenu.java.

package CustomJMenuXML.source;

import javax.swing.ImageIcon;

/**
 *
 * @author McCubo
 */
public class CustomJMenu extends javax.swing.JMenu{

    private String label;
    private ImageIcon icon;

    public CustomJMenu(String label, ImageIcon icon) {
        super(label);
        this.label = label;
        this.icon = icon;
        setIcon(this.icon);
    }

}
En realidad no era necesario crear esta clase, el único motivo por el cual la he creado es para crearle un constructor al JMenu en el cual reciba como parámetro el icono del elemento y no tener que estar llamando al método setIcon(ImageIcon) cada vez que se crea un nuevo elemento de menú. pero bueno, ustedes valoraran si como se les sea mas útil ^^

Ahora, no estoy del todo seguro que esta sea la mejor forma de agregarle los ActionListener a cada uno de los elementos del menú, así que cualquier observación o sugerencia para realizar esta tarea de una forma mas efectiva, lo agradecería. igual encontré formas que eran menos ... no se, estéticas o eficientes x) así que mejor he decidido implementar la forma que se me ocurrió x).
Crearemos una clase que extienda de javax.swing.JMenuItem y que implemente java.awt.event.ActionListener esa clase la llamaremos CustomJMenuItem.java.
package CustomJMenuXML.source;

import java.awt.event.ActionListener;
import javax.swing.ImageIcon;

/**
 *
 * @author McCubo
 */
public class CustomJMenuItem extends javax.swing.JMenuItem implements java.awt.event.ActionListener {

    private String key;

    public CustomJMenuItem(String label, ImageIcon icon, String key) {
        super(label, icon);
        this.key = key;
        addActionListener(getActionListener());
    }

    private ActionListener getActionListener() {
        return this;
    }

    public void actionPerformed(java.awt.event.ActionEvent e) {
        if (key.equals("os.app.chrome")) {
            System.out.println("os.app.chrome");
        }
        /*
         * aqui todos los IF para cada una de las opciones del menu, por eso
         * digo que no es muy eficiente este metodo, muchos IF, pero ni modo xD
         */
    }
}

y por ultimo, creamos lo que será la barra de menú de nuestra aplicación. extendemos una clase de javax.swing.JMenuBar y la llamamos CustomXMLMenu.java. dicha clase deberá quedarnos así:

package CustomJMenuXML.source;

import java.util.ArrayList;
import java.util.List;
import javax.swing.JMenuBar;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author McCubo
 * @version 0.5.1
 */
public class CustomXMLMenu extends JMenuBar {

    private Document document;

    public CustomXMLMenu() {
        try {
            Document doc = null;
            DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
            doc = docBuilder.parse(getClass().getResourceAsStream("menuDefinition.xml"));
            doc.normalize();
            this.document = doc;
            Element root = getRoot();
            for (Element element : getChildList(root)) {
                CustomJMenu customJMenu = new CustomJMenu(element.getAttribute("itemLabel"), ImageUtils.getScaledImage(element.getAttribute("icon")));
                addChildsToNode(customJMenu, getChildList(element));
                add(customJMenu);
            }
        } catch (Exception ex) {
            System.out.println(ex.toString());
        }
    }

    private void addChildsToNode(CustomJMenu menu, List<element> childList) {
        for (Element element : childList) {
            if (!getChildList(element).isEmpty()) {
                CustomJMenu customJMenu = new CustomJMenu(element.getAttribute("itemLabel"), ImageUtils.getScaledImage(element.getAttribute("icon")));
                addChildsToNode(customJMenu, getChildList(element));
                menu.add(customJMenu);
            } else {
                String label = element.getAttribute("itemLabel");
                String key = element.getAttribute("key");
                CustomJMenuItem jMenuItem = new CustomJMenuItem(label, ImageUtils.getScaledImage(element.getAttribute("icon")), key);
                menu.add(jMenuItem);
            }
        }
    }

    private Element getRoot() {
        if (document == null) {
            return null;
        }
        List<element> list = getChildList(document);
        if (list.size() > 0) {
            return list.get(0);
        }
        return null;
    }

    private List<element> getChildList(Node node) {
        List<element> list = new ArrayList<element>();
        NodeList nodeList = node.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            if (nodeList.item(i).getNodeType() == Node.ELEMENT_NODE) {
                list.add((Element) nodeList.item(i));
            }
        }
        return list;
    }
}
eso seria todo para crear nuestra barra de menú. ahora simplemente la agregamos a nuestra aplicación, creando una instancia de esta y agregándola al contenedor. asii:

package CustomJMenuXML.source;

import java.awt.BorderLayout;
import java.awt.Dimension;

/**
 *
 * @author McCubo
 * @version 0.7.6
 */
public class JMenuXMLMain extends javax.swing.JFrame {

    private CustomXMLMenu menu = new CustomXMLMenu();

    public JMenuXMLMain() {
        initComponents();
        setVisible(true);
    }

    private void initComponents() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        setSize(new Dimension(300, 350));
        getContentPane().add(menu, BorderLayout.NORTH);
    }

    public static void main(String[] args) {
        new JMenuXMLMain();
    }
}

Como podemos ver, el código Java permanece mas legible que si creáramos el menú desde este archivo.

Espero que te sirva, puedas compartir con alguien que lo necesite, simplemente aprender del código y, porque no, mejorarlo y mandármelo ya corregido xD (no, no es broma).

Link de descarga de la aplicacion completa
Nos vemos a la próxima, y saludos!

1 comentario: