sábado, 24 de julio de 2010

Dialogos Modales y No Modales en Java SE 6. Novedades Swing.

Saludos compañeros nos vemos otra vez aquí, para compartir ideas y experiencias de Java. Esta vez le toca el turno a los diálogos modales, mostraremos que conforma el nuevo API de Java SE 6 para crear diálogos modales y evitar diálogos modales, empecemos:

En versiones anteriores de Java ya existía una forma de crear diálogos modales a través del parámetro boolean (modal o no) del constructor de la clase java.awt.Dialog, este parámetro al ser verdadero (true) bloqueaba toda la aplicación y hasta que ella se cerrara no se podía seguir con la aplicación. Veamos un pequeño en ejemplo con este constructor:



package mipaquete;
import javax.swing.*;

public class Marco {
   public static void main(String[] args) {
      JFrame f = new JFrame("");
      f.setAlwaysOnTop(true);
      f.setSize(300,300);
      f.setVisible(true);
      JDialog d = new JDialog(f, "Dialog", true);
      d.setSize(300,300);
      d.setLocationRelativeTo(null);
      d.setVisible(true);
   }
}


Si se ejecuta este párrafo anterior se muestra un Frame y un Diálogo, hasta que no sea cerrado el diálogo no puede tomar el foco el Frame, es decir no tengo acceso a él.

Si tengo una aplicación en la cual deseo que no toda la aplicación sea bloqueada sino sólo parte de ella, puedo hacer uso del nuevo API de Java 6 donde se incluyen dos clases estáticas dentro de la clase Dialog, estas son:


static class ModalityType y static class ModalExclusionType, ambas tienen dentro de sí una enumeracion que indican el nivel de bloqueo que pueden generar.

static class ModalityType, esta clase sirve para definir el tipo de bloqueo, o también llamado alcance del bloqueo en una aplicación, su enumeración contiene:

APPLICATION_MODAL: Bloquea todas las ventanas de la aplicación, excepto las ventanas que se deriven de ella, es decir, las ventanas que estén abiertas para el momento en que se muestra este diálogo quedan bloqueadas, pero si a partir de ese momento se crean nuevas ventanas (ventanas hijas) estas no serán bloqueadas.

DOCUMENT_MODAL: Bloquea la ventana padre de la ventana y su jerarquía superior, es decir bloque las ventanas de donde fue derivada, pero esto no incluyen las ventanas que no pertenecen a esta jerarquía por lo tanto algunas ventanas quedarán sin bloquear.

MODELESS: no bloquea ninguna ventana.

TOOLKIT_MODAL: Bloquea las ventanas de nivel superior que corren en el mismo TOOLKIT.

Podemos excluir del bloqueo a una ventana o diálogo utilizando la clase ModalExclusionType, esta puede excluir del bloqueo según alguna de estas opciones de su enumeración:

APPLICATION_EXCLUDE: La ventana que utiliza este valor de enumeracion no será bloqueada por ningún diálogo de la aplicación.

NO_EXCLUDE: Indica que la ventana no estará excluida del bloqueo si este ocurriera.

TOOLKIT_EXCLUDE: Indica que no se bloqueará esta ventana si se llamase a APPLICATION_MODAL o TOOLKIT_MODAL.

Lo que nos faltaría por ver son los métodos agregados en Java SE 6.0 que nos permiten utilizar estas enumeraciones:

En la clase java.awt.Dialog se incluyeron los métodos setModalityType() que recibe un Dialog.ModalityType para definir el alcance del bloqueo, también se agregó el método getModalityType(), entre otros.

En la clase Windows se incluyó el método setModalExclusionType() que recibe un elemento de la enumeracion ModalExclusionType para definir que tipo de exclusión será aplicada, también se cuenta con el método getModalExclusionType(), el uso es obvio.

También se agregaron los constructores public Dialog(Window owner, String title,Dialog.ModalityType modalityType) y public Dialog(Window owner, String title, Dialog.ModalityType modalityType, GraphicsConfiguration gc), GraphicsConfiguration queda fuera del alcance de este artículo.

Ahora si estamos listos para crear nuestra aplicación con Ventanas y Diálogos modales con las nuevas modalidades Java SE 6.0. Veamos:


package modality;
import java.awt.*;
import java.awt.Dialog.*;
import java.awt.event.*;
import javax.swing.*;

public class ClaseModal {

static JFrame padre;

public static void main(String[] args) {
      padre = new JFrame("Marco1");
      padre.setLayout(new BorderLayout());
      JButton btnNuevaVentana =
        new JButton("Boton Mostrar Hija");
      JLabel etiqueta = new JLabel("Etiqueta");
      etiqueta.setBackground(Color.BLUE);
      padre.add(etiqueta, BorderLayout.CENTER);
      padre.add(btnNuevaVentana, BorderLayout.SOUTH);
      btnNuevaVentana.addActionListener(
        new ActionListener(){
         @Override
         public void actionPerformed(ActionEvent e) {
              JDialog dialogoAuxiliar =
                  new JDialog(padre, "hija",
                    Dialog.ModalityType.DOCUMENT_MODAL);
                  JButton btnSaveAs =
                    new JButton("Save as...");
                  btnSaveAs.addActionListener(
                    new ActionListener(){
                  @Override
             public void actionPerformed(ActionEvent e) {
//Disminucion de sanguia por cuestiones de espacio
               FileDialog fileDialog =
                 new FileDialog(padre,
               "Guardar", FileDialog.SAVE);
               fileDialog.setSize(400,400);
               fileDialog.setVisible(true);
                  }
                  });
                  dialogoAuxiliar.add(btnSaveAs);
                  dialogoAuxiliar.setSize(300,300);
                  dialogoAuxiliar.setLocation(150,150);
                  dialogoAuxiliar.setVisible(true);
   }
});
padre.setSize(300,300);
padre.setVisible(true);
JFrame ventanaIndependiente =
new JFrame("Ventana Independiente");
ventanaIndependiente.setModalExclusionType(
Dialog.ModalExclusionType.APPLICATION_EXCLUDE);
ventanaIndependiente.setSize(500,500);
ventanaIndependiente.setLocation(75, 75);
ventanaIndependiente.setVisible(true);
}
}


