Archivo

Archive for the ‘Programación’ Category

JTable “dinámico” en NetBeans

18 noviembre, 2009 38 comentarios

Al utilizar NetBeans y colocar algún JTable en la interfaz gráfica de usuario suele ser un problema el poder agregar filas a medida que se van necesitando.
Esto se debe a que los JTable no tienen un método para controlar la creación y eliminación de sus filas. Investigando un poco uno descubre que esto se hace utilizando el modelo (TableModel) del objeto JTable. y supone que puede accederlo (a un puntero del modelo) por medio del método getModel() de JTable.
El problema es que aún accediendo al modelo de la tabla, no es posible agregar o eliminar filas como uno esperaría.

La solución más sencilla (y la peor) es asignar por medio del editor de NetBeans una cantidad “estimada” de filas para la tabla. Si llegan a ser demasiadas, el uso de memoria del programa se vuelve exagerado y hay que modificar los parámetros de la JVM para iniciar el programa con la consecuente pérdida de rendimiento. En pocas palabras es lo más ineficiente que se puede hacer.

Una solución muchísimo mejor que también es fácil de implementar es declarar el modelo que va a utilizar la tabla como cualquier otro atributo de la clase (para poder ser utilizado con un JTable agregado gráficamente).

private DefaultTableModel modeloTabla;

Luego este modelo se debe instanciar e inicializar (en el constructor). Pero esto tiene que ser antes de la llamada al método initComponents() que genera NetBeans. De lo contrario este método intentará asignar un modelo no instanciado al JTable, provocando un IllegalArgumentException.

modeloTabla = new DefaultTableModel();
// Agregar las columnas que se necesiten utilizando el método
// addColumn(Object columnName) de DefaultTableModel.
// También es posible utilizar un constructor sobrecargado
// que recibe como parámetros un vector Object[] { … } cuyos
// elementos son los nombres de las columnas y un int con la
// cantidad inicial de filas (se crean vacías):
// DefaultTableModel(new Object[] { "Col 1", "Col 2" }, 5)
initComponents();

Lo que sigue es asociar este modelo al JTable agregado gráficamente. Para hacer esto se selecciona el JTable, y en sus propiedades se busca “model”.

 
En la ventana que aparece al presionar el botón para editar la propiedad se selecciona “Custom code” en el combo box y en el campo de texto simplemente se coloca el nombre del objeto modelo definido en el código.

 
Después sólo es cuestión de agregar, eliminar o incluso mover las filas cuando sea necesario, directamente en el modelo.

// Agregar fila, el parámetro es un vector con los valores de
// las columnas

modeloTabla.addRow(Object[] rowData);

// Eliminar fila, el parámetro es el índice de la fila a
// eliminar

modeloTabla.removeRow(int row);

// Eliminar todas las filas
modeloTabla.getDataVector().clear();

// Mover una o varias filas, los primeros dos parámetros
// seleccionan la(s) fila(s) que se va(n) a mover y el tercero
// indica la posición hacia donde se va a mover dicha selección

modeloTabla.moveRow(int start, int end, int to);

Los cambios se reflejan instantáneamente en el JTable.

Anuncios
Categorías:Programación

Eliminación de directorios con C++

27 marzo, 2009 6 comentarios

En esta ocasión presento algo que parece bastante sencillo, pero que cuesta implementar, especialmente si se utiliza el compiladorcito de Borland. Se trata de un programa que elimina directorios completos.

El desafío lo presentan las restricciones del lenguaje y del compilador. Sólo se puede eliminar un directorio una vez que no contenga archivos ni subdirectorios, y que no se encuentre en uso por algún proceso. En pocas palabras, no existe alguna función que reciba el nombre del directorio y lo elimine. Bueno, este procedimiento lo hace.

Aquí está el código, bien comentado espero:

#include <direct>
#include <dirent>
#include <stdio>
#include <string>

void eliminarDirectorio(char *directorio);

void main()
{
    char *directorio;
    // Ojo, un char* sin diagonal inversa al final. Pero podría
    // ser prácticamente cualquier directorio (así que cuidado).
    // También debe ser el nombre completo, incluyendo la letra
    // de la unidad de disco.

    directorio = "C:\\New Folder";
    eliminarDirectorio(directorio);
}

