12.176 cursos gratis
8.742.277 alumnos
Facebook Twitter YouTube
Busca cursos gratis:

Capítulo 6:

 Desarrollo de una aplicación

6. DESARROLLO DE UNA APLICACIÓN

6.1.Descripción del proyecto

El proyecto en el que se ha pretendido aplicar los conocimientos adquiridos mediante esta investigación consiste en una pequeña hoja de cálculo con la que se puedan realizar las operaciones aritméticas básicas (igualación, suma, resta, multiplicación y división) y combinaciones sencillas de estas (potencia, promedio, por ejemplo), además de comparaciones entre varios elementos (determinar el valor mayor o menor de una lista).

También se busca que la aplicación tenga la capacidad de almacenar los datos de la hoja de cálculo de manera persistente en el equipo, así como de abrir documentos (hojas) guardados con anterioridad y borrar las hojas, que ya han sido guardadas previamente, para liberar el limitado espacio de almacenamiento del dispositivo.

6.2. Herramientas a utilizar

Para la realización de la hoja de cálculo (llamada hojaC, nombre perteneciente a la clase principal), se ha utilizado una computadora de las siguientes características:

-Procesador AMD Duron a 1.3 Ghz.

-160 MB de memoria RAM.

-Disco duro de 8.4 Gb.

-Sistema operativo: Mandrake Linux 8.2.

Se inició la escritura del código de hojaC, como se comentó en el capítulo 4, utilizando un editor de texto llamado Gedit y Java Wireless Toolkit para la compilación. Aunque esto resultaba sumamente tediosos, por lo que se optó por utilizar el Entorno Integrado de Desarrollo (IDE) Sun ONE Studio, en una versión de evaluación especial para la plataforma micro de Java.

6.3. Desarrollo

La aplicación está compuesta por cinco clases principales, de las cuales la llamada hojaC es la que se ejecuta al iniciar y la que inicia el resto de las clases. A continuación se describen las funciones con que cada clase debe cumplir.

-hojaC: Es la clase MIDlet, es decir, la que se ejecuta. esta clase sólo utiliza una forma (Form) sobre la que se instancian una clase Tabla y una clase TextField, de estas, TextField se encarga de mostrar en la parte inferior de la pantalla el valor de la celda actual, Tabla se explica enseguida.

-Tabla [1]: Esta es la clase que realiza la mayor parte del trabajo, ya que es la encargada de dibujar la hoja y sus valores, almacenar dichos valores temporalmente, realizar las llamadas a otras clases y la actualización del campo de texto (TextField). Esta clase implementa la interfaz CustomItem, la que permite la utilización de elementos propios de la clase Canvas (eventos de teclas, dibujo de líneas, rellenado de rectángulos, cambio de colores, etc.) y de los formularios o formas (TextField, por ejemplo).

-Texto: Hereda o extiende a la clase TextBox, su única función consiste en capturar los valores de las celdas y enviarlos al arreglo donde los guarda Tabla.

-funcion: Esta clase contiene los métodos necesarios para la resolución de las fórmulas que se introducen a la hoja de cálculo. Un objeto instanciado en esta clase se construye cada vez que la hoja se actualiza, esto es, cada que se presiona una tecla

-Archivo: La clase Archivo es la encargada de todas las operaciones que se realizan con archivos. En ella se encuentran los métodos destinados a pedir el nombre o mostrar la lista de los archivos, así como guardarlos, borrarlos o abrirlos, según lo solicite el usuario.

Ahora se explicará cada clase mostrando aquellos fragmentos de código que se consideren importantes para tal explicación.

6.3.1.Clase hojaC

La clase hojaC, como el resto de las clases de esta aplicación, forma parte del paquete hoja. Debido a que será la clase que habrá de albergar a las demás, debe heredar el paquete midlet, lcdui será para poder utilizar la clase Form. Además la clase, para poder se ejecutada, necesita extender la clase MIDlet.

package hoja;

import javax.microedition.midlet.*;

