viernes, 12 de agosto de 2011

Abrir solo un JinternalFrame por instancia en una Aplicación MDI // Allow only one JinternalFrame per Instance in a MDI app.



Para lograr este objetivo existen muchas maneras (como toda cosa en la vida) de lograrlo, pero como mi conocimiento es muy limitado no conozco todas esas maneras x). asi que solamente alcanzaremos ese objetivo mediante 3 formas: 
  1. Patron Singleton
  2. Utilizando HashMap y una clase de utilidad
  3. Haciendo modal el JinternalFrame
A mi parecer la mas eficiente y eficaz (Nose cual es la diferencia entre esas dos palabras pero suena como que si en verdad se lo que estoy hablando xD) de las opciones es la #2. Pero mi intencion no es obligar a nadie a usar  simplemente las opciones que a mi me gustan, sino mas bien, ampliar el conocimiento de cómo podemos alcanzar el mismo objetivo mediante diferentes medios.
La estructura de estos ejercicios (para mayor claridad) sera: un solo proyecto, el cual tendra 3 Opciones en la barra de menu, en cada opcion se desarrollara cada una de las formas. 


mainFrame.java
package Clases;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyVetoException;
import java.lang.reflect.Constructor;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

// author McCubo
public class mainFrame extends JFrame{

    private JDesktopPane desktopPane;
    private JMenuBar menuBar;
    private JMenu menuSingleton;
    private JMenu menuHashMap;
    private JMenu menuModal;
    private JMenuItem optionSingleton;
    private JMenuItem optionHashMap;
    private JMenuItem optionModal;

    public mainFrame() {
        super("SingletonPack");
        initComponents();
        initMenuListeners();
    }

    private void initComponents(){
        setDefaultCloseOperation(EXIT_ON_CLOSE);        
        setSize(new Dimension(800, 500));
        setLocationRelativeTo(null);
        setBackground(Color.WHITE);
        setLayout(new BorderLayout());
        desktopPane = new JDesktopPane();
        // setDragMode sirve para indicarle en que forma se visualizaran los JInternalFrames dentro del Componente
        // OUTLINE_DRAG_MODE: solo se visualiza el borde de los JInternalFrame
        desktopPane.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
        menuBar = new JMenuBar();
        menuSingleton = new JMenu("Singleton");
        menuHashMap = new JMenu("HashMap");
        menuModal = new JMenu("Modal");
        //Creando los Item de los Menu
        optionSingleton = new JMenuItem("Abrir", new ImageIcon("recursos/gamepad.png"));
        optionHashMap = new JMenuItem("Abrir", new ImageIcon("recursos/img.jpg"));
        optionModal = new JMenuItem("Abrir", new ImageIcon("recursos/default.png"));
        // añadimos esos Items a su correspondiente Menu
        menuSingleton.add(optionSingleton);
        menuHashMap.add(optionHashMap);
        menuModal.add(optionModal);
        // añadimos los menus al MenuBar
        menuBar.add(menuSingleton);
        menuBar.add(menuHashMap);
        menuBar.add(menuModal);
        // El JMenuBar y JDesktopPane son agregados al JFrame
        this.add(menuBar, BorderLayout.NORTH);
        this.add(desktopPane, BorderLayout.CENTER);
        setVisible(true);
    }

    private void initMenuListeners(){
        optionSingleton.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                try {
                    // Obtenemos una instancia (Nueva o la que ya tenemos) de la clase OpcionSingleton
                    OpcionSingleton instance = OpcionSingleton.getInstance();
                    if(!instance.isVisible() && !instance.isIcon()){
                        // Si la instancia No esta visible y No esta Minimizada (osea que no existe dentro del MDI)
                        // la agregamos al JDesktopPane
                        desktopPane.add(instance);                        
                    }
                    instance.setVisible(true);
                    instance.setIcon(false);
                } catch (PropertyVetoException ex) {
                    Logger.getLogger(mainFrame.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });

        optionHashMap.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                try {
                     // Obtenemos el JInternalFrame que deseamos abrir, Null si no existe
                    JInternalFrame jInternal = Util.getJInternal(OpcionHashMap.class.getName());
                    if (jInternal == null || jInternal.isClosed()) {
                         // Class.forName(OpcionHashMap.class.getName()) = Creamos una Nueva clase partiendo del Nombre, y de esa Clase
                         // obtenemos el contructor .getConstructor() y le indicamos a que clase pertenece el parametro enviado, nuestro
                         // caso un String.class
                        Constructor constructor = (Class.forName(OpcionHashMap.class.getName())).getConstructor(String.class);
                         // dado que nuestro constructor recibe como parametro un String, dicho parametro es enviado en
                         // constructor.newInstance("Opcion del HashMap");
                         // y hacemos el respectivo Cast al objecto que nos es retornado (JInternalFrame)
                         //
                        jInternal = (JInternalFrame) constructor.newInstance("Titulo del Opcion del HashMap desde mainFrame :O");
                        desktopPane.add(jInternal);
                         // agregamos el recien creado JInternalframe al HashMap de la clase Util
                        Util.addJInternal(OpcionHashMap.class.getName(), jInternal);
                    }
                    jInternal.setVisible(true);
                    jInternal.setIcon(false);                    
                } catch (Exception ex) {
                    Logger.getLogger(mainFrame.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });

        optionModal.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                OpcionModal opcionModal = new OpcionModal(mainFrame.this);
                opcionModal.setVisible(true);
            }
        });
    }

    public static void main(String ... args){
        new mainFrame();
    }

} 
Clase Util.java (necesaria para los el menu de HashMap)

package Clases;

import java.util.HashMap;
import javax.swing.JInternalFrame;

 // @author McCubo