void eliminarDirectorio(char *directorio)
{
    // Se utiliza para listar un directorio.
    DIR *folder;
    // En Borland, la estructura dirent únicamente tiene el
    // campo d_name, que es el nombre del archivo o
    // subdirectorio. Muy lamentable y una limitante
    // considerable.

    struct dirent *entry;
    // Contendrá el nombre del archivo o subdirectorio contenido
    // en el directorio que se quiere eliminar.

    string nombre;
    // Hay que "moverse" hacia dentro del subdirectorio, porque
    // como sólo se dispone del nombre del archivo o
    // subdirectorio, no puede "haber" nombres de directorios en
    // la ruta de lo que se quiere eliminar. Hay que estar en el
    // mismo directorio.

    chdir(directorio);
    // Se abre el directorio para poder recorrer los elementos
    // que contiene.

    folder = opendir(directorio);
    // Esto recorre todos los subdirectorios y archivos dentro
    // del directorio "original".

    while (entry = readdir(folder))
        // Otra vez, por la estructura dirent de Borland, no se
        // puede usar el campo d_type. Esto excluye al
        // directorio actual y al directorio que lo contiene de
        // la lista de lo que se va a eliminar.

        if (strcmp(entry->d_name, ".") != 0 &&
            strcmp(entry->d_name, "..") != 0)
        {
            // Al fin, el nombre del archivo o subdirectorio.
            nombre = entry->d_name;
            // La función remove sólo funciona con archivos. Si
            // no tiene éxito, es porque lo que trató de
            // eliminar fue un directorio. Entonces retorna 1,
            // indicando que la operación falló. Y significa que
            // se trató de un subdirectorio, posiblemente con
            // más archivos y subdirectorios.

            if (remove(nombre.c_str()))
            {
                // Considero que 600 caracteres son más que
                // suficiente para una ruta. Igual puede
                // aumentarse.

                char dir[600];
                // Concatena al nombre del directorio que se
                // quiere eliminar, una diagonal inversa y el
                // nombre del subdirectorio.

                sprintf(dir, "%s\\%s", directorio,
                        entry->d_name);
                // Y tenía que serlo, un procedimiento
                // recursivo. Se llama a sí mismo, pero con el
                // nombre del subdirectorio esta vez.

                eliminarDirectorio(dir);
            }
        }
    // Cierra el directorio que se recorrió. De no hacerlo la
    // eliminación del directorio no es posible.

    closedir(folder);
    // Se mueve al directorio superior, otro requisito para la
    // eliminación.

    chdir("..");
    // Elimina el directorio recorrido (posiblemente el
    // subdirectorio de una de las llamadas recursivas).

    _rmdir(directorio);
}

En cuanto al funcionamiento, el código lo explica bien, pero por si acaso:

  1. Se abre el directorio que se quiere eliminar.
  2. Se recorren los contenidos del directorio. Si son archivos se eliminan. Si es un subdirectorio, el procedimiento se llama a sí mismo, pero para eliminar el subdirectorio.
  3. La ruta de funcionamiento del programa se mueve al directorio superior.
  4. Se elimina el directorio con el que se llamó al procedimiento.

Lo bonito de este programa es que funciona con el compilador Borland 5.02, por lo menos.

Categorías:Programación

Agua animada con Blender y Java 3D (parte 2)

19 octubre, 2008 11 comentarios

En la primera parte se explica cómo crear superficies de agua en Blender y exportarlas como archivos .obj apropiados para utilizar con Java 3D. En esta parte se explica cómo importar archivos .obj en Java 3D y utilizarlos para hacer una animación de una superficie de  agua.

Debo aclarar que todo lo que está a continuación no es más que la adaptación del ejemplo Morphing, que se encuentra incluido entre los ejemplos disponibles en la parte 1. La clase MorphingBehavior, que debe incluirse en el proyecto, también está incluida en ese ejemplo, NO es alguna clase que yo haya hecho, sólo la utilizo. El ejemplo terminado (como proyecto de NetBeans) se encuentra aquí:

Agua.zip

