Enviar respuesta 
 
Calificación:
  • 0 votos - 0 Media
  • 1
  • 2
  • 3
  • 4
  • 5
¿Cómo tener una sola instancia de una ventana en un escritorio?
dgalo88 Sin conexión
lider de proyecto
****

Mensajes: 74
Registro en: Jun 2010
Reputación: 0
Mensaje: #1
¿Cómo tener una sola instancia de una ventana en un escritorio?
Saludos.!!

Necesito cargar una ventana (WindowPane en el caso particular), la cual puede permanecer abierta sin bloquear la funcionalidad del Desktop. El problema es que solo necesito una ventana a la vez...

[Imagen: raxn5j.jpg]
(Este mensaje fue modificado por última vez en: 25-02-2011 07:42 PM por demian.)
24-02-2011 12:33 PM
Visita su sitio web Encuentra todos sus mensajes Cita este mensaje en tu respuesta
demian Sin conexión
Administrator
*******

Mensajes: 1.808
Registro en: Jun 2010
Reputación: 0
Mensaje: #2
RE: Instancia de objeto unica
Voy a suponer que tienes una clase MyDesktop que representa el escritorio ese MyDesktop debería heredar de ContentPane si mal no recuerdo.

Luego voy a suponer que tienes varios formularios/ventanas, por ejemplo FrmFoo, FrmFaa, FrmBar, etc. y que todos heredan de WindowPane.

También voy a suponer que tienes en el escritorio un menú de tres botones, (Foo, Faa, Bar) y que el comportamiento del menú es abrir los respectivos formularios si aún no se ha abierto el primero, o mover al frente el respectivo formulario si ya está abierto.

Por ejemplo, si el usuario aún no ha hecho click por primera vez en Foo, entonces al hacerle click se crea una nueva instancia de FrmFoo y se añade al escritorio. Si el usuario ya hizo click en el botón Foo con anterioridad, y ya hay una ventana FrmFoo abierta, entonces es necesario mover al frente esa instancia que ya está abierta para que el usuario la pueda ver, es decir, puede estar detrás de otras ventanas de manera que no se ve y es necesario traerla al frente.

¿Se entiende la funcionalidad y la estructura básica que tenemos?

Si la respuesta es si, entonces vamos a ver como lo implementamos. Debo aclarar que no estoy codificando esto, así que es muy probable que si copian/pegan las cosas no funcionen directamente. Lo que quiero ilustrar es el principio, darles la idea para que la codifiquen ustedes mismos.

Primero, al escritorio MyDesktop tenemos que añadirle una tabla (hash) que indexe instancias de ventanas por tipo de ventana. Es decir, es una tabla cuya clave es el tipo de ventana y cuyo valor es una instancia de una ventana de dicho tipo. Eso en Java se puede hacer más o menos:

Código:
class MyDesktop extends ContentPane {
  // ... bla bla ...

  private Map<String, WindowPane> windowsByType = new HashMap<Class, WindowPane>();

  // ... bla bla ...
}

Creo que a estas alturas no tengo que explicar lo que es una tabla hash.

La idea es la siguiente, el "Tipo" de ventana en este caso es la primera cadena, por ejemplo, si quieren una ventana de tipo FrmFoo, entonces el tipo va a ser la cadena "FrmFoo", y así para todos los demás casos. Más adelante vamos a ver que hay una forma más elegante de hacer esto, pero por lo pronto está bien así.

Ahora bien, cuando el usuario hace click en el botón "Foo", si queremos que la aplicación se comporte como se planteó al principio de este post es necesario hacer lo siguiente:

1.- Buscar en la tabla de ventanas por tipo si existe una instancia ya agregada al escritorio.
2.1a.- Si no existe, entonce es necesario crear una nueva instancia y añadirla a la tabla, de forma que la próxima vez que se haga click en el botón la podamos encontrar en la tabla (ver 2b)

2.2a.- Tenemos que añadir un manejador de eventos que detecte cuando la ventana se cierre para poder eliminarla de la tabla, porque si no lo hacemos entonces la próxima vez que alguien haga click en algún botón para abrir una ventana vamos a tener un bug (ver 2b también)