Al ejecutar esta aplicación se obtienen 2 ventanas, una ventana Padre y una Independiente. Al hacer click en el botón Mostrar Hija, se desplega una ventana hija que bloquea la padre (DOCUMENT_MODAL), la ventana hija tiene un único botón que llama un FileDialog (Cuadro de dialogo Guardar), este Cuadro de diálogo tampoco bloquea la ventana independiente ya que utiliza a Dialog.ModalExclusionType.APPLICATION_EXCLUDE, si se utilizara Dialog.ModalExclusionType.NO_EXCLUDE o simplemente no se asigna ningún ModalExclusionType, entonces la ventana independiente se bloquearía con el cuadro de diálogo SAVE.

Bueno, espero haberme explicado y este código les ayude a resolver dudas o inquietudes que se les hayan podido encontrar, cualquier pregunta tenemos los comentarios abiertos, para ampliar el tema, o resolver dudas, por mi parte me despido hasta una próxima edición.

lunes, 31 de mayo de 2010

El cálculo de PI con 500 decimales

Saludos de nuevo compañeros. Hoy tenemos un nuevo reto el cálculo de PI hasta de 500 decimales, esto como respuesta a un comentario que fue realizado en el post sobre Interfaces Genéricas, si ya sabemos, no tiene nada que ver, pero en ese post fue realizada la duda y en este post UNA posible solución.

Muchos métodos han sido utilizados para calcular el Valor de PI, hoy en día existen algoritmos que generan con gran precision este valor, En la clase Math existe una constante PI de tipo double que contiene el valor literal


public static final PI = 3.141592653589793d;


Pero por supuesto, es insuficiente para cálculos avanzados. No nos da mayor precisión, allí termina.

De hecho cuando he trabajado con los valores primitivos double he tenido dificultades para obtener mayor precisión, soy "todo oído" para soluciones.

Pero gracias a Dios contamos con la clase BigDecimal que permite obtener mayor precisión en el cálculos numérico con decimales, a pesar de que los cálculos son mas limitados.

Si quiere consultar la clase BigDecimal haga clic aqui.

En esta clase contamos con un constructor que recibe un MathContext que en su constructor recibe un parámetro que dice cual es la precisión con la que queremos trabajar en este caso yo paso 500 así:


MathContext mc = new MathContext(500);


Aunque en realidad me da mayor precisión que esa.

Ahora bien es hora de hablar de métodos de cálculo de PI.

Primero que todo debemos hablar de la Serie de Taylor.

Esta propone una serie para el cálculo de el arcotangente:



Siendo así podemos programar un método que calcule el arcotangente de un valor con la Serie de Taylor:


private static BigDecimal serieArcotangente
(double x, int iteraciones) {
MathContext mc = new MathContext(500);
BigDecimal result = new BigDecimal(0.00, mc);
BigDecimal uno = new BigDecimal(1.00);
for(int i = 0; i < iteraciones; i++){
double div = (2.00*i+1.00) *
(Math.pow(1.00/x, 2.00 * i + 1.00));
BigDecimal divisor = new BigDecimal(div);
if(i % 2 == 0){
result = result.add(uno.divide(divisor, mc));
}else{
result = result.subtract(uno.divide(divisor, mc));
}
}
return result;
}


Explicando un poco este método, tenemos que retorna un BigDecimal para mayor precision de PI, tenemos MathContext de 500 decimales de precision. Inicializamos un BigDecimal en cero pasandole este MathContext y un BigDecimal inicializado en 1.0 que servirá para el cálculo.

Después comenzamos las iteraciones (sentencia for), calculamos un double divisor (divisor de la Serie de Taylor) y lo inicializamos como otro BigDecimal, luego tenemos:


if(i % 2 == 0){
pi = pi.add(uno.divide(divisor, mc));
}else{
pi = pi.subtract(uno.divide(divisor, mc));
}

Que significa que si i es par sumo, sino resto, ya que -1 en la serie está elevada a la n (si n es par se convierte en positivo - se suma - sino es negativo y por lo tanto se resta). Además se divide uno entre el divisor con el MathContext de 500 decimales de precisión.

Una vez hecho esto podemos ver las de arcotangentes desarrolladas por algunos matemáticos para calcular el valor de PI:

Haciendo historia, en 1844, Dase, un calculista ultrarrápido, utilizó otra fórmula del tipo arcotangente para conseguir una aproximación con 200 decimales correctos. La fórmula descubierta por Strassnitzky es:

PI/4 = arctan(1/2) + arctan(1/5) + arctan(1/8)

En programación sería:


public static BigDecimal calculoPiDase(){
BigDecimal pi = serieArcotangente(1.00/2.00, 100)
.add(serieArcotangente(1.00/5.00, 100))
.add(serieArcotangente(1.00/8.00,100))
.multiply(new BigDecimal(4.00));
return pi;
}


El gran matemático alemán, Karl Friedrich Gauss (1777-1855), también descubrió algunas fórmulas similares a las anteriores. Una de las más utilizadas ha sido:

PI/4 = 12*arctan(1/18) + 8*arctan(1/57) - 5*arctan(1/239)

este sería así:


public static BigDecimal calculoPiGauss(){
BigDecimal pi = serieArcotangente(1.00/18.00, 60)
.multiply(new BigDecimal(12.00))
.add(serieArcotangente(1.00/57.00, 60)
.multiply(new BigDecimal(8.00))
.subtract(serieArcotangente(1.00/239.00, 60)
.multiply(new BigDecimal(5.00)))
);
return pi.multiply(new BigDecimal(4.00));
}


Y otra fórmula similar descubierta por Störmer (1896):

PI/4 = 6*arctan(1/8) + 2*arctan(1/57) + arctan(1/239)


public static BigDecimal calculoPiStormer(){
BigDecimal pi = serieArcotangente(1.00/8.00, 60)
.multiply(new BigDecimal(6.00))
.add(serieArcotangente(1.00/57.00, 60)
.multiply(new BigDecimal(2.00))
.add(serieArcotangente(1.00/239.00, 60)));
return pi.multiply(new BigDecimal(4.00));
}