Toda la explicación siguiente se basa en el método crearAgua() de la clase Agua, por lo que recomiendo descargar el ejemplo, abrir el archivo Agua.java en su editor favorito y buscar el método mencionado. El código está muy bien comentado (según lo que entendí del ejemplo original).

¿List@? Muy bien. En primer lugar, el método crearAgua() retorna un objeto BranchGroup porque este tipo de objeto permite ser “compilado”. Java 3D lo compila y lo almacena en caché, con el propósito de mejorar el rendimiento. Sin embargo, esto evita que el objeto devuelto pueda ser modificado, aunque no veo por qué debería hacerse. En todo caso, con un poco de experiencia en Java es posible modificarlo de modo que el objeto devuelto sea un TransformGroup.

De manera general, éstos son los pasos que componen el método:

  1. Se declara e instancia el BranchGroup que se va a devolver.
  2. Se definen las transformaciones necesarias para que el agua se “ajuste” a nuestro universo (creado en el constructor). Estas transformaciones consisten en aplicar una escala ya sea para agrandar o reducir la superficie (según convenga); y rotar toda la superficie 90º sobre el eje x, para que la superficie sea horizontal. De lo contrario es como si el agua se viera desde arriba, y lo que es peor, no sería lógico en el sistema de coordenadas del universo, al tener una superficie de agua vertical.
  3. Se crea una esfera para limitar la aplicación de la iluminación y el fondo de la escena. También se usa para indicar cuándo debe activarse el objeto Behavior de la animación.
  4. Se carga una imagen y se utiliza como fondo para simular el cielo. Esto es opcional.
  5. Se definen las luces ambiental y direccional. La primera es para que el material de la superficie de agua refleje su color y la segunda para acentuar las olas con sombras y reflejos. Yo diría que esto es necesario, de lo contrario el agua se vería como un simple plano.
  6. Se declaran e instancian distintos vectores de objetos de Java 3D para contener y manipular las superficies distintas en distintas “etapas” desde que se cargan, hasta ser unidas en una animación.
  7. Se cargan las superficies y se manipulan en los vectores definidos en el paso anterior.
  8. Se define un objeto Appearance para el agua. Aquí se establecen el color y la brillantez de la misma.
  9. La apariencia anterior, junto con uno de los vectores, se utiliza para crear un objeto Morph que permite transformar las distintas geometrías contenidas en el vector, una en otra.
  10. Se crea el objeto Alpha que controlará la velocidad de la animación.
  11. El objeto Morph y el objeto Alpha se utilizan para instanciar un objeto MorphingBehavior (la única clase que debe copiarse al proyecto). Esta clase une la transformación de las geometrías con el tiempo para que sea realizada. Y se agrega directamente al BranchGroup que se retornará.
  12. Cuando la superficie de agua se ve “de lado”, muchos polígonos desaparecen. Lo único que hay además del agua es el fondo, por lo que sólo se ven un montón de olas que más parecen gaviotas volando. Recomiendo comentar la línea 182 para comprender a qué me refiero. Lo que hice para solucionar esto fue crear un objeto Box que actúa como un fondo extra, para evitar que se vea el fondo de cielo. Al final esta caja resulta siendo una parte considerable del agua. Por eso es que se crea un nuevo objeto Appearance para darle a la caja un color azul. Y obviamente también se crea la caja. También se le aplican transformaciones para ubicarla donde debe quedar y completar el efecto deseado.
  13. Finalmente se compila y se retorna la rama.

Con esto es suficiente para poder utilizar agua en cualquier proyecto con Java 3D que se desee, basta con copiar el método. Pero esa no es la gracia tampoco. Además del método crearAgua() lo único que hay en la clase Agua es su constructor y el método main(). En el constructor se configura el layout de la ventana y se crea un Canvas3D para dibujar allí el universo, que también se instancia allí. Hay unas líneas que configuran el punto de vista del universo para que el agua se vea ligeramente desde arriba, y por último se agrega la rama del agua al universo llamando al método crearAgua(). En el método main() sólo se agrega el Applet devuelto por el constructor a un MainFrame para verlo en una ventana.

Ésta es una muestra del resultado final (la animación real es mucho más fluida y no se ve tan rítmica, pero sólo es para darse una idea):

Agua