public class Util {

     // HashMap el cual almacenara el nombre de la clase y la ventana correspondiente a dicha clase
    private static HashMap<String , JInternalFrame> JInternalMap = new HashMap<String , JInternalFrame>();


     // @param className Nombre de la clase que queremos Obtener
     // @return si la className se encuentra en el set de Llaves del HashMap, entonces retorna
     // el objeto correpondiente, de lo contrario retorna null

    public static JInternalFrame getJInternal(String className){
        return JInternalMap.get(className);
    }

     // @param className Nombre de la clase
     // @param JInternal Objeto Correspondiente a esa clase    
    public static void addJInternal(String className, JInternalFrame JInternal){
        JInternalMap.put(className, JInternal);
    }
}
Ahora la clase con patron Singleton
Nota: el diseño de los siguentes JInternalFrame lo hice con el IDE Netbeans, asi que el metodo initComponents y la declaracion de campos (JLabel, JTextFields, etc) no los presentos, debido a que es muy extenso. pero no importa mucho, ya que el proyecto esta en un zip al final de la entrada, so dont worry ;)
package Clases;

// @author McCubo

public class OpcionSingleton extends javax.swing.JInternalFrame {

    private static OpcionSingleton frameOpcion1;

     // si no se tiene ninguna instancia de esta clase O la instancia que poseemos
     // el JInternalFrame esta cerrado, siendo asi retornamos una nueva instancia;
     // caso contrario, retornamos la instancia que ya tenemos

    public static OpcionSingleton getInstance() {
        if (frameOpcion1 == null || frameOpcion1.isClosed) {
            frameOpcion1 = new OpcionSingleton();
        }
        return frameOpcion1;
    }

    // Creates new form OpcionSingleton

     // El Constructor debe ser privado para que ninguna clase pueda crear
     // nuevas instancias directamente
    private OpcionSingleton() {
        initComponents();
    }

    private void initComponents(){
       // Codigo Generado por Netbeans
    }

   // Variables declaration - do not modify 
   // Mas Codigo Generado por NetBeans
}// Fin de la Clase
Ahora la Clase OpcionHashMap.java
package Clases;

// @author McCubo

public class OpcionHashMap extends javax.swing.JInternalFrame {

    // Creates new form Opcion2
    public OpcionHashMap(String tituloDelJinternal) {
        initComponents();
        setTitle(tituloDelJinternal);
    }

    private void initComponents(){
       // Codigo Generado por Netbeans
    }

   // Variables declaration - do not modify 
   // Mas Codigo Generado por NetBeans
}// Fin de la Clase
y por ultimo (lo que dicen "y por ultimo pero no menos importante es mentira")
OpcionModal.java
package Clases;

import javax.swing.JDialog;
import javax.swing.JFrame;

// @author mcubias

public class OpcionModal extends JDialog {

    // Creates new form OpcionModal
    public OpcionModal(JFrame mainFrame) {
        super(mainFrame, "Soy modal!!!", true);
        initComponents();
        setLocationRelativeTo(null);
    }

    private void initComponents(){
       // Codigo Generado por Netbeans
    }

   // Variables declaration - do not modify 
   // Mas Codigo Generado por NetBeans
}// Fin de la Clase

Finalmente mi opinion Noooo Noo no dejes de leer por favor!!!!! es que el patron singleton es bastante sencillo, muy comprensible y facil de implementar pero a todas las clases deberiamos agregarle el metodo static que nos retorna la instancia de la clase... con el tiempo y entre mas grande sea la aplicacion, esas cuantas lineas se pueden llegar a convertir en muuuchas lineas extras de codigo.
Mientras (como dije arriba) el HashMap me parece ideal, quiza un poco mas complejidad en el codigo pero, las clases que se llaman no se toca su estructura en ningun momento, asi que me parece bastante bueno usarlo, por muchas razones, la cantidad y tipo de parametro pueden variar entre cada clase que mandamos a llamar, y con ese metodo podemos facilmente adaptarlo para dicho funcionamiento (recibir distinto tipos y cantidad de parametros por constructor).

Y Por ultimo: hacer modal la ventana. lo que debemos hacer es simplemente darle un extends de JDialog, enviarle el frame principal y en el parametro del constructor enviarle true para que sea modal... pros: facil de usar. (solo ese creo). contras: aveces necesitamos que la clase descienda de una clase que No se JDialog, y como bien sabemos, una clase solo puede extender de una clase a lo mucho, asi que ahi veriamos problemas, otro contra, en toda la aplicacion solo vas a poder tener una ventana abierta... poco funcinal creo. pero bueno ahi esta si la quieres implementar :D

Enlace descarga

Espero a alguien en el universo le sirva este ejemplo. Obviamente hay formas mucho mejores para la generacion de Menus en Java, generarlo desde un xml, tener una clase ListenerMenuXxx en lugar de tener todos los Listener en la misma clase, nose pueden haber infinitas mejores formas de hacerlo, pero mi objetivo ahora simplemente era mostrar y compartir algunas formas con las cuales es posible la implementar solo un JInternalFrame por instancia.
asi que sin mucho mas que decir, Saludos!!!

4 comentarios:

  1. podrias dar un tuto de como poder abrir un JInternalFrame desde otro JInternalFrame?

    ResponderEliminar
  2. @Alex rotten
    Perdón por no darte una respuesta inmediata (o almenos mas rápida), pero para el próximo Viernes (21/11) es posible que publique la entrada explicando lo que solicitas. Gracias por comentar y visitar el blog :D.
    Saludos!

    ResponderEliminar
  3. Muy bueno, me sirvio mucho para generar menus dinamicamente. Muchas gracias

    ResponderEliminar