o bien,

2b.- Si ya existe una instancia, entonces es que ya hay una ventana en el escritorio, por lo tanto tenemos que obtenerla (eso es fácil, la tomamos de la tabla porque se supone que tenemos el tipo) y hay que traerla al frente para que se le muestre al usuario (como quiera que eso se haga en Echo).

El algoritmo del manejo del botón en Java sería algo como esto (no esperen que compile):

Código:
// Este es el manejador del evento del botón Foo del menú
private void btnFooClicked() {
  // Pedimos la instancia en la tabla. "FooFrm" es el tipo de ventana,
  // cambiar acorde según la rutina que maneje el botón.
  WindowPane wp = windowsByType.get("FooFrm");

  if (wp == null) {
    // No hay una ventana de ese tipo en el escritorio
    wp = new FooFrm();
    add(wp);
    windowsByType.put("FooFrm", wp); // Para encontrarla más adelante.

    // El manejador del evento, para que al cerrar la ventana la eliminemos
    // de la tabla
    wp.addWindowListener(new WindowAdapter() {
      public void windowClosed(WindowEvent evt) {
        windowsByType.remove("FooFrm");      
      }
    });
  } else {
    wp.raise(); // como ya tenemos la ventana, simplemente la movemos al frente.
  }
}

En general eso debería funcionar.

[Imagen: dmi-1.jpg]
24-02-2011 02:43 PM
Visita su sitio web Encuentra todos sus mensajes Cita este mensaje en tu respuesta
demian Sin conexión
Administrator
*******

Mensajes: 1.808
Registro en: Jun 2010
Reputación: 0
Mensaje: #3
RE: Instancia de objeto unica
Ahora bien, todo esto lo pueden mejorar un poco. Por ejemplo, yo no usaría un String para indexar la tabla, usaría un Class, por ejemplo, la tabla hash en MyDesktop la declararía así:

Código:
private Map< Class<?>, WindowPane> windowsByType = new HashMap<Class, WindowPane>();

O inclusive:

Código:
private Map< Class<? extends WindowPane>, WindowPane> windowsByType = new HashMap<Class, WindowPane>();