Y con esto finalmente termina esta explicación para crear agua animada en Java 3D con la ayuda de Blender. Espero que sea de utilidad.

Categorías:Programación Etiquetas: , , , ,

Agua animada con Blender y Java 3D (parte 1)

9 octubre, 2008 5 comentarios

Bueno, en uno de mis proyectos de la U me tocó representar agua en Java 3D. Buscando entre unos ejemplos (muy buenos) disponibles aquí encontré uno sobre transformar una figura tridimensional en otra. Empecé a estudiar el ejemplo y vi que es posible cargar archivos de tipo .obj (archivos de texto con coordenadas tridimensionales que representan objetos) desde Java 3D. Entonces pensé en crear distintas mallas de agua ligeramente diferentes y transformar una en otra para simular las ondulaciones del agua.

Lo difícil era entonces crear esas mallas. Y aquí entra Blender. Es posible exportar figuras como archivos .obj. Y mejor aún, existe una manera muy simple de crear estas mallas en minutos. Eso sí, las simulaciones de superficies de agua hechas con Blender que se encuentran en internet suelen valerse de texturas, que no se exportan adecuadamente porque la sintaxis de los archivos .obj de Blender no es soportada por completo por Java 3D, entonces lo mejor es usar una malla (grid) "moldeada" como una superficie de agua. Pero en serio, es muy fácil.

Lo primero es obvio, eliminar el cubo que viene por defecto en el archivo nuevo de Blender. Sugiero tener cuidado de no mover el cursor para que todo lo que se cree aparezca en el centro. Y luego se agrega un objeto "Grid" (la malla que se va a moldear).

También se deben eliminar la cámara y el foco que vienen en el archivo nuevo. De verdad, no son necesarios en Java 3D, pues tiene su propia forma de lidiar con la vista y la iluminación.

Para la malla se va al menú Add > Mesh > Grid. Luego Blender solicita las dimensiones, sugiero que ambas sean menores a 100, alrededor de 50 funciona bien. Éste es el resultado, con la vista "Top" (por defecto).

Luego se cambia a cualquier vista que permita ver a la malla horizontalmente. Yo uso la frontal.

La razón es que es mucho más fácil lograr un desplazamiento únicamente vertical para las siguientes transformaciones que hay que hacer. También recomiendo aumentar el zoom para que la malla ocupe casi todo el ancho de la ventana. Esto permite ver las líneas de medición que proporciona Blender y permite calcular mejor el desplazamiento necesario.

 

Lo siguiente es cambiar el modo de la vista de "Object Mode" a "Edit Mode". Eso se puede hacer con la tecla Tab. Posteriormente se deselecciona todo, con la tecla A. Habiendo hecho esto se va al menú Select > Random… Recomiendo usar el 50% que está por defecto.

Esto selecciona la mitad de los puntos que componen la malla al azar. Lo que resta es tomar la flecha de la selección que apunta hacia arriba (flecha z) y arrastrarla hacia arriba o abajo. Ojo, no mucho. Luego se deselecciona todo de nuevo con A y se vuelven a seleccionar la mitad de los puntos al azar. Luego se toma de nuevo la flecha de z, pero se mueve en la dirección opuesta a la anterior.

El resultado es una bonita superficie aleatoria que, si no somos muy exigentes, pasa por agua.

 

 

 

 

La renderizada no es necesaria, lo que sigue es exportar la malla.

Para exportar se va al menú File > Export > Wavefront (.obj)… Luego se selecciona la ubicación y nombre del archivo (es un poco confuso) y hecho esto, recomiendo deseleccionar todas las opciones que aparecen. De nuevo, esto se debe a la sintaxis del archivo que se genera. Sería necesario hacerle cambios para que funcione en Java 3D si se exportara cualquier otra cosa.

 

Todo el proceso se repite dos veces más, de modo que el resultado final son tres archivos .obj. Para facilitar su uso con Java todos deben tener el mismo nombre, seguido de un número. Los números deben ser correlativos (lo mejor es usar 0, 1 y 2, por ejemplo Agua0.obj, Agua1.obj y Agua2.obj).

En la siguiente parte explicaré a grandes rasgos la utilización de estos archivos y cómo transformarlos para hacer la animación del agua.

Categorías:Programación