Bueno por si acaso, ahora voy a estructurar bien el codigo:



public class PIPreciso {
public static void main(String[] args) {
System.out.println("DASE: "+calculoPiDase());
System.out.println("GAUSS: "+calculoPiGauss());
System.out.println("STORMER: "+calculoPiStormer());
}

public static BigDecimal calculoPiDase(){
//Este codigo ya fue visto
}

public static BigDecimal calculoPiGauss(){
//Este codigo ya fue visto
}

public static BigDecimal calculoPiStormer(){
//Este codigo ya fue visto
}

private static BigDecimal serieArcotangente
(double x, int iteraciones) {
//Codigo ya visto
}
}


Ya tenemos tres métodos para calcular PI con 500 decimales o mas, espero esto haya podido ayudarte. Cualquier pregunta no dudes en escribir aquí o en cualquier post. Hasta Luego

miércoles, 19 de mayo de 2010

Hablemos de intefaces genéricas de java 5.0

   Así como vimos en un artículo anterior (Enero 2009 - tipos genéricos) podemos crear clases genéricas. Ahora, y como respuesta a un comentario que expuso la duda, hablaremos de interfaces genéricas.
Las interfaces genéricas se declaran de la "misma manera" que una clase genérica, veamos la sintaxis general:


interface nombre-interfaz<T1, T2, T3...>{
metodo1();
metodo2();…
}


Donde T1, T2, T3... son los tipos parametrizados.
Aunque no siempre se así, una clase que implementa esta interfaz debe ser en forma general:



class nombre-clase<T1, T2, T3...>
implements nombre-interfaz<T1, T2, T3...>{
   metodo1Implementado(){}
   metodo2Implementado(){}…
}


Pero como les dije esto no es siempre veamos con un ejemplo cuales son las excepciones:
Supongamos que queremos crear una interfaz para verificar si un valor está contenido en un arreglo, la interfaz sería:


interface Contenedora<T>{
   boolean contiene(T valor);
}

El método contiene debe devolver verdadero o falso en caso de que el arreglo contenga o no el valor.
La clase que implementa puede ser:



class Verificadora<T> implements Contenedora<T> {
T[] datos;

public Verificadora(T[] x){
datos = x;
}

public boolean contiene(T dato) {
for(T valor: datos)
if(valor.equals(dato)) return true;
return false;
}
}


Hagamos un ejemplo con una clase Main:


public class Main {
public static void main(String[] args) {
Integer[] x = {0,1,2,3,4};
Verificadora<Integer>; ver =
new Verificadora<Integer>(x);
if(ver.contiene(2)){
System.out.println("Si tiene 2");
}else{
System.out.println("NO tiene 2");
}
if(ver.contiene(6)){
System.out.println("Si tiene 6");
}else{
System.out.println("NO tiene 6");
}
/*if(ver.contiene(9.333)){ //error porque no es Integer
System.out.println("Si tiene 9.333");
}else{
System.out.println("NO tiene 9.333");
}*/
}
}


La interfaz Contenedora no limita el tipo de dato con que trabajará, puede ser Integer, Double, Float o inclusive String, cualquier Object en general, por lo tanto podemos utilizar el método equals de esta clase (Object), tal como lo hace la clase verificadora en el método contiene().

Ahora veamos varios detalles más con respecto a las interfaces genéricas:

Si una clase implementa una interfaz genérica, la clase debe ser genérica a menos que la interfaz defina el tipo de dato parametrizado con el que va a trabajar. Vamos por parte, si la interfaz no define el tipo de dato parametrizado, entonces la clase debe ser genérica:

class Verificadora<T> implements Contenedora<T>{//Bien


Es correcto ya que la clase utiliza el mismo parámetro de que la interfaz.

Sería un error:

class Verificadora implements Contenedora<T>{
//Error de compilación


Debido a que como la interfaz es genérica la clase debe ser genérica.

Pero si la interfaz trabaja con String, es decir que define el parámetro:

class Verificadora implements Contenedora<String>{//Bien


Entonces la clase no debe ser genérica necesariamente, ya que el tipo de dato lo define la interfaz.

También, así como la clase, tal como lo vimos en el artículo de tipos parametrizados, puedo limitar el tipo de dato con que trabajará la interfaz. En caso que queramos trabajar con números podemos hacer:


interface Contenedora<T extends Number>{
boolean contiene(T valor);
}


Si lo hacemos de esta manera la clase debe implementarse así:

class Verificadora<T extends Number> 
implements Contenedora<T>{//Bien


ya no se requerirá repetir el tipo de dato de la interfaz, de hecho, si se hace se caería en un error de compilación:

class Verificadora<T extends Number> 
implements Contenedora<T extends Number>{//Error

viernes, 30 de abril de 2010

(Parentesis. II Parte) Compartiendo el código Applet

Ahora si llegó la hora de seguir en la onda java, ahora para presentar (como lo prometí en un post anterior), el código del Applet que me permite imprimir un reporte HTML, que me da la ventaja de capturar si el usuario del sistema hizo click en "Imprimir" o "Cancelar" para que se puedan hacer los cambios en la base de datos después que se procedió a imprimir.

Este código también muestra como cambiar la configuración de impresión o también llamado atributos de impresión, por ejemplo los márgenes, todo esto desde código Java, también se utiliza el paquete java.net (la clase URL) que me permite buscar en una dirección remota un recurso, en este caso un archivo, que fue generado y grabado en el servidor.

Como lo expliqué en el post anterior: este código me quedó de una experiencia en mi trabajo para la impresión de cheque, pero que no fue implementado por cambio en los requerimientos del sistema, pero pienso que les puede servir como ejemplo o ayuda a todo aquel que busque información en lo que se refiere a todos los elementos que actúan en el código que ya he mencionado en los párrafos anteriores.

Bueno empecemos con el archivo HTML que contiene el contenido del cheque, este archivo debe ser creado por el programa en el servidor y por lo tanto estar en un directorio del mismo.
el Código es:


package cheque;

import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.MediaPrintableArea;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.print.*;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessControlException;

public class ReporteApplet extends JApplet
implements ActionListener, Printable{
private JEditorPane htmlPane;
private URL initialURL;
private String ipServer;
private String rutaArchivoHtml;
private JButton printButton;
private int margenSuperior, margenIzquierdo,
private int anchoPagina, altoPagina;
private PrintRequestAttributeSet atributosDeImpresion;
private int unidadMedidaImpresion;
public void init() {
super.init();
margenSuperior = (getParameter("margen-superior") != null)?
Integer.parseInt(getParameter("margen-superior")):20;
margenIzquierdo = (getParameter("margen-izquierdo") != null)?
Integer.parseInt(getParameter("margen-izquierdo")):20;
anchoPagina = (getParameter("ancho-pagina") != null)?
Integer.parseInt(getParameter("ancho-pagina")):210;
altoPagina = (getParameter("alto-pagina") != null)?
Integer.parseInt(getParameter("alto-pagina")):297;
ipServer = getParameter("ip-server");
rutaArchivoHtml = getParameter("ruta-archivo");
atributosDeImpresion =
new HashPrintRequestAttributeSet();
unidadMedidaImpresion = MediaPrintableArea.MM;
}
public void start(){
try {
//if ipServer == 127.0.0.1 invocar a localhost
//else invocar 192.168.0.91
if(ipServer != "192.168.0.91" &&
ipServer != null &&
ipServer.trim() != ""){
this.initialURL = new URL
("http://localhost/rutaProyecto/"+
rutaArchivoHtml);
}else{
this.initialURL = new URL
("http://192.168.0.91/rutaProyecto/"+
rutaArchivoHtml);
}
htmlPane = new JEditorPane(initialURL);
} catch (MalformedURLException e) {
warnUser("Ruta de archivo incorrecto");
} catch (AccessControlException e) {
warnUser("no se tiene permiso de abrir archivo");
}catch (IOException e) {
warnUser("Verifique el IP de la maquina.\n
Debe pasar por parámetro el IP correcto del servidor");
}
JPanel buttonPanel = new JPanel();
buttonPanel.setBackground(Color.lightGray);
printButton = new JButton("Imprimir");
printButton.addActionListener(this);
buttonPanel.add(printButton);
getContentPane().add(buttonPanel, BorderLayout.SOUTH);
htmlPane.setEditable(false);
JScrollPane scrollPane = new JScrollPane(htmlPane);
getContentPane().add(scrollPane,
BorderLayout.CENTER);
Dimension screenSize =
Toolkit.getDefaultToolkit().getScreenSize();
int width = screenSize.width * 8 / 10;
int height = screenSize.height * 8 / 10;
setBounds(width/8, height/8, width, height);
setSize(500, 250);
setVisible(true);
}
public void actionPerformed(ActionEvent event){
String command = event.getActionCommand();
if (command.equals("Imprimir")){
this.printComponent();
}
}
private void warnUser(String message){
JOptionPane.showMessageDialog(null, message, "Error",
JOptionPane.ERROR_MESSAGE);
}

public void printComponent(){
print();
}

public void print(){
PrinterJob job = PrinterJob.getPrinterJob();
job.setPrintable(this);
atributosDeImpresion.add(new MediaPrintableArea
(margenIzquierdo, margenSuperior,
anchoPagina, altoPagina, unidadMedidaImpresion));
if (job.printDialog(atributosDeImpresion)){
try{
job.print();
}catch (Exception ex){
System.out.println(ex);
}
}else{
warnUser("Impresión cancelada");
}
}

public int print(Graphics g, PageFormat pf,
int pageIndex) throws PrinterException{
Graphics2D g2 = (Graphics2D)g;
Dimension d = htmlPane.getSize();
double panelWidth = d.width;
double panelHeight = d.height;
double pageHeight = pf.getImageableHeight();
double pageWidth = pf.getImageableWidth();
double scale = pageWidth/panelWidth;
int totalNumPages = (int)Math.ceil(scale *
panelHeight / pageHeight);
//Para asegurar que no imprime página vacía
if(pageIndex >= totalNumPages){
return Printable.NO_SUCH_PAGE;
}
g2.translate(pf.getImageableX()-19,
pf.getImageableY()-19);
g2.translate(0f, -pageIndex*pageHeight);
g2.scale(scale, scale);
htmlPane.paint(g2);
return Printable.PAGE_EXISTS;
}
}


Veamos que quiere decir todo este código. Entre los atributos de inteŕes de la clase ReporteApplet tenemos una referencia JEditorPane que es el que me sirve para mostrar el código HTML del cheque que se va a imprimir. Para definir los atributos de impresión se utilizan una interfaz (PrintRequestAttributeSet) que define los métodos para agregar o definir los atributos de impresión, una clase HashPrintRequestAttributeSet que implementa esta interfaz y una clase final MediaPrintableArea para definir el patrón de medida de los atributos así como usar finalmente estos atributos, aunque en el código esta disperso por cuestiones de orden del applet, la secuencia es:



private PrintRequestAttributeSet atributosDeImpresion;
atributosDeImpresion = new HashPrintRequestAttributeSet();
unidadMedidaImpresion = MediaPrintableArea.MM;
atributosDeImpresion.add(new MediaPrintableArea(
margenIzquierdo, margenSuperior, anchoPagina,
altoPagina, unidadMedidaImpresion));


Con el método add de la interfaz PrintRequestAttributeSet podemos agregar atributos de impresión utilizando la clase HashPrintRequestAttributeSet y MediaPrintableArea y con ello definir los márgenes, ancho y alto de la página así como la unidad de medida de la impresión (MM: milímetros o INCH para pulgadas).

Si se sigue observando el código del Applet se puede ver que éste recibe varios parámetros, estos son:



margenSuperior = (getParameter("margen-superior") != null)?
Integer.parseInt(getParameter("margen-superior")):20;
margenIzquierdo = (getParameter("margen-izquierdo") != null)?
Integer.parseInt(getParameter("margen-izquierdo")):20;
anchoPagina = (getParameter("ancho-pagina") != null)?
Integer.parseInt(getParameter("ancho-pagina")):210;
altoPagina = (getParameter("alto-pagina") != null)?
Integer.parseInt(getParameter("alto-pagina")):297;
ipServer = getParameter("ip-server");
rutaArchivoHtml = getParameter("ruta-archivo");


Todo estos parámetros los debo pasar a través del código del html que tiene el applet de la siguiente manera:



<applet code="cheque.ReporteApplet.class" width="500" height="150">

<param name="margen-superior" value="10">
<param name="margen-izquierdo" value="10">
<param name="ancho-pagina" value="180">
<param name="alto-pagina" value="275">
<param name="ip-server" value="127.0.0.1">
<param name="ruta-archivo" value="applets/cheque.html">

</applet>


Por último y no menos importante del código de ReportEApplet es que cuando se hace click en el botón "Imprimir" se llama al método PrintComponent() este a su vez llama al método print que contiene:


public void print(){
PrinterJob job = PrinterJob.getPrinterJob();
job.setPrintable(this);
atributosDeImpresion.add(new MediaPrintableArea
(margenIzquierdo, margenSuperior,
anchoPagina, altoPagina, unidadMedidaImpresion));
if (job.printDialog(atributosDeImpresion)){
try{
job.print();
//Aqui codigo para cambiar valores en Base de datos
}catch (Exception ex){
System.out.println(ex);
}
}else{
warnUser("Impresión cancelada");
}
}

Donde es creado un PrinterJob y luego llamo a printDialog (Ventana de diálogo de impresión) pasándole como parámetro los atributos de impresión para saber cuales atributos va a utilizar en la Impresión. Si en el diálogo se hace click en "Imprimir" se envía automáticamente a imprimir, de otra forma (si se hace click en cancelar) se muestra un mensaje con el motivo correspondiente.

Bueno por ahora sólo falta el código HTML del cheque se los anexo aquí:



<html>
<head>
<title></title>
<meta content="">
<style>
body {
padding: 0;
margin: 0;
}
.papel{
position: absolute;
width: 18.60cm;
height: 20.70cm;
background: #dddddd;
margin: 0;
padding: 0;
}
.cheque table{
cellspacing: 0;
cellpadding: 0;
}
.cheque *{
font-family: arial;
font-size: 8pt;
font-weight: bold;
}
.cheque{
position: relative;
float: left;
clear:left;
width: 17.60cm;
height: 100px;
font-family: arial;
font-size: 8pt;
font-weight: bold;
background: #ffffff;
border: 2px solid black;
}
</style>
</head>
<body>
<table cellpadding="0" cellspacing="0" >
<tr>
<td width="13.60cm"></td>
<td align="left" height="0.92cm" valign="bottom" style="font-size: 8pt; font-family: arial; font-weight: bold;">2.550,75</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" class="cheque">
<table >
<tr>
<td height="1.30cm"></td>
</tr>
<tr>
<td width="1.50cm" height="0.80cm"><td>BENEFICIARIO DEL CHEQUE DE PRUEBA</td></td>
</tr>
</table>
<table >
<tr>
<td width="1.80cm" height="0.75cm"><td>DOS MIL QUINIENTOS CINCUENTA CON 75/100</td></td>
</tr>
<tr><td height="0.60cm"></td></tr>
</table>
<table >
<tr>
<td width="1.00cm" height="0.90cm"></td><td width="6.00cm">MARACAIBO, 09 DE ABRIL</td><td width="0.50cm">2010</td>
</tr>
</table>

</table>

</body>
</html>

Esto es sólo un ejemplo, pero en la práctica debe crearse un HTML dinámico que cambie los valores para monto, beneficiario, y los demás datos que el cheque necesita.

El archivo anterior HTML, se debe llamar cheque.html y debe estar en una ruta especificada por la clase ReporteApplet. En este caso estamos pasando como ruta del archivo applets/cheque.html y debe estar en la carpeta del servidor:

Aquí esta este detalle de la clase ReporteApplet que indica esto que acabo de mencionar:


//if ipServer == 127.0.0.1 invocar a localhost
//else invocar 192.168.0.91
if(ipServer != "192.168.0.91" &&
ipServer != null &&
ipServer.trim() != ""){
this.initialURL = new URL
("http://localhost/rutaProyecto/"+
rutaArchivoHtml);
}else{
this.initialURL = new URL
("http://192.168.0.91/rutaProyecto/"+
rutaArchivoHtml);
}



Bueno, espero que este código les sirva de modelo para impresión de cheques o cualquier reporte donde deban imprimir mediante ciertos atributos de impresión. Hasta la Próxima.

viernes, 16 de abril de 2010

(Paréntesis) Compartiendo ideas y experiencias...

Saludos de nuevo.

Ahora, voy a compartir una experiencia que tuve en mi trabajo de la cual, como todas las experiencias, fue muy enriquecedora. En mi trabajo no tengo la oportunidad de trabajar mucho en Java, debido a que allí utilizamos php como lenguaje para resolver un Sistema Administrativo para que se adapte a las necesidades de nuestro Instituto INZIT. Por lo tanto en mis ratos libres aprendo un poco más sobre Java, ya que me considero amante a este tecnología.

Hace poco teníamos una duda acerca de cómo debíamos imprimir un reporte, de hecho era como imprimir el cheque, por experiencia pasada sabíamos que un PDF no era buena idea ya que la impresora de matriz de punto que se utiliza para este fin no generaba buenas impresiones PDF en ninguna fuente y también por experiencias pasadas sabíamos que la solución para este tipo de impresora era un archivo HTML en pantalla o texto plano (txt).

Pero había una restricción más para nuestro requerimiento de sistema y era que el cheque debe imprimirse una vez, y por lo tanto debe asegurarse que el cheque sea impreso en la forma adecuada.

La primera investigación que hicimos fue que JavaScript tiene la funcion window.print() que imprime el contenido en la ventana y también document.print() que puedo adaptarlo a que me imprima una sección de la página (como un DIV de HTML, por ejemplo). Ahora bien, las restricciones que vimos es que no había manera de determinar si el usuario presionaba click en "Imprimir" o "Cancelar" y por lo tanto no garantizaba que se realizara la impresión y de ser así para el sistema puede que ya se había impreso o cambiado el estado del cheque (y de otras tablas relacionadas más) por más que el usuario haga click en cancelar.

Al no gustarnos esta solución fuimos buscando otras alternativas, de allí surgió la idea, ¿Podríamos utilizar un applet de Java? Bueno, la idea era buena, que tal si por java podríamos capturar si se manda a imprimir satisfactoriamente o no y de allí cambiar los todas las tablas y los estados de nuestra base de datos para garantizar de que en verdad se mandó a imprimir y por la impresora correcta.

Bueno, la duda surgió un viernes y se esperó el fin de semana. Al otro lunes ya teníamos la solución en verdad el Applet lo puede hacer, de hecho, puedo capturar un archivo HTML, cargarlo en un JEditorPane mostrar el cuadro de diálogo imprimir y verificar si el presionó sobre Imprimir o Cancelar.

Total empezamos a desarrollarlo y a cuadrarlo con las dimensiones de los distintos formatos de cheques que tiene la nuestra empresa. Y en tres días pudimos hacer que nuestra aplicación PHP generara un archivo HTML en disco duro servidor, conectar en Applet con este archivo a través de un URL, mostrarlo en el JEditorPane y mandarlo a imprimir cuadrando con las dimesiones de todos los cheques. Eureka estaba listo!.

Bueno, pero no todo fue color de rosa.

Si por una parte podíamos hacer que se visualizara el HTML en el JEditorPane, este editor de Java no soportaba las sentencias más recientes de CSS como posicionado absoluto, muy importante para darle una ubicación exacta y no relativa, por lo tanto, cuadrarlo dio mucho trabajo.

Pero después de tenerlo hecho se nos presentó una idea nueva, nuestro jefe por experiencia en desarrollo de Sistemas Administrativos dijo que si era posible imprimir más de una vez el cheque si éste a pesar que se mandó a imprimir en forma satisfactoria, en realidad se mandó a imprimir en otra impresora que no era la de matriz de punto, esto trajo una variable nueva a nuestro sistema, ya no era necesario detectar si se daba click en Imprimir en cancelar, después de todo los datos del cheque ya estaban generado y se podía imprimir de nuevo si así el usuario lo necesitaba, así lo que que hicimos fue que siempre se iba a imprimir el cheque por PHP y JavaScript, pero sólo se imprimía de nuevo el cheque con una autorización del Jefe del Departamento Administrativo.

Bueno total, me quedé con el código Java, con la clase Applet que permite capturar un archivo HTML, presentarlo en pantalla a través de un JEditorPane y poder imprimir, inclusive configurando desde códigos las configuración de impresión como márgenes y otros.

En el próximo post explicaré con detalle el código que pude desarrollar a ver si a cualquiera de ustedes les sirve. Por ahora, lo más importante que quiero compartir es que cuando se tenga una idea de software de cómo desarrollar cierta aplicación hay que ver y discutir todas las opciones, es decir, después de recaudar los requerimientos de sistema, ver qué se debe desarrollar: ver las ventajas, desventajas de cada opción y sobretodo no programar "linealmente" cómo fue solicitado, ya que de seguro vendrán cambios en la aplicación mas adelante, el reto, para nosotros los desarrolladores es detectar los elementos claves del sistema y desarrollar en base a esto y después que el usuario quiera un cambio, este no debe afectar al desarrollo normal del sistema, lo que significa que si la lógica del negocio no cambia, el cambio debe ser adaptable en poco tiempo, ya que los elementos raíces se mantienen.

No intento relatar una idea nueva, esta idea ya ha sido expuesta en varias metodologías de desarrollo de software, sólo intento compartir una experiencia que me sucedió y que todavía estoy procesando, creo que la programación va más allá de resolverle a un usuario, la programación es detectar la lógica del negocio, para que éste mismo sistema pueda funcionar en distintos ambientes, empresas, negocios y usuarios, por ello nuestra labor será trascendente cuando nuestro trabajo lo sea y pueda resolver y adecuarse a las distintas pruebas.

Espero que esta idea les sea de utilidad y empecemos a detectar que necesita el hombre de nuestros días y a partir de ellas crear ideas... ¡Hasta la próxima!

Para ver la segunda parte de este artículo haga click aquí:
Paréntesis. II Parte. Compartiendo Código Applet

miércoles, 31 de marzo de 2010

Novedades en el paquete javax.swing de java SE 6

Hola a todos de nuevo, es un placer para mí estar aquí de nuevo compartiendo acerca de las novedades que trae java 6. Ahora le toca el turno al paquete javax.swing.

Observando las clases directamente de la página de Sun Microsystem, tanto del JAVA SE 5 como del JAVA SE 6, puede verse que en este último fueron agregadas las siguientes clases:

DefaultRowSorter
DefaultRowSorter.Model
DefaultRowSorter.Wrapper

GroupLayout

JList.DropLocation
JTable.DropLocation
JTree.DropLocation

LayoutStyle

RowFilter
RowFilter.Entry
RowSorter
RowSorter.SortKey

SwingWorker

Para este artículo se explicará los fundamentos básicos para realizar interfaces gráficas con GroupLayout. Sin mas que esperar, empecemos:

Este administrador de disposición trabaja con eje vertical y horizontal separadamente. Cuando se ha definido la colocación de los componentes en uno de los ejes ya está la mitad del problema resuelto, se pasa luego a definir la colocación del mismo componente en el otro eje, si, esto quiere decir que cada componente debe estar presente tanto en el eje vertical como en el horizontal de no ser así el GroupLayout lanza una exception.

Este administrador de esquema puede organizar los componentes o secciones en forma secuencial o paralela: Secuencial significa uno al lado para el eje horizontal y uno arriba del otro para el vertical.

El paralelo significa colocar en una misma línea (vertical u horizontal) los elementos que necesite. Tanto si utiliza Secuencial como Paralelo se pueden agregar a ellos tanto componentes como nuevos grupos.

Veamos un ejemplo para aclarar las dudas que se hayan ir podido planteando hasta ahora:

Supongamos que queremos crear la siguiente interfaz gráfica:



Espero explicarme con estas dos imágenes a continuación; primero veamos la distribución horizontal:



Las líneas oscuras (negras) superiores reflejan la distribución mas externa de ese panel, como puede verse existe una disposición secuencial, de izquierda a derecha, por lo tanto si creamos primero en GroupLayout myLayout, lo primero que debemos hacer es definir esta distribución, de la siguiente manera:


myLayout.setHorizontalGroup( myLayout.createSequentialGroup());


Luego, se va definiendo o agregando el código de las secciones mas internas, por ejemplo, la primera sección contiene una distribución paralela de dos componentes, primero codificamos la sección paralela:


myLayout.setHorizontalGroup( myLayout.createSequentialGroup()
  .addGroup( myLayout.createParallelGroup())
);



Luego los componentes:


myLayout.setHorizontalGroup( myLayout.createSequentialGroup()
  .addGroup( myLayout.createParallelGroup()
    .addComponent(lblNombre)
    .addComponent(lblEmail)
)
);



Como hemos dicho, cada sección puede contener tanto componentes como nuevos grupos, esto quiere decir que pueden haber grupos anidados, es decir, grupos dentro de otros grupos.
Un ejemplo de esto es cuando queremos colocar los JCheckBox. La sección horizontal media contiene un nuevo grupo, tal como lo muestra el código que sigue:



.addGroup(myLayout.createParallelGroup()
  .addComponent(txtNombre)
  .addComponent(txtEmail)
  .addGroup(myLayout.createSequentialGroup()
    .addGroup(myLayout.createParallelGroup()
      .addComponent(chkActualizacion)
//etc... mas adelante está el código completo esto es sólo para explicación
)
);



Pasemos ahora a definir nuestra distribución vertical:



En este caso puede verse también una distribución secuencial (de arriba hasta abajo) y en este caso un poco más sencillo debido a que cada sección contiene directamente los componentes, es decir, no hay subdivisiones o grupos anidados.

El código fuente final de el GroupLayoutExample queda de la siguiente manera:

GeSHi © 2004-2007 Nigel McNie, 2007-2009 Benny Baumann, 2008-2009 Milian Wolff
  1. package ejemploblog;

  2.  

  3. import javax.swing.*;

  4.  

  5. public class GroupLayoutExample extends JPanel{
  6.  
  7.         JLabel lblNombre = new JLabel("Nombre: ");
  8.         JLabel lblEmail  = new JLabel("email: ");
  9.         JTextField txtNombre = new JTextField(20);
  10.         JTextField txtEmail = new JTextField(20);
  11.         JCheckBox chkActualizacion = new JCheckBox("Actualizaciones");
  12.         JCheckBox chkComentarios = new JCheckBox("Comentarios");
  13.         JCheckBox chkCodigo = new JCheckBox("Codigo Fuente");
  14.         JCheckBox chkInfo = new JCheckBox("Informacion");
  15.         JButton btnAceptar = new JButton("Aceptar");
  16.         JButton btnOmitir = new JButton("Omitir");
  17.         public GroupLayoutExample(){
  18.                 GroupLayout myLayout = new GroupLayout(this);
  19.                 setLayout(myLayout);

  20.                 //Crear espacio entre componentes
  21.                 myLayout.setAutoCreateGaps(true);
  22.                 //Crear espacio entre componente contenedor
  23.                 myLayout.setAutoCreateContainerGaps(true);
  24.                
  25.                 myLayout.setHorizontalGroup( myLayout.createSequentialGroup()
  26.                             .addGroup(myLayout.createParallelGroup()
  27.                                         .addComponent(lblNombre)
  28.                                         .addComponent(lblEmail)
  29.                             )
  30.                             .addGroup(myLayout.createParallelGroup()
  31.                                         .addComponent(txtNombre)
  32.                                         .addComponent(txtEmail)
  33.                                         .addGroup(myLayout.createSequentialGroup()
  34.                                                         .addGroup(myLayout.createParallelGroup()
  35.                                                                                 .addComponent(chkActualizacion)
  36.                                                                                 .addComponent(chkComentarios)
  37.                                                                 )
  38.                                                         .addGroup(myLayout.createParallelGroup()
  39.                                                                         .addComponent(chkCodigo)
  40.                                                                         .addComponent(chkInfo)
  41.                                                         )
  42.                                         )
  43.                             )
  44.                             .addGroup(myLayout.createParallelGroup()
  45.                                         .addComponent(btnAceptar)
  46.                                         .addComponent(btnOmitir)
  47.                             )

  48.                         );
  49.                 myLayout.setVerticalGroup(myLayout.createSequentialGroup()
  50.                         .addGroup(myLayout.createParallelGroup()
  51.                                         .addComponent(lblNombre)
  52.                                         .addComponent(txtNombre)
  53.                                         .addComponent(btnAceptar)
  54.                         )
  55.                         .addGroup(myLayout.createParallelGroup()
  56.                                         .addComponent(lblEmail)
  57.                                         .addComponent(txtEmail)
  58.                                         .addComponent(btnOmitir)
  59.                         )
  60.                         .addGroup(myLayout.createParallelGroup()
  61.                                         .addComponent(chkActualizacion)
  62.                                         .addComponent(chkCodigo)
  63.                         )
  64.                         .addGroup(myLayout.createParallelGroup()
  65.                                         .addComponent(chkComentarios)
  66.                                         .addComponent(chkInfo)
  67.                         )
  68.                 );
  69.         }      

  70. }

  71.  

Parsed in 0.176 seconds at 12.76 KB/s


Para crear la clase ejecutable (main), sería:

GeSHi © 2004-2007 Nigel McNie, 2007-2009 Benny Baumann, 2008-2009 Milian Wolff
  1. package ejemploblog;
  2.  
  3. import java.awt.BorderLayout;
  4.  

  5. import javax.swing.JFrame;
  6.  
  7. public class Aplicacion {
  8.  
  9.        
  10.         public static void main(String[] args) {
  11.                 JFrame marco = new JFrame("Suscripcion Acerca de Java");
  12.                 marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  13.                 marco.getContentPane().add(new GroupLayoutExample(), BorderLayout.CENTER);
  14.                 marco.pack();
  15.                 marco.setVisible(true);
  16.  
  17.         }
  18. }

Parsed in 0.109 seconds at 3.64 KB/s


Bueno, esto es todo por ahora, espero haberme explicado en este artículo, si quieren un Tutorial completo (en inglés) sobre GroupLayout, busca en esta dirección:

How to Use GroupLayout

Donde hay inclusive otro ejemplo.

Hasta la próxima!

viernes, 19 de marzo de 2010

Java 6. Derby, la base de datos incluida en jdk 6

   Saludos compañeros y gracias por estar de nuevo aquí aprendiendo juntos un poco más sobre lo que es la tecnología Java en su última version de Java SE 6.

   Ahora le toca el turno a Derby: Base de datos relacional y transaccional que viene incluido por defecto en el JDK 6.0, más específicamente lo puede encontrar en la ruta de java. En windows: C:\Archivos de Programa\Java\jdk1.6.0\db, o en general en la carpera db donde se encuentra la instalación de java SE 6.

   Para hablar un poco de dónde viene Derby, esta se inicia como CloudSpace: una base de datos relacional creada por Informix, compañía que fue adquirida en 2001 por IBM. en 2004 IBM decide convertir a CloudSpace en software libre cambiando su nombre a Derby.
La página principal de Derby es: http://db.apache.org/derby/.

   Las ventajas que promete Derby son sus mínimas necesidades de configuración y administración y el pequeño espacio que ocupa en disco. Otra ventaja es que Derby soporta el almacenamiento de una base de datos archivada en un archivo JAR, lo que lo hace fácil de incluirlo y distribuirlo en nuestra aplicación.

   Visto esto, empecemos a ver cómo incluir esta base de datos en nuestra aplicación Java.

   Objetando un poco: Aunque se dice que Derby viene incluido con el JDK 6.0, en las últimas instalaciones que he realizado no ha sido instalado por defecto, por lo tanto lo descargué directamente de db-derby-10.5.3.0-bin.zip.

   Una vez descargado se descomprime en la carpeta que se desee (supongamos C:\derby). Ahora se agrega al PATH (variable de entorno) la ruta interna bin (es decir C:\derby\bin). En esta carpeta se encuentran dos archivos esenciales: ij.bat y startNetworkServer.bat. Abrimos una consola y ejecutamos primero el startNetworkServer.bat (en la ruta C:\derby\bin) con esto se levantará el servidor de base de datos Derby.

   Una vez hecho esto hay que dirigirse en una consola nueva a la carpeta de nuestro proyecto (preferiblemente) para crear nuestra base de datos nueva.
Una vez ubicado en la ruta de nuestro proyecto java se escribe ij.bat, se iniciará la aplicación ij que es la herramienta para conectarse, crear y manipular base de datos Derby.
NOTA IMPORTANTE: La creación de una base de datos Derby agrega una carpeta con el nombre de la base de datos al sistema en la ruta donde fue ejecutada la aplicación ij, por ello es recomendable dirigirse a la carpeta donde se encuentra nuestro proyecto y ejecutar allí esta herramienta.


C:\RutaProyecto\ij.bat
Version ij 10.5
ij>


   Ahora a la linea ij> se debe agregar algo como:

ij> connect 'jdbc:derby:NombreBD;create=true';



NOTA: No olvide colocar el punto y coma al final de cada sentencia derby, ya que esto indica que finaliza la misma, si no lo hace no se ejecuta la sentencia escrita.

   Con esto se crea una base de datos Derby de nombre NombreBD en la ruta donde se ejecutó el ij.bat. Podemos ahora crear una tabla, insertar registros y seleccionarlos:


ij> CREATE TABLE persona(id integer, nombre varchar(15), apellido varchar(15), edad integer);
0 filas insertadas/actualizadas/suprimidas
ij>INSERT INTO persona VALUES(1, 'Rafael', 'Romero', 31),(2, 'Orlando', 'Gracia', 42),(3, 'Yessica', 'Alba', 35);
3 filas insertadas/actualizadas/suprimidas
ij> SELECT * FROM persona;
//Se muestran los datos ingresados
3 filas seleccionadas


   Por defecto la ejecución automática está activada (autocommit ON), se puede controlar escribiendo en ij> autocommit ON; o autocommit OFF;

   Para salir de la herramienta ij se escribe exit;

   Una vez visto esto veamos como podemos enlazar esta base de datos a nuestra aplicación.
  1. import java.sql.*;
  2.  
  3. public class Derbiando {
  4.  
  5.         public static void main(String[] args) {
  6.                 Derbiando dbDerby = new Derbiando();
  7.                 dbDerby.mostrarDatos();
  8.         }
  9.  
  10.         public void mostrarDatos(){
  11.                 try{
  12.                         String driver = "org.apache.derby.jdbc.EmbeddedDriver";
  13.                         Class.forName(driver).newInstance();
  14.                         Connection conn = DriverManager.getConnection("jdbc:derby:C:\\RutaProyecto\\NombreBD");
  15.                         Statement s = conn.createStatement();
  16.                         ResultSet rs = s.executeQuery("SELECT * FROM persona");
  17.                         while(rs.next()){
  18.                                 System.out.println("id: "+rs.getInt(1));
  19.                                 System.out.println("nombre: "+rs.getString(2));
  20.                                 System.out.println("apellido: "+rs.getString(3));
  21.                                 System.out.println("edad: "+rs.getInt(4));
  22.                                 System.out.println();
  23.                         }
  24.                         rs.close();
  25.                         s.close();
  26.                         conn.close();
  27.                 }catch (Exception e) {
  28.                         e.printStackTrace();
  29.                 }
  30.                        
  31.                
  32.         }
  33. }



Salida Generada:
id: 1
nombre: Rafael
apellido: Romero
edad: 31

id: 2
nombre: Orlando
apellido: Gracia
edad: 42

id: 3
nombre: Yessica
apellido: Alba
edad: 35