Dependiendo de lo exquisitos que se quieran poner (no me pongan a explicar la parametrización, para eso lean sobre generics en Java.

Voy a decir muy brevemente que un clas de tipo Class es una clase que se usa para describir otras clases (meta-data), todos las clases en Java tienen asociado un objeto de tipo Class que las describe, y eso es la base en parte de todo el mecanismo de reflection programming de Java (ver: http://foo.org.ve/mybb/showthread.php?tid=207 ).

Uno puede obtener un objeto de tipo class en Java (Para un tipo Foo) de dos formas:

La primera, de forma estática (sin tener una instancia de una clase):

Código:
Class clazz = Foo.class;

La segunda, teniendo una instancia:

Código:
Foo f; // Supongan que f está adecuadamente inicializada

Class clazz = f.getClass();

Ahora bien, la pregunta que uno se puede hacer es, ¿Por qué es útil usar reflection programming en este caso si el problema ya está resuelto como se muestra en el post anterior? pues porque es posible generalizar muchas cosas.

Por ejemplo, con la estrategia usada en el post anterior tendrían que escribir un manejador de eventos particular para cada ventana, y repetir en cada manejador el mismo código, cambiando solamente el tipo que se usa para la tabla hash por el tipo adecuado, en cambio usando reflection programming eso se puede optimizar un poquito:

Código:
private void btnFooClicked() {
  genericOpenWindow(FrmFoo.class);
}

private void btnFaaClicked() {
  genericOpenWindow(FrmFaa.class);
}

// etc...

private void genericOpenWindow(Class<WindowPane> windowType) {
  WindowPane wp = windowsByType.get(windowType);

  if (wp == null) {
    // Esto crea un nuevo tipo según el Class pasado.
    wp = windowType.newInstance();
    add(wp);
    windowsByType.put(windowType, wp);

    wp.addWindowListener(new WindowAdapter() {
      public void windowClosed(WindowEvent evt) {
        WindowPane source = (WindowPane)evt.getSource();
        windowsByType.remove(source.getClass());      
      }
    });
  } else {
    wp.raise();
  }
}

Claro, esto asume cosas como que los formularios tienen/usan constructores vacíos a los que no es necesario pasarle parámetros (lo que es en general una buena idea).

En fin, quizá no le hagan mucho caso a este segundo post que usa reflexión (aunque mi consejo es que aprendan a usar esa técnica), pero creo que el primer post les debería dar una idea clara de como resolver el problema.

[Imagen: dmi-1.jpg]
24-02-2011 02:59 PM
Visita su sitio web Encuentra todos sus mensajes Cita este mensaje en tu respuesta
dgalo88 Sin conexión
lider de proyecto
****

Mensajes: 74
Registro en: Jun 2010
Reputación: 0
Mensaje: #4
RE: Instancia de objeto unica
Gracias profe, ya funciona bien solo que no hace el raise pues WindowPane no tiene esa opción.

Con el raise dentro del else genera un error de compilación, cuando lo quito todo funciona pero debo agregar un throw. No creo que hacer el raise de la ventana sea tan necesario pues solo hay 2 ventanas de este tipo en el juego, así que por ahora podemos dejarlo de esa forma, pero me gustaría saber si es posible hacer el raise de otra forma.

[Imagen: raxn5j.jpg]
25-02-2011 06:59 PM
Visita su sitio web Encuentra todos sus mensajes Cita este mensaje en tu respuesta
demian Sin conexión
Administrator
*******

Mensajes: 1.808
Registro en: Jun 2010
Reputación: 0
Mensaje: #5
RE: Instancia de objeto unica
(24-02-2011 02:43 PM)dmi escribió:  Debo aclarar que no estoy codificando esto, así que es muy probable que si copian/pegan las cosas no funcionen directamente.

Conste que lo dije...

(25-02-2011 06:59 PM)dgalo88 escribió:  no hace el raise pues WindowPane no tiene esa opción.

Si, es probable que no exista el método raise para un WindowPane, eso me suena más a Swing, QT, o algún otro framework de widgets de escritorio. Ahora que lo pienso, creo que en Echo3 la cosa se hace con "setZIndex" o algo por el estilo, lo que tiene sentido porque concuerda con el hecho de que Echo está generando html y que en html lo que define que se ve encima de que es el z-index.

Si mal no recuerdo, era necesario recorrer todas las ventanas del escritorio (ya las tienes en la tabla hash) y asignarle e la ventana que tienes que mostrar un z-index superior al mayor índice que encontraste entre todas las ventanas.

Copio y pego una rutina que hace el trabajo (en Echo2, advierto):

Código:
public static void defaultAddForm(Component parent, WindowPane form) {
    int maxZindex = 0;

    for (int i = 0; i < parent.getComponentCount(); i++) {
      Component component = parent.getComponent(i);

      if (!(component instanceof WindowPane)) {
        continue;
      }

      WindowPane curr = (WindowPane) component;

      if (curr.getZIndex() > maxZindex) {
        maxZindex = curr.getZIndex();
      }
    }

    form.setZIndex(maxZindex + 1);
    parent.add(form);
  }

Ahí puedes ver como se manejan los z-indices. La rutina básicamente añade un nuevo form (form) a un component (que usualmente es un Desktop o un ContentPane) y garantiza que el z-index sea superior al de todas las ventanas existentes en el componente (parent) pasado.

No es exactamente lo que necesitas para el "raise()", pero con este ejemplo puedes escribir algo similar que use el mismo principio del z-index y que funcione.

[Imagen: dmi-1.jpg]
25-02-2011 07:41 PM
Visita su sitio web Encuentra todos sus mensajes Cita este mensaje en tu respuesta
Enviar respuesta 


Salto de foro:


Usuario(s) navegando en este tema: 1 invitado(s)