import javax.microedition.lcdui.*;

public class hojaC extends MIDlet implements CommandListener {

Esta clase incluirá los comandos Salir, Guardar, Abrir, Borrar y Nuevo. También se declaran las clases disp, forma1, tabla, txt1 y una variable lógica prim que servirá para identificar cuando sea la primer vez que se ejecuta la clase, esto sirve para no redefinir los comandos y los valores de algunas variables.

    private final static Command Salir = new Command("Salir",                                                                                                          Command.EXIT, 1);

    private final static Command Guardar = new Command("Guardar",                                                                                             Command.SCREEN, 3);

    private final static Command Abrir = new Command("Abrir",                                                                                                         Command.SCREEN, 3);

    private final static Command Borrar = new Command("Borrar",                                                                                                    Command.SCREEN, 3);

    private final static Command Nuevo = new Command("Nuevo",                                                                                                   Command.SCREEN, 3);

    private Display disp;

    private Form forma1;

    private Tabla tabla;

    public TextField txt1;

    private boolean prim;

En el constructor de la clase sólo se instancian las clases forma1, tabla, txt1 y se le asigna a prim el valor inicial de verdadero.

    public hojaC(){

        forma1=new Form("Mini-hoja de cálculo");

        tabla=new Tabla(null, Display.getDisplay(this),this);

        txt1=new TextField("=", null, 64, 0);

        prim=true;

    }

En el método startApp() se agregan los comandos, la tabla y el campo de texto a forma1 y se define esta como objeto a mostrar.

    public void startApp() {

        if (prim){

            disp=Display.getDisplay(this);

            forma1.append(tabla);

            forma1.append(txt1);

            forma1.addCommand(Salir);

            forma1.addCommand(Guardar);

            forma1.addCommand(Abrir);

            forma1.addCommand(Borrar);

            forma1.addCommand(Nuevo);

            forma1.setCommandListener(this);

            prim=false;

            disp.setCurrent(forma1);

        }else{

            disp.setCurrent(forma1);

        }

    }

    public void pauseApp() {    }

    public void destroyApp(boolean unconditional) {    }

La función settexto es utilizada por la clase Tabla para actualizar el contenido de txt1 cada vez que en la tabla se cambia de celda.

    protected void settexto(String texto){

        txt1.setString(texto);

    }

Finalmente, en el método commandAction se define que se hará en caso de cada opción del menú.

    public void commandAction(Command c, Displayable d){

        if (c==Salir){

            destroyApp(false);

            notifyDestroyed();

        }

        if(c==Abrir){

            Archivo ar=new Archivo('A',tabla.getDatos(),disp,forma1);

            forma1.setCommandListener(this);

        }

        if(c==Guardar){

            Archivo ar=new Archivo('G',tabla.getDatos(),disp,forma1);

            forma1.setCommandListener(this);

        }

        if(c==Borrar){

            Archivo ar=new Archivo('B',tabla.getDatos(),disp,forma1);

            forma1.setCommandListener(this);

        }

        if(c==Nuevo){ tabla.limpiar(); }

    }

}

6.3.2.Clase Tabla

Esta clase importará el paquete javax.microedition.lcdui y la clase funcion de el paquete hoja. Además agrega un comando al menú que había creado hojaC, el comando cmdAny (que mostrará el texto "Valor"). Se hereda la capacidad de manejo de herramientas de Canvas en la misma pantalla que elementos de Form.

package hoja;

import javax.microedition.lcdui.*;

import hoja.funcion;

public class Tabla extends CustomItem implements ItemCommandListener {

    private final static Command cmdAny = new Command("Valor",                                                                                                  Command.SCREEN,1);       

    private Display display;

//Ancho de la columna que muestra el número de fila o renglón

    private static int num=18;

//Filas y columnas en pantalla

    private int rows = 6;

    private int cols = 3;

//Filas y columnas reales de la tabla (filas y columnas del arreglo)

    private int rrows = 5;

    private int rcols = 3;

//Tamaño de una celda

