Hola hola taringuer@s de toda America y del mundo!!!
Palabras "clave" o pertinentes para tener en cuenta resaltadas en rojo obscuro
En este post veremos como crear una aplicación en java con una interfaz gráfica decente, enteramente x código y cuando digo esto me refiero a que esta vez no usaremos el asistente de creación de GUI (Graphical User Interface) de NetBeans.
Comencemos desde 0!
Tanto en NetBeans como en Eclipse como en cualquier IDE (Integrated Development Enviroment) en el que acostumbres codificar lo primero que nos aparece en un proyecto nuevo (si es que nos abre una plantilla de proyecto java) es la clase principal y el método principal (main).
A nuestra clase de ejemplo le llamaremos "Frame"
public class Frame{
public static void main (String [] args){
}
}
Bien, hasta aquí nada fuera de lo común, tenemos un nuevo proyecto creado pronto para la acción.
Lo que haremos será "extender" nuestra clase "Frame" de JFrame, es decir haremos que nuestra clase "herede" todos los métodos y propiedades de la clase JFrame.
Esto lo haremos x una cuestión de prolijidad al momento de codificar, para ahorrarnos código y x que es la manera óptima de crear un jFrame.
Lo único que debemos hacer es agregar al "identificador" (nombre) de nuestra clase el código
=======================
extends JFrame
=======================
Si no hicieramos que nuestra clase heredara de JFrame deberíamos crear un objeto de esa clase y cada vez que quisieramos modificar alguna propiedad a la ventana deberíamos referirnos a ese objeto.
De cualquier manera nos vemos obligados a importar el paquete que contiene a dicha clase.
Y nuestra clase debería quedarnos así:
import javax.swing.JFrame;
public class Frame extends JFrame{
public static void main (String [] args){
}
}
Bien, ahora debemos crear un "constructor" para definir el proceso de inicialización de nuestra clase una vez que es instanciada, es decir, cuando creemos un "objeto" de nuestra clase, lo primero en ser ejecutado será todo lo que allí se encuentre (en nuestro caso la creación de la interfaz gráfica), la "invocación" a nuestro constructor se llevará a cabo solamente cuando con el operador new instanciemos a nuestra clase.
el constructor se define de la siguiente manera, como no le pasaremos ningún parámetro, los paréntesis estarán vacíos:
=================================
public Frame(){
}
===============================
y así estaría la clase "completa":
import javax.swing.JFrame;
public class Frame extends JFrame{
public Frame(){
//Constructor
}
public static void main (String [] args){
//Método principal
}
}
Bien, antes de crear ninguna ventana, lo que haremos será crear un método sin retorno (void) y será en ese método donde crearemos nuestro frame y nuestros componentes, este método luego será llamado en el constructor de nuestra clase.
Todo esto de crear un método para iniciar la ventana y los componentes también es x una cuestión de prolijidad y buenos hábitos al codificar en java.
A nuestro método podemos llamarle inicio y lo haremos inmediatamente después del constructor y antes del main:
Así que este será nuestro método (como no le pasaremos parámetros los paréntesis irán vacíos):
=======================================================
public void inicio(){
//Aqui crearemos la ventana y agregaremos los componentes
}
=======================================================
Y hasta ahora nuestra clase debería verse así:
import javax.swing.JFrame;
public class Frame extends JFrame{
public Frame(){
inicio(); //"Llamada al método"
}
public void inicio(){
//Aqui crearemos la ventana y agregaremos los componentes
}
public static void main (String [] args){
}
}
Primero crearemos una variable de tipo "Container", la que igualaremos al método getContentPane()con lo que con menos lineas de código nos será mas fácil agregar componentes a nuestra ventana.
ContentPane es (a partir de la versión 1.5 de java) el contenedor x defecto de las aplicaciones swing, de otra manera podríamos agregar un "sub contenedor" como ser un JPanel, pero en este caso agregaremos los componentes directo sobre el JFrame:
=============================
import javax.awt.Container; //importación para el contenedor
...
Container contenedor = getContentPane();
...
=============================
Damos tamaño a nuestro Frame usando el método:
=========================
setBounds(x,y,Width,Heigth);
=========================
x=posición horizontal en pantalla(en pixeles)
y=posición vertical en pantalla(en pixeles)
Width=ancho
Heigth=altura
Este método recibe como parámetros 4 valores de tipo int, el primero la posición horizontal en pantalla, el segundo la posición vertical, esto tomando en cuenta el vértice superior izquierdo de nuestra ventana.
Pero como mas adelante centraremos x defecto la ventana, en los parámetros referidos a la posición le daremos valores en 0.
El tercer parámetro refiere al ancho que tendrá nuestra ventana, y el último al ancho que le daremos a la misma.
X ejemplo para hacer que nuestra ventana tenga x defecto el tamaño de 200 píxeles de ancho x 300 de alto debería ser así:
=============================
setBounds(0,0,200,300);
=============================
Luego, debemos indicar la acción que tomará el botón de cerrar creado x defecto en las ventanas de java que son iniciadas con decoración (barra de titulos del sistema operativo):
Como queremos que nuestra aplicación se cierre al presionar ese botón, ingresamos el siguiente código:
=================================
setDefaultCloseOperation(EXIT_ON_CLOSE);
=================================
EXIT_ON_CLOSE es solo una constante de tipo int x lo que sería lo mismo poner el número 3, que representa la acción de cerrar la aplicación al pulsar el botón pero x una cuestión de prolijidad y buenos hábitos ingresamos la constante.
Existen otras acciones que puede tomar nuestro botón, pero x el momento solo queremos cerrar.
Luego deberíamos anticiparnos y centrar la ventana en la pantalla del ordenador, de otra manera aparecería en la posición x=0 y=0 (arriba del todo a la izquierda).
Para centrar el frame haremos uso del método que designa la relación de la ventana relativa a otro componente, como queremos que esté en el centro le pasaremos un parámetro null:
====================================
setLocationRelativeTo(null);
====================================
Podemos ya mismo ponerle un título a la ventana, y será este el título del JFrame, el método para dar nombre a la ventana recibe como párametro una variable de tipo String x lo q para dar título solo debemos usar el método:
==========================
setTitle(String s);
==========================
Uds. pueden poner el título que se les ocurra, yo en este caso pondré "Mi Aplicación":
===========================
setTitle("Mi Aplicación");
============================
Como último paso en la creación de la ventana, debemos instanciar nuestra clase en el método principal, donde a la vez que creamos el objeto, ya mismo le pasamos el método que hará que nuestra ventana sea Visible:
============================
new Frame().setVisible(true);
============================
Con lo cual en este momento nuestra clase debería verse así:
import javax.swing.JFrame;
import javax.awt.Container
public class Frame extends JFrame{
public Frame(){
inicio(); //"Llamada al método"
}
public void inicio(){
Container contenedor = getContentPane();
setBounds(0, 0, 200, 300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setTitle("Mi Aplicación");
}
public static void main (String [] args){
new Frame().setVisible(true);
}
}
Así que si en este punto damos "run" ya deberiamos ver nuestra ventana:
Hasta aquí nuestra ventana está pronta, solo que aún no tiene ningún componente que la haga útil, en este caso lo que haremos será una calculadora muuuuuy sencilla, solo a modo de ejemplo.
Primero que nada, antes de añadir componente alguno debemos definir un "layout" para nuestro Frame.
ya que x defecto los JFrame´s traen un layout de tipo "BorderLayout", si ponemos un solo componente con este layout, el componente tomará el tamaño absoluto de la ventana.
layout es un sistema de organización de posición espacial de componentes, es decir, como se organizarán nuestros componentes en el Frame, en nuestro caso, lo que haremos será dar nosotros cada una de las coordenadas y tamaño a nuestros componentes, para lo que le "pasaremos" a nuestro JFrame un layout(null), siempre en inicio();
================================
setLayout(null);
================================
Para dar tamaño y/o locación espacial a nuestros componentes debemos antes entender las coordenadas espaciales que se manejan en un JFrame:
Cada vez que hablemos de el eje x, nos estaremos refiriendo a la coordenada horizontal en nuestro frame, tomando como origen (punto 0) el límite de la izquierda (sin importar el limite vertical).
Y cada vez que nos referimos al eje y, hablamos de las coordenadas verticales (sin importar las horizontales).
Es decir, cada vez que creamos un componente, debemos darle locación espacial y tamaño como lo hicimos con el Frame, con el método:
========================================
setBounds(x,y,Ancho,Alto);
========================================
Los componentes toman como referencia para su locación el vértice superior izquierdo:
X ejemplo un botón ubicado en la posición x=100 , y=100 y con un tamaño de 80 px de ancho y 30 de alto, en nuestra aplicación se vería así:
Este es un ejemplo de un JFrame con el mismo tamaño del nuestro, al que x medio de la clase Graphics le "dibujé" las medidas, para entender como se posiciona un componente.El código de este ejemplo al final del post.
Entendiendo esto, a nuestra calculadora le vamos a agregar, tres JButton´s, y tres jTextfields.
Nuestro ejemplo será solo con el fin de comprender como agregar componentes y su relación, además de x supuesto aprender a agregarle "acciones" a los mismos:
Nuestra calculadora contará con tres campos de texto (JTextFields) y tres botones (JButtons).
Como usualmente en una aplicación tal vez deseemos modificar nuestros componentes o su comportamiento en distintas partes del código, vamos a crear nuestras variables de componentes como atributos de nuestra clase principal.
============================================
JTextField campo,campo1,campo2;
JButton boton,boton1,boton2;
============================================
Como verán, solo creamos las variables, para inicializarlas en nuestro método inicio().
import javax.swing.JFrame;import javax.swing.JButton;
import javax.swing.JTextField;
import javax.awt.Container;
public class Frame extends JFrame{
JTextField campo,campo1,campo2;
JButton boton,boton1,boton2;
public Frame(){
inicio(); //"Llamada al método"
}
public void inicio(){
Container contenedor = getContentPane();
setBounds(0, 0, 200, 300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(null);
setLocationRelativeTo(null);
setTitle("Mi Aplicación");
}
public static void main (String [] args){
new Frame().setVisible(true);
}
}
Y ahora debemos inicializar cada componente pasandole en el método setBounds() a cada uno la posición que ocuparán y el tamaño, además también llamaremos al método add de nuestro "contenedor" para así agregar los componentes al jFrame.
En este caso las posiciones espaciales y tamaños, se las pasé x capricho, ya Uds. podrán modificar el código a su gusto (esa es la idea).
Para agregar cada uno de nuestros componentes al jFrame, solo debemos ahora en nuestro método inicio, seguir 3 pasos sencillos, inicializarlo, darle posición y tamaño y agregarlo al Frame:
=================================
boton = new JButton("Resultado");
boton.setBounds(10,100,100,30);
contenedor.add(boton);
boton1 = new JButton("+");
boton1 .setBounds(20,45,70,30);
boton1 .setFont(new Font("Arial",1,16);
contenedor.add( boton1 );
boton2 = new JButton("Limpiar");
boton2.setBounds(20,170,80,30);
contenedor.add( boton2);
campo = new JTextField();
campo.setBounds(100,40,70,20);
contenedor.add(campo);
campo1 = new JTextField();
campo1.setBounds(100,65,70,20);
contenedor.add(campo1);
campo2 = new JTextField();
campo2.setBounds(70,140,100,20);
contenedor.add(campo2);
===================================
Bien, nuestra clase ahora debería verse así:
import javax.swing.JFrame;import javax.swing.JButton;
import javax.swing.JTextField;
import javax.awt.Container
public class Frame extends JFrame{
JTextField campo,campo1,campo2;
JButton boton,boton1,boton2;
public Frame(){
inicio();
}
public void inicio(){
Container contenedor = getContentPane();
boton = new JButton("Resultado");
boton.setBounds(10,100,100,30);
contenedor.add(boton);
boton1 = new JButton("Suma");
boton1 .setBounds(20,45,70,30);
contenedor.add( boton1 );
boton2 = new JButton("Limpiar");
boton2.setBounds(20,170,80,30);
contenedor.add( boton2);
campo = new JTextField();
campo.setBounds(100,40,70,20);
contenedor.add(campo);
campo1 = new JTextField();
campo1.setBounds(100,65,70,20);
contenedor.add(campo1);
campo2 = new JTextField();
campo2.setBounds(70,140,100,20);
contenedor.add(campo2);
setBounds(0, 0, 200, 300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(null);
setLocationRelativeTo(null);
setTitle("Mi Aplicación");
}
public static void main (String [] args){
new Frame().setVisible(true);
}
}
2 cosas:
-Parecerá una bobada lo de hacer un método para agregar los componentes, pero es una manera óptima de hacerlo.
-Por una cuestión de orden todo lo referido al Frame lo dejamos para el final.
Hasta este momento si damos "Run" nuestra aplicacion debería verse así:
Hasta ahora lo único que nos resta es darle función a cada uno de nuestros botones...
Para esto lo que haremos será agregar a cada botón un "ActionListener", método que lleva a su vez como parámetro una clase que al ser instanciada de manera "anonima" como además es "abstracta" nos obliga a "sobreescribir" el método ActionPerformed, que a su vez lleva cómo parámetro un ActonEvent.
Si, suena complicado, pero x Ej. para agregar una acción a un botón el código queda así:
==============================================
boton.addActionListener(new java.awt.event.ActionListener() {
@ Override //Anotación de sobreescritura de método
public void actionPerformed(ActionEvent ae) {
//Acá lo que queramos que haga el botón
}
});
==============================================
En nuestro ejemplo lo que haremos será aprovechar al máximo nuestro botón para el operador(+,-,*,/), y esto lo haremos con un simple switch, aprovechando que a partir de la versión 1.7 de java el switch puede hacerse en base a una variable de tipo String.
Lo que hará nuestro switch es indicarle al botón que cuando su texto sea "+", si lo presionamos pondrá "-", cuando dice "-" si lo presiomamos dirá "x", cuando diga "x" si lo presionamos dirá "%" y por último cuando dice "%" si lo presionamos volverá a decir "+".
Para poner texto en un botón se utiliza el método setText(String s); que lleva un String x parámetro:
==================================
boton.setText("+");
==================================
Para obtener el texto actual de un botón usamos getText();
Por Ej. para imprimir el dato:
===================================
System.out.println(boton.getText());
===================================
Lo que logramos con esto es que luego, en la acción del botón de mostrar resultado, analizaremos el texto que muestra el botón de operador y dependiendo de lo que devuelva será el resultado que mostraremos.
En la acción de nuestro boton1 el switch puede ser así:
====================================
boton1.addActionListener(new java.awt.event.ActionListener() {
@ Override
public void actionPerformed(ActionEvent ae) {
switch (boton1.getText()) {
case "+":
boton1.setText("-");
break;
case "-":
boton1.setText("x");
break;
case "x":
boton1.setText("%");
break;
case "%":
boton1.setText("+");
break;
}
}
});
=======================================
Y así logramos de manera ciclíca cambiar el operador ("x", y "%" son fantasía eh! los operadores verdaderos son * y /).
Bien, entonces, x una cuestión de prácticidad al codificar en el método ActionPerformed del botón de selección de operación creamos dos variables de tipo float (para tomar en cuenta si el usuario quiere operar con decimales) y las igualamos al texto correspondiente a cada campo de texto.
Para obtener un valor de un campo de texto es igual que con los botones, por Ej:
========================================
float num=campo.getText();
=========================================
Pero cómo lo que nos devuelve el método getText() es un String, la anterior linea de código nos marcaría error, motivo x el cual debemos "parsearla" (convertirla de String a float) de la siguiente manera:
=========================================
float num = Float.parseFloat(campo.getText());
=========================================
Noten que no resalté en verde a num, ya que será una variable local del método ActionPerformed de nuestro JButton boton
y para obtener el valor del siguiente campo lo mismo:
.
=========================================
float num1 = Float.parseFloat(campo1.getText());
=========================================
Entonces en este boton (boton) nuestro switch puede mostrar dependiendo lo que devuelva boton1, una operación distinta, x ejemplo si boton1 dice "+", en campo2 mostraremos num+num.
Pero como el método setText() recibe como parámetro un String la manera correcta de mostrar el dato sería usando el método String.valueOf(), el cual recibe cualquier variable númerica como parámetro y la convierte en String:
===============================================
switch(boton.getText()){
case "+":
campo2.setText(String.valueOf(num+num1));
break;
}
===============================================
El switch completo de nuestro boton (boton) podría quedarnos así:
======================================================
boton.addActionListener(new java.awt.event.ActionListener() {
@ Override
public void actionPerformed(ActionEvent ae) {
float num = Float.parseFloat(campo.getText());
float num1 = Float.parseFloat(campo1.getText());
switch (boton1.getText()) {
case "+":
campo2.setText(String.valueOf(num + num1));
break;
case "-":
campo2.setText(String.valueOf(num - num1));
break;
case "x":
campo2.setText(String.valueOf(num * num1));
break;
case "%":
campo2.setText(String.valueOf(num / num1));
break;
}
}
});
======================================================
Y lo único que nos resta es darle acción al boton "limpiar", el cual solo borrará el texto de los 3 campos y le devolverá el foco al primero de ellos (cuestion de practicidad para el usuario), borramos los campos pasandole como parámetro al método setTex() un parámetro null y el foco se le da a un componente con componente.requestFocusInwindow():
======================================================
boton2.addActionListener(new java.awt.event.ActionListener() {
@ Override
public void actionPerformed(ActionEvent ae) {
campo.setText(null);
campo1.setText(null);
campo2.setText(null);
campo.requestFocusInWindow();
}
});
======================================================
Y hasta aquí nuestra clase debería verse así:
import java.awt.Container;
importjava.awt.event.ActionEvent;
importjavax.swing.JButton;
importjavax.swing.JFrame;
importjavax.swing.JTextField;
public class Frame extends JFrame {
JTextField campo, campo1, campo2;
JButton boton, boton1, boton2;
public Frame() {
inicio();
}
private void inicio() {
Container contenedor = getContentPane();
boton = new JButton( "Resultado" ) ;
boton.setBounds(10, 100, 100, 30);
contenedor.add(boton);
boton.addActionListener(new java.awt.event.ActionListener() {
@ Override
public void actionPerformed(ActionEvent ae) {
float num = Float.parseFloat(campo.getText());
float num1 = Float.parseFloat(campo1.getText());
switch (boton1.getText()) {
case "+":
campo2.setText(String.valueOf(num + num1));
break;
case "-":
campo2.setText(String.valueOf(num - num1));
break;
case "x":
campo2.setText(String.valueOf(num * num1));
break;
case "%":
campo2.setText(String.valueOf(num / num1));
break;
}
}
});
boton1 = new JButton( "+" ) ;
boton1.setBounds(20, 45, 50, 30);
contenedor.add(boton1);
boton1.addActionListener(new java.awt.event.ActionListener() {
@ Override
public void actionPerformed(ActionEvent ae) {
switch (boton1.getText()) {
case "+":
boton1.setText("-");
break;
case "-":
boton1.setText("x");
break;
case "x":
boton1.setText("%");
break;
case "%":
boton1.setText("+");
break;
}
}
});
boton2 = new JButton("Limpiar");
boton2.setBounds(20, 170, 80, 30);
contenedor.add(boton2);
boton2.addActionListener(new java.awt.event.ActionListener() {
@ Override
public void actionPerformed(ActionEvent ae) {
campo.setText(null);
campo1.setText(null);
campo2.setText(null);
campo.requestFocusInWindow();
}
});
campo = new JTextField();
campo.setBounds(100, 40, 70, 20);
contenedor.add(campo);
campo1 = new JTextField();
campo1.setBounds(100, 65, 70, 20);
contenedor.add(campo1);
campo2 = new JTextField();
campo2.setBounds(70, 140, 100, 20);
contenedor.add(campo2);
setBounds(0, 0, 200, 300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(null);
setLocationRelativeTo(null);
setTitle("Mi Aplicación");
}
public static void main(String[] args) {
new Frame().setVisible(true);
}
}
X si alguien se lo pregunta, si, podría haber agregado un Event Dispach Thread en el main, pero es una aplicación tan sencilla que no lo consideré necesario.
El Event Dispach Thread (EDT) en java es un hilo de ejecución especial propio de la clase awt que hace que la acción de los botones corra en un hilo aparte del main, pero aún no lo entiendo bien y he probado varias aplicaciones sin el y corren bien...
=================================================
...
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@ Override
public void run() {
new Frame().setVisible(true);
}
});
}
===========================================
Y todo está pronto para correr nuestra aplicación y hacer algunas cuentas!
Bueno, espero que les sea útil y haber sido claro, hasta aquí la aplicación es realmente básica, x lo que deben tener en cuenta que se le pueden hacer infinidad de mejoras, estéticas y funcionales.
Casi me olvido, aquí el código del Frame de ejemplo de locación de componentes:
http://pastebin.com/MRpvLF5F
Otros posts propios sobre el tema que pueden interesarte:
Gracias x la visita, como siempre puntos, dudas, comentarios o sugerencias bienvenidos!!!
Además acepto sugerencias sobre el tema de mi próximo post sobre este maravilloso lenguaje!!!