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