    private int dx = 51;

    private int dy = 19;

//Celda seleccionada

    private int currentX = 0;

    private int currentY = -1;

//Arreglo donde se almacenarán los datos

    private String[][] data = new String[rrows][rcols];

//Indicador de la pantalla vertical y horizontal donde se encuentra[2].

    private int pantallaV=1;

    private int pantallaH=1;

//Se necesita utilizar una clase hojaC para la actualización de txt1.

    private hojaC hoja1;

En el constructor de Tabla se crea una clase CustomItem (super) con title como parámetro, se asigna valor a d y se definen el comando.

    public Tabla(String title, Display d,hojaC hoja) {

        super(title);

        display = d;

        addCommand(cmdAny);

        setDefaultCommand(cmdAny);

        setItemCommandListener(this);

        hoja1=hoja;

    }

El método paint es el encargado de dibujar el estado actual de la tabla, originalmente integra a la clase Canvas, y CustomItem lo hereda de esta. Lo primero que se hace es dibujar las líneas y los títulos de columna y renglón.

   protected void paint(Graphics g, int w, int h) {

        for (int i = 0; i <= rows; i++) {

            if (i>0&&i<rows){

                int ClipX = g.getClipX();

                int ClipY = g.getClipY();

                int ClipWidth = g.getClipWidth();

                int ClipHeight = g.getClipHeight();

                g.setClip(1, (i*dy), num - 1, dy - 1);

                g.drawString(String.valueOf(i), num-2, ((i + 1) * dy)+1,

                                              Graphics.BOTTOM | Graphics.RIGHT);

                g.setClip(ClipX, ClipY, ClipWidth, ClipHeight);

            }

            if (i==0)

                g.drawLine(0,0, cols * dx+num, i * dy);

            g.drawLine(0, i * dy, cols * dx+num, i * dy);

        }

        for (int i = 0; i <= cols; i++) {

            if (i>0){

                int x;

                x=((i * dx)+((i-1)*dx))/2;

                int ClipX = g.getClipX();

                int ClipY = g.getClipY();

                int ClipWidth = g.getClipWidth();

                int ClipHeight = g.getClipHeight();

                g.setClip(((i-1)*dx)+num, 1, dx - 1, dy - 1);

                g.drawString(letrade(i), x+num,dy,

                                        Graphics.BOTTOM | Graphics.HCENTER);

                g.setClip(ClipX, ClipY, ClipWidth, ClipHeight);

            }else

                g.drawLine(0,0, 0, rows * dy);

            g.drawLine((i * dx)+num, 0, (i * dx)+num, rows * dy);

        }

Después se dibuja un rectángulo de color diferente al resto para indicar la celda seleccionada.

        int oldColor = g.getColor();

        g.setColor(0x00D0D0D0);

        g.fillRect((currentX * dx) + 1+num, ((currentY+1) * dy)+1, dx - 1, dy - 1);

        g.setColor(oldColor);

Finalmente se busca en el arreglo data todas aquellas celdas que contengan algún valor y se dibuja su contenido en la posición adecuada.

        for (int i = 1; i < rows; i++) {

            for (int j = 0; j < cols; j++) {

                if (data[i-1][j] != null) {

                    int oldClipX = g.getClipX();

                    int oldClipY = g.getClipY();

                    int oldClipWidth = g.getClipWidth();

                    int oldClipHeight = g.getClipHeight();

                    funcion func=new funcion(data);

                    g.setClip((j * dx) + num, i * dy+1, dx - 1, dy - 1);

                    g.drawString(func.Valor(data[i-1][j]), (j * dx) + num+1,

                          ((i + 1) * dy) - 2, Graphics.BOTTOM | Graphics.LEFT);

                    g.setClip(oldClipX, oldClipY, oldClipWidth, oldClipHeight);

                }

            }

        }

    }

El método traverse está definido como integrante de CustomItem, esta clase utiliza los eventos de teclas de la clase Canvas para lograr el desplazamiento por las celdas de la tabla.

    protected boolean traverse(int dir, int viewportWidth, int viewportHeight,

                                                                                            int[] visRect_inout) {

        switch (dir) {

            case Canvas.DOWN:

                    if (currentY < (rrows - 1)) {

                        currentY++;

                        repaint(currentX * dx, currentY * dy, dx, dy);

                        repaint(currentX * dx, currentY+1 * dy, dx, dy);

                    } else {

                        pantallaV++;

                    }

                break;

            case Canvas.UP:

                    if (currentY > 0) {

                        currentY--;

                        repaint(currentX * dx, (currentY + 2) * dy, dx, dy);

                        repaint(currentX * dx, (currentY+1) * dy, dx, dy);

                    } else {

                        pantallaV--;

                        return false;

                    }

                break;

            case Canvas.LEFT:

                if (currentX > 0) {

                    currentX--;

                    repaint((currentX + 1) * dx, currentY * dy, dx, dy);

                    repaint(currentX * dx, currentY * dy, dx, dy);

                }else{

                    pantallaH--;

                }

                break;

            case Canvas.RIGHT:

                if (currentX < (rcols - 1)) {

                    currentX++;

                    repaint((currentX - 1) * dx, currentY * dy, dx, dy);

                    repaint(currentX * dx, currentY * dy, dx, dy);

                }else{

                    pantallaH++;

                }

        }

        visRect_inout[0] = currentX;

        visRect_inout[1] = currentY;

        visRect_inout[2] = dx;

        visRect_inout[3] = dy;

        //Se actualiza el contenido de txt1, de la clase hoja.

        hoja1.settexto(data[currentY][currentX]);

        return true;

    }

Se utiliza setText para almacenar en el arreglo data una cadena de caracteres que corresponde al valor de la celda actual.

    public void setText(String text) {

        if (text.compareTo("")==0){

            text=null;

        }

        data[currentY][currentX] = text;       

        currentY--;

        repaint(currentY * dx, currentX * dy, dx, dy);

    }

Cuando en el menú se selecciona la opción "Nuevo", la clase hojaC llama al método limpiar, el cual recorre toda la tabla eliminando cualquier valor que ésta tenga y actualizando la celda.

    public void limpiar() {

        for(int y=0;y<rrows;y++)

            for(int x=0;x<rcols;x++){

                data[y][x] = null;

                repaint(y * dx, x * dy, dx, dy);

            }

    }

Las funciones getdato, getText y getDatos se emplean para obtener el valor de la celda actual, de una celda determinada y de toda la tabla, respectivamente.

    protected String getdato(){

        return data[currentY][currentX];

    }

    protected String getText(int x, int y){

        return data[y][x];

    }

    protected String[][] getDatos(){

        return data;

    }

Al seleccionar la opción "Valor" del menú se asigna a modo el valor para admitir cualquier carácter y se crea una instancia de Texto, mostrándola en pantalla.

    public void commandAction(Command c, Item i) {

         if (c==cmdAny){

             modo=TextField.ANY;

            Texto txt1 = new Texto(data[currentY][currentX], this, display, modo);

            display.setCurrent(txt1);

         }

    }

Por efecto de claridad y espacio, se han omitido algunos fragmentos del código de la clase Tabla que se han considerado innecesarios en la explicación del funcionamiento de la clase, aunque eso no significa que no sean vitales para la operación real de la misma.

6.3.3.Clase Texto

La clase Texto extiende a la clase TextBox, motivo por el cual debe importar  javax.microedition.midlet.MIDlet, aunque no sea una clase ejecutable por sí sola. Se utiliza una clase Tabla, una clase Display para mostrar el TextBox, los comandos CMD_OK y CMD_CANCEL para insertar el texto en la tabla o cancelar la modificación y una variable String que habrá de almacenar el texto ingresado.

package hoja;

import javax.microedition.lcdui.*;

import javax.microedition.midlet.MIDlet;

public class Texto extends TextBox implements CommandListener {

    private final static Command CMD_OK = new Command("OK",                                                                                                               Command.OK,1);       

    private final static Command CMD_CANCEL = new Command("Cancelar",

                                                                                      Command.CANCEL,1);

    private Tabla parent;

    private Display display;

    private String text;

El constructor toma como parámetros una cadena de texto, una clase Tabla que es la que lo llama, una clase Display donde se ha de mostrar ca caja de texto y una variable entera modo, esta variable entera está pensada para la posible utilización de varios formatos de cadenas de texto (numérico, carácter, correo electrónico, etc.). Los valores de los parámetros tomados se asignan a los objetos con el mismo nombre declarados en esta clase.

    public Texto(String text, Tabla parent, Display display, int modo) {

        super("Ingrese valor", text, 64, modo);

        this.parent = parent;

        this.display = display;

        this.text=text;

        addCommand(CMD_OK);

        addCommand(CMD_CANCEL);

        setCommandListener(this);

    }

El método commandAction utiliza el método setText de la clase Tabla (parent en este caso) y regresa display a dicha clase si se selecciona el comando CMD_OK, de los contrario no utiliza setText.

    public void commandAction(Command c, Displayable d) {       

        if (c == CMD_OK) {

            parent.setText(getString());

            display.setCurrentItem(parent);

        } else

            if (c == CMD_CANCEL) {

                display.setCurrentItem(parent);

            }

    }

}

6.3.4.Clase funcion

Al igual que las hojas de cálculo para computadoras de escritorio, ésta permite la utilización de fórmulas para realizar operaciones, ya sea con valores constantes o con valores introducidos dentro de las celadas de la hoja. El formato para la utilización de fórmulas en hojaC es el siguiente:

operación(operando1, operando2, ... , operando n)

Donde los operandos pueden ser números enteros, celdas de la hoja (A1, B3, C4, por ejemplo) u otras funciones y operación se sustituye con el símbolo correspondiente a la operación que se desee realizar, tales símbolos se muestran en la tabla 6.1.

Tabla 6.1. Símbolos utilizados en las funciones de la hoja de cálculo.

SímboloFunciónSintaxisEjemplo
+Suma.+(op1, op2, ... , opn)+(A1,2,8,B4)
-Resta (A-B).-(op1, op2)-(356,*(A1,3))
*Multiplicación.*(op1, op2, ... , opn)*(4,5,2,1,3)
/División (A/B)./(op1, op2)/(25,5)
<Mínimo.<op1, op2, ... , opn)<(32,1,0,A2)
>Máximo.>op1, op2, ... , opn)>(C3,A2,B1,5)
%Tanto por ciento (A * B%).%(op1, op2)%(100,25)
:Promedio.:(op1, op2, ... , opn):(10,9,8,7,6)
@Potencia.@(base, potencia)@(9,2)
  = Igualación.=(Valor)=(B2)

A continuación se explica el código fuente de esta clase:

Se utilizará una variable data, la cual es una arreglo de String (igual al data de la clase Tabla), para tener acceso a todos los valores de la hoja, debido a que una función puede hacer referencia al valor de cualquier celda.

    private static String data[][];

    int lev=0;

    public funcion(String[][] data1){

        data=data1;

    }

La función getDato toma como parámetro el nombre de una celda (A1, B2, etc.), lo transforma a coordenadas en la tabla ([1][2], por ejemplo) para después obtener el valor contenido en esa localidad, ese valor es el que esta función devuelve como cadena de caracteres.

    private String getDato(String celda){

        int x,y;

        String a;

        char b;

        try{

            a=celda.substring(1);

            y=Integer.parseInt(a);

            b=celda.charAt(0);

            x=Character.digit(b,Character.MAX_RADIX)-9;

        }catch (Exception e){

            return "Error";

        }

        if ((x<1)||(y<1)||(x>24)||(y>65))

            return "Error";

        a=data[y-1][x-1];

        if (a==null||a.compareTo("")==0)

            return "0";

        return data[y-1][x-1];

    }

Los valores de las celdas que empleará una operación (suma, resta. división etc.) se almacenan en un objeto de la clase Vector, lo más similar en Java a los arreglos dinámicos de C++, de manera que primero se deben introducir todos los datos al vector y después se procesan según lo solicite la fórmula.

En la suma se convierten a enteros todos los términos del vector y se suman los resultados de tal conversión. Si alguno de los términos no se puede convertir (no es la representación de un entero) la función devuelve la constante -32767 que se ha designado como error.

    private int suma(Vector datos){

        int result=0,R=0;

        for(int x=0;x<datos.size();x++){

            try{

                R=Integer.parseInt(datos.elementAt(x).toString());

            }catch (Exception e){

                return -32767;

            }

            result+=R;

        }

        return result;

    }

La resta se puede realizar únicamente entre dos elementos enteros, si el vector tiene más elementos o si alguno de ellos no se ha podido convertir a entero, se devuelve la constante de error (-32767), de lo contrario la resta se realiza y se devolverá el resultado.

    private int resta(Vector datos){

        int result,r=0,R=0;

        int s=datos.size();

        if(s==2){

            try{

                R=Integer.parseInt(datos.elementAt(0).toString());

                r=Integer.parseInt(datos.elementAt(1).toString());

            }catch (Exception e){

                return -32767;

            }

            result=R-r;

        }else{

            result=-32767;

        }

        return result;

    }

El proceso de la igualación es muy sencillo, sólo se verifica que en el vector exista un único elemento y se regresa ese valor, o -32767 de no ser así.

    private String igual(Vector datos){

        if(datos.size()==1){

            return datos.elementAt(0).toString();

        }else{

            return "Error";

        }

    }

El proceso de multiplicación es igual al de la suma, se convierten los elementos a entero y se procesan (multiplican, en este caso) acumulativamente.

    private int multi(Vector datos){

        int result=1,R=0;

        for(int x=0;x<datos.size();x++){

            try{

                R=Integer.parseInt(datos.elementAt(x).toString());

            }catch (Exception e){

                return -32767;

            }

            result*=R;

        }

        return result;

    }

Así como la suma y la multiplicación siguen el mismo procedimiento, la resta y la división también lo hacen, con las obvias diferencias en la operación medular.

    private int divi(Vector datos){

        int result,r=0,R=0;

        if(datos.size()==2){

            try{

                R=Integer.parseInt(datos.elementAt(0).toString());

                r=Integer.parseInt(datos.elementAt(1).toString());

            }catch (Exception e){

                return -32767;

            }

            result=R/r;

        }else{

            result=-32767;

        }

        return result;

    }

Para calcular máximos y mínimos se utiliza la misma función, incluyendo el parámetro de entrada mm, el cual habrá de tomar un valor de 'm' si se trata de un mínimo o 'M' si lo que hay que calcular es el máximo. Al igual que el resto de las operaciones, el máximo y el mínimo también devuelven la constante de error si los datos del vector no se pueden convertir a enteros.

    private int maxmin(Vector datos, char mm){

        int M=-32767,x,B;

        try{

            M=Integer.parseInt(datos.elementAt(0).toString());

        }catch(Exception e){

            return -32767;

        }   

        for (x=1;x<datos.size();x++){

            try{

                B=Integer.parseInt(datos.elementAt(x).toString());

            }catch(Exception e){

                return -32767;

            }

            if(mm=='M')

                if(M<B){

                    M=B;

                }

            if(mm=='m')

                if(M>B){

                    M=B;

                }

        }

        return M;

    }

El cálculo del tanto por ciento también utiliza sólo dos valores, el primero es el valor base y el segundo el tanto por ciento a calcular.

    private int pciento(Vector datos){

        int result,r=0,R=0;

        if(datos.size()==2){

            try{

                R=Integer.parseInt(datos.elementAt(0).toString());

                r=Integer.parseInt(datos.elementAt(1).toString());

            }catch (Exception e){

                return -32767;

            }

            result=R/100 * r;