====== Usabilidad y accesibilidad =====
Los conceptos de usabilidad o experiencia de usuario y accesibilidad tienen una importante relacion con las interfaces de usuario.
{{ :bloque3:usabilidad-error.png?direct&230|}}
El estandar internacional [[https://www.iso.org/standard/64764.html|ISO/IEC 25000]] sobre los requisitos y la evaluación de calidad del software indica que la **usabilidad** //se refiere a la capacidad de un software de ser comprendido, aprendido, usado y ser atractivo para el usuario, en condiciones específicas de uso.//
La accesibilidad se refiere a que las aplicaciones software, las herramientas y las tecnologías estén diseñadas y desarrollados para que las personas con discapacidad puedan usarlas. Algunas características de los usuarios ante las que deben estar adaptadas las interfaces gráficas pueden ser: {{:bloque3:accesibility.jpg?direct&200 |}}
* Discapacidades visuales: desde baja visión hasta ausencia completa.
* Discapacidades de movimiento: dificultad para mover un ratón o pulsar varias teclas al mismo tiempo.
* Deficiencias auditivas: desde ser capaz de oír algunos sonidos pero no distinguir las palabras habladas, hasta ausencia de audición.
* Trastornos Cognitivos y del Lenguaje: Es difícil la exposición compleja o inconsistente, o la selección pobre de palabras usando ordenadores para estos usuarios.
Nuestras interfaces gráficas deben ser diseñadas atendiendo a cuqluier tipo de usuario.
===== Principios de usabilidad y accesibilidad=====
Aunque los dos conceptos no son idénticos, tienen ciertos aspectos comunes. A continuación se enumeran algunos:
* **Identificación de controles**: Un usuario necesita identificar para qué sirve cada control de la interfaz y qué información hay que introducir o seleccionar en cada caso. Debemos acompañar etiquetas o mensajes desplegables para identificar de forma visual la finalidad del control o la información que el usuario debe introducir.Esta identificación se debe hacer mediante texto antes que mediante imágenes, aunque no impide el uso de imágenes para complementar la identificación. {{ :bloque3:accesibilidad-usabilidad.jpg?direct&300|}}
* **Distribución de controles**: Una interfaz gráfica debe ofrecer una estructura o distribución de los controles que resulte comprensible para el usuario. Asociar o agrupar los controles atendiendo a su finalidad, u ordenarlos dependiendo de su importante o frecuencia de uso, son algunos ejemplos.
* **Sencillez de la interfaz**: Las interfaces gráficas más modernas persiguen la sencillez debido a varias razones: pantallas más pequeñas, mejora de la usabilidad y experiencia de usuario y división de subinterfaces por funcionalidad. Para ello se utilizan pestañas que nos permiten cambiar entre vistas de información o botones que muestran una nueva ventana de la aplicación. El objetivo es no saturar al usuario con multitud de controles dentro de una misma sección.
* **Acceso a controles frecuentes**: las opciones de uso más habitual no deben tener un acceso rápido dentro de nuestra interfaz gráfica, y que no se alberguen con mucha profundidad dentro de menús o secciones.
* **Uso del teclado**: facilitar el acceso eficiente del teclado a todas las funciones de la aplicación. Algunos usuarios pueden ser incapaces de usar un ratón, y muchos «usuarios avanzados» prefieren usar el teclado de todos modos.
* **Opciones para deshacer**: permitir deshacer acciones, o mantener un historial de acciones realizadas para poder corregir o volver al estado original ante posibles accesiones no intencionadas.
* **Asistencia desde la interfaz**: mostrar mensajes de ayuda que indiquen al usuario qué puede hacer, o cómo debe usar ciertos controles. Impedir el uso incorrecto de la aplicación ante posibles errores.
* etc...
En la [[https://developer.gnome.org/accessibility-devel-guide/stable/index.html.es|guía de accesibilidad para desarrolladores de GNOME]] se indican muchas orientaciones sobre estos aspectos.
===== Aspecto Look&Feel =====
El //look and feel// de una interfaz gráfica en Java se encarga del aspecto de los componentes gráficos. Como Java es un lenguaje multiplataforma por defecto utiliza una estética independiente del sistema en el que se ejecute la aplicación.
Para modificar esta estética utilizamos la clase **UiManager** junto con su método ''setLookAndFeel()'', que recibe un ''String'' en el que se indica el //lookandfeel// que se quiere emplear.
En caso de modificar el //look and feel// de una aplicación, **se debe establecer al inicio del método //main()// que inicie la aplicación**.
{{ :bloque1:lookandfeel.png?direct&400 |}}
> Diferentes aspectos look & feel
==== Look&Feel dependiente o independiente del sistema ====
La estética //look and feel// por defecto se conoce como //cross-platform look and feel// también llamada //metal look and feel//. Pero en algunos casos quizas nos interese utilizar el //look and feel// del sistema en el que se ejecuta la aplicación.
//Usar cross-platform L&F, ó metal L&F
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
//Usar el look and feel del sistema anfitrión
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName();
*El método ''setLookAndFeel()'' lanza excepciones que debemos controlar con un try-catch.
==== Otros Look&Feel ====
También podemos modificar el //look and feel// mediante el pasando como parámetro el String que lo identifica. Los distintos aspectos que podemos usar son:
* **Metal o Cross-Platform**: Clase ''javax.swing.plaf.metal.MetalLookAndFeel''
* **Windows**: Clase ''com.sun.java.swing.plaf.windows.WindowsLookAndFeel''
* **Windows Classic**: Clase ''com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel''
* **Nimbus**: Clase ''javax.swing.plaf.nimbus.NimbusLookAndFeel''
* **CDE/Motif**: Clase ''com.sun.java.swing.plaf.motif.MotifLookAndFeel''
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
//UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
//UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
//UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
//UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
===== Iconos intuitivos =====
Los botones de la mayoría de aplicaciones, no son botones que contengan texto, sino que contienen iconos. Del mismo modo los elementos de los menús, aunque si que contienen texto, también indican su función mediante un icono.
Debemos seleccionar las imágenes que contienen dichos botones de forma que representen las funciones que realizan.
{{ :bloque3:intuitive-icons.jpg?200 |}}
===== Etiquetas ToolTips de ayuda =====
{{ :bloque3:tooltip.png?direct&200|}}
Un **tooltip** es una ayuda visual que nos permite insertar un texto a modo de explicación o ayuda sobre un componente o control de la aplicación. Este texto aparecerá automáticamente, al pasar el ratón por encima del componente.
JButton btnMensajeError = new JButton("Abrir fichero");
//Incluimos una ayuda tooltip sobre el boton
btnMensajeerror.setToolTipText("Opción de abrir fichero");
También podemos indicarlo desde el diseñador de WindowBuilder:
{{ :bloque3:settooltipwindowbuilder.png?direct&250 |}}
===== Control de errores de entrada de usuario =====
Podemos advertir al usuario cuando se produzcan errores mediante dialogos ''JOptionPane''. Es muy útil poder notificar errores de entrada cuando se interactua con la aplicación:
* Al introducir un tipo de datos erroneo (Strings en lugar de double, p.e.)
* Al dar de alta un objeto, pero no se han introducido todos lo datos
* Al eliminar objetos que no existen o no están seleccionados
* Al salir de la aplicación sin guardar datos
* Cuando se cargan datos de un fichero que no existe
* etc ...
Para que nuestra interfaz sea robusta, no podemos permitir que errores no controlados interrumptan la ejecución estable de nuestra aplicación.
===== Acceso por teclado =====
Como se ha comentado al inicio del bloque, una interfaz gráfica debe facilitar el manejo por teclado, por accesibilidad y ademas también por velocidad.
==== Accesos Mnemonicos ====
{{ :bloque3:mnemonickeys.png?direct&250|}}
Puedo realizar acciones utilizando teclas mnemónicas. Estas teclas se usan manteniendo la tecla //ALT// + tecla mnemónica. Es la forma mas sencilla de acceso por teclado y se puede asociar a elementos de __menú__ y a __botones__.
{{:bloque3:mnemonicwindowbuilder.png?direct&230 |}}
**Para poder usar el acceso mnemónico es necesario ver el boton, o la opción del menú**.
Si está en otra sección de la aplicación y no está a la vista no puedo usar su acceso mnemónico.
Se pueden añadir mediante código o desde las propiedades de WindowBuilder:
JButton btnNuevoUsuario = new JButton("Nuevo Usuario");
btnNuevoUsuario.setMnemonic(KeyEvent.VK_N);
Ahora puedo realizar la accion de pulsar el botón, mediante las teclas ''ALT + N''. Java __subraya__ la letra del acceso mnemónico.
====Aceleradores de Menu====
{{ :bloque3:acceleratorwindowbuilder.png?direct&200|}}
En el caso de las opciones anidades dentro de un ''JMenu'', tenemos otra opción mas práctica que las teclas mnemónicas: los //aceleradores de menú//. Aunque en la documentación se les da ese nombre, son básicamente atajos del teclado, pero solo se pueden aplicar a elementos del menú (''JMenuItem'', ''JMenuCheckBoxItem'', etc).
{{:bloque3:menuaccelerator.png?direct&100 |}}
La diferencia con las teclas mnemónicas es que permite ejecutar la acción dsede cualquier lugar de nuestra aplicación, independientemente de si el menú está desplegado o no.. Una vez configurada se muestra una indicación al lado del ''JMenuItem''.
Se pueden añadir tanto desde WindowBuilder como mediante código:
//Pulsando CTRL + N
mntmNuevoUsuario.setAccelerator(
KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK));
==== Atajos del teclado para acciones ====
Si queremos tener la misma funcionalidad que los aceleradores, pero para los botones, la opción que tenemos es definir acciones mediante las clases ''Action'' y ''AbstractAction''. Es habitual cuando desde nuestra aplicació podemos realizar las mismas acciones desde los elementos de un Menú y desde los botones de la barra de herramientas.
JButton boton= new JButton();
Action accionBoton = new AbstractAction("Refrescar") {
@Override
public void actionPerformed(ActionEvent evt) {
System.out.println("Refrescando...");
}
};
String key = "Refrescar";
button.setAction(buttonAction);
buttonAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_R);
button.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0), key);
button.getActionMap().put(key, buttonAction);
==== Botones por defecto ====
{{ :bloque3:defaultbutton.png?direct&200|}}
Los botones por defecto son aquellos que están //seleccionados// cuando usamos una aplicación, y responden a una tecla (habitualmente //Enter//).
Solo puede haber un boton por defecto en cada contenedor de alto nivel.
JButton btnAdd = new JButton("Añadir");
//Indico el boton por defecto
getRootPane().setDefaultButton(btnAdd);
==== Seleccionar Texto ====
Es habitual que al pulsar sobre un campo de texto se seleccione el texto que contiene de modo que es más fácil modificarlo: el texto seleccionado se borra al escribir cualquier caracter. También ocurre cuando pulsamos la tecla "Tabulación" para pasar de un campo de texto a otro.
Para implementar esta funcionalidad sobre un campo de texto, debemos implementar un manejador de eventos de tipo Foco: ''FocusListener''. El //foco// se denomina a la propiedad que hace que algo este seleccionado. Cuando seleccionamos un campo de texto con el ratón, este campo gana el foco, y cuando pulsamos sobre otro campos de texto, el campo anterior pierde el foco.
La interface ''FocusListener'' tiene dos métodos: ''focusGained()'' y ''focusLost()'', que se ejecutan cuando un elemento gana o pierde el foco.
public class Controlador implements FocusListener{
. . .
miCampodeTexto.addFocusListener(this);
. . .
//Cuando mi componente gana el foco, realizo la operacion
@Override
public void focusGained(FocusEvent evt) {
//Si el componente que gana el foco es mi campo de texto
if(evt.getSource() == miCampoDeTexto){
miCampoDeTexto.selectAll();
}
}
@Override
public void focusLost(FocusEvent evt) { }
}
===== Deshacer acciones =====
La opción de //deshacer// también es algo habitual en las aplicaciones que usamos. Podemos deshacer el último cambio realizado, o una lista de cambios.
Para ello debemos de crear una estructura de datos que almacene el cambio realizado. Puede ser la creación o la eliminación de un objeto, por ejemplo. En el momento de realizar la acción debemos guardar en esa estructura el tipo de cambio realizado y el objeto que hemos modificado, creado o borrado.
De este modo si queremos deshacer la acción, podemos recuperar el estado anterior de nuestra aplicación.
//Ejemplo de clase para almacenar modificaciones
public class Modificación{
private String accion;
private Object objetoModificado;
public Modificacion(String accion, Objecto objeto){
this.accion = accion;
this.objetoModificado = objeto;
}
Las modificaciones las podemos guardar en una lista o guardar solo la última modificación. De este modo podemos recuperar el estado anterior y saber qué se hizo.
===== Control de acceso de usuarios =====
Si queremos que nuestra aplicación controle el acceso de los usuarios, debemos crear una ventana de //login//. Suelen consistir en un pequeño cuadro de diálogo en el que se permite introducir un nombre usuario y una contraseña.
La lógica del control de usuarios es bastante sencilla, basta con controlar si el usuario coincide con la contraseña especificada. Podemos controlar un solo usuario, o varios usuarios dentro de una lista, por ejemplo.
Para mejorar la funcionalidad sería recomendable que nuestra aplicación permitiera crear usuarios y su contraseña. Incluso crear usuarios con diferente rol (administrador y usuario estándar), dejando ciertas operaciones solamente disponibles para ciertos roles.
Algunas indicaciones para la creación de ventana de login:
{{ :bloque3:logindialog.png?direct&170|}}
- Usar ''JDialog'' en lugar de ''JFrame'' para esta ventana
- Hacer un JDialog modal, ya que así impide el acceso al resto de la aplicación
- Usar un ''JTextField'' para el nombre y un ''JPasswordField'' para la contraseña
- Si el login es correcto se cierra (''dispose()'')
- Si el login es incorrecto, se indica el error y la ventana se mantiene
- Si se presiona //Cancelar//, se termina la aplicacion (''System.exit()'')
- Es conveniente quitar la barra de título y sus botones (''undecorated'')
Un tipo de implementación sencilla podría ser
public class VentanaLogin extends JDialog{
//Declaro dos constantes con las credenciales
private static final String USUARIO = "admin";
private static final String PASS = "admin";
public VentanaLogin(){
//cuando es modal, la ejecución se detiene
//cuando se cierra el dialogo, continua la ejecución del programa
setModal(true);
//inicialización de componentes gráficos
. . .
//elimino la barra de título
setUndecorated(true);
//Listener para botón de Login
loginButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//Si la contraseña coincide, cierro la ventana
if(comprobarUsuario(txtUser.getText(), passwordField.getPassword())) {
dispose();
} else {
//Si no coincide lo indico, pero dejo la ventana
JOptionPane.showMessageDialog(ventana, "Login incorrecto", "Error", JOptionPane.ERROR_MESSAGE);
}
}
});
//Listener boton cancelar
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
//Salgo de la aplicacion
System.exit(0);
}
});
}
. . .
//Devuelvo true si la comprobación es correcta
private boolean comprobarUsuario(String usuario, char[] pass){
if(USUARIO.equals(usuario) && PASS.equals(pass.toString){
return true;
}
return false;
}
}
===== Internacionalización (I18n)=====
//Internacionalizar// (ó I18n) una aplicación consiste en hacerla accesible para distintas regiones. Se pueden internacionalizar las aplicaciones Swing aportando las traducciones de cadenas de caracteres, imágenes, etc., sin necesidad de tener que reconstruir el proyecto; sin tener que compilarlo según el país al que vaya dirigido.
Se puede utilizar esta características empleando ficheros de recursos (ResourceBundle files). En ellos, deberíamos indicar todos los textos visibles, como el texto de etiquetas, campos de texto, botones, etc. Posteriormente modificando el parametro ''Locale'' de nuestra aplicación, se utilizará un fichero de idioma concreto.
==== Clase Locale ====
La clase ''Locale'' se usa para representar la región geográfica y el lenguaje de la plataforma que ejecuta la aplicación. El objeto ''Locale'' suele ser creado a partir de los parámetros de nuestro sistema operativo. Si nuestro sistema indica que esta en //España// y que nuestro lenguaje es el //español//, las aplicaciones en Java utilizarán esos parámetros regionales.
Aun así, se puede modificar manualmente los valores del objeto ''Locale'', siendo la forma más fácil de poder gestionar el idioma en el que se muestra nuestra aplicación.
* **Modificar la localizacion**:
public static void main(String[] args) {
System.out.println(Locale.getDefault()); //Muestra es_ES
Locale.setDefault(Locale.UK);
System.out.println(Locale.getDefault()); //Muestra en_GB
. . .
}
* **Crear un elemento Locale**:
//Obtener Locale actual (obtenido de los parámetros del Sistema)
Locale locale = Locale.getDefault();
//Mediante constructor (español-España)
locale = new Locale("es", "ES");
//Mediante constantes (inglés-USA)
locale = Locale.US;
//Mediante Locale.Builder (francés-Canada)
locale = new Locale.Builder().setLanguage("fr").setRegion("CA").build();
//Modificar la localizacion actual
Locale.setDefault(locale);
//Para acceder a sus propiedades
String idioma = locale.getLanguage(); //fr
String pais = locale.getCountry(); //CA
=== Códigos de región e idiomas ===
Las regiones e idiomas se representan mediante un código de dos caracteres. Las regiones en __MAYÚSCULAS__ y los idiomas en __minúsculas__. A continuación se enumeran algunos códigos:
^Código de región^ Región ^
|ES |España|
|US |Estados Unidos|
|UK |Reino Unido|
|AU |Australia|
|BR |Brasil|
|CA |Canada|
|CN |China|
|DE |Alemania|
|FR |Francia|
|IN |India|
|RU |Rusia|
^ Código de idioma^ Idioma^
|es | Español|
|de| Aleman|
|en |Ingles|
|fr |Frances|
|ru |Ruso|
|ja |Japones|
|jv |Javanes|
|ko |Koreano|
|zh |Chino|
==== Ficheros .properties ====
Los ficheros de propiedades son ficheros de texto plano. Su contenido está organizado mediante parejas //clave-valor//.
En este tipo de ficheros es donde se guardarán todas las cadenas de texto que haya en la GUI de mi aplicacion: las etiquetas, el texto de los botones, el texto de los menus. Todo lo que sea susceptible de ser traducido en distintos idiomas
Por cada linea hay una clave y un valor:
* **Clave**: el identificador de la cadena de texto con la que posteriormente vamos a buscarla para poder recuperarla.
* **Valor**: el texto que corresponde a ese elemento
# Fichero de idioma por defecto (español)
lblNombre = Nombre:
btnNuevoUsuario = Nuevo
menuItemGuardar = Guardar
. . .
# Fichero de idioma inglés
lblNombre = Name:
btnNuevoUsuario = New
menuItemGuardar = Save
. . .
En cada fichero deben aparecer las mismas claves, pero traducidas el fichero de idioma concreto.
Estos ficheros de idiomas se deben llamar igual, y debe haber uno por defecto:
* ''EtiquetasBundle.properties'' -> Fichero por defecto (siempre debe haber uno)
* ''EtiquetasBundle_en.properties'' -> Para inglés
* ''EtiquetasBundle_en_US.properties'' -> Para inglés de EEUU
* ''EtiquetasBundle_fr.properties'' -> Para francés
Si el idioma indicado en el objeto ''Locale'' no conincide con ningun fichero de idioma, se usará el base por defecto.
{{ vimeo>475489822?medium }}
==== Clase ResourceBundle ====
Es la clase sobre la que basamos la internacionalización. Nos permite cargar propiedades de diferentes ficheros dependiendo de las propiedades del objeto ''Locale'' indicado.
Mediante el método estático ''getBundle()'' indicamos las propiedades de localización. Podemos indicarle un objeto ''Locale'' o que se utilice el actual:
* **getBundle**("miResourceBundle", locale) -> Usa el objeto ''Locale'' indicado en el parámetro //locale//
* **getBundle**("miResourceBundle") -> Usa el objeto ''Locale'' actual (el que devuelve Locale.getDefault())
//Nos carga el fichero EtiquetasBundle.properties (español)
Locale locale = new Locale("es", "ES")
ResourceBundle bundle = ResourceBundle.getBundle("EtiquetasBundle", locale);
String texto = bundle.getString("btnNuevoUsuario"); //Obtengo "Nuevo"
//Nos carga el fichero EtiquetasBundle_en.properties (inglés)
bundle = ResourceBundle.getBundle("EtiquetasBundle", Locale.US);
String texto = bundle.getString("btnNuevoUsuario"); //Obtengo "New"
//Si no pasamos un parametro Locale, usa el actual (Locale.getDefault())
Locale.setDefault(Locale.UK);
ResourceBundle bundle = ResourceBundle.getBundle("EtiquetasBundle");
{{ vimeo>475489888?medium }}
=== Sustituir texto estático por texto localizado ===
Cuando utilizamos las clases ''Locale'' y ''ResourceBundle'' para internacionalizar, debemos tener en cuenta algo muy importante: **No puede haber textos estáticos en las etiquetas de nuestros componentes gráficos**.
Debemos revisar nuestro código fuente y sustituir toda aparición de un texto, susceptible de ser traducido, por una llamada al método ''getString()'' de la clase ''ResourceBundle'':
//Debo sustitir estas cadenas
JButton btnGuardar = new JButton("Guardar");
JMenu menu = new JMenu("Archivo");
JLabel lbl1 = new JLabel("Nombre:");
//Por el método getString y la clave
ResourceBundle bundle = ResourceBundle.getBundle("EtiquetasBundle", locale);
JButton btnGuardar = new JButton(bundle.getString("textoGuardar"));
JMenu menu = new JMenu(bundle.getString("menuArchivo"));
JLabel lbl1 = new JLabel(bundle.getString("lblNombre"));
De este modo, el programa mostrará los textos utilizando los valores de cada fichero de ResourceBundle, en el idioma indicado por el objeto Locale actual.
En el siguiente video se plantea una forma de modificar el idioma desde la interfaz gráfica.
{{ vimeo>475614087?medium }}
==== Opciones de Internacionalización en IntelliJ ====
El IDE IntelliJ, permite algunas facilidades a la hora de internacionalizar una aplicación:
* Permite crear ResourceBundle de varios idiomas
* Ofrece añadir claves directamente, tanto desde el editor de código como desde un GUI Form
* Facilita la traducción de los ficheros ''properties'' a varios idiomas con su editor de ResourceBundle
Para poder añadir claves a nuestro //ResourceBundle// con los literales de String que tengamos en nuestro código fuente, es aconsejable habilitar una opcion de la configuracion: **File -> Settings -> Editor -> Inspections**.
Dentro del árbol buscar la sección **Java -> Internationalization Issues -> Marcar el checkbox //Hard coded strings//**
A partir de ahí puedo añadir literales de strings desde mi código fuente como claves a los ficheros //.properties// de los ResourceBundles.
En el siguiente video se explican las diferentes funcionalidades de IntelliJ respecto a la internacionalización:
{{ vimeo>492804959?medium }}
===== Pantalla de carga (SplashScreen) =====
Tener una pantalla de carga es muy útil cuando nuestra aplicación no tiene solamente que levantar la GUI, sino que también realizar operaciones iniciales que conllevan algo de tiempo, establecer conexiones con una bbdd y obtener muchos datos, hacer un mapeo objeto-realacional, o cualquier operación que requiera algunos segundos de tiempo.
Podemos ocultar esas operaciones mediante una pantalla de carga que tarde un tiempo en desaparecer, momento en el cual mostraremos nuestra GUI principal después de haber realizado las operaciones //más lentas//.
{{ :bloque3:splashscreen.png?direct&400 |}}
El siguiente código se puede descargar y muestra un ejemplo de una ventana de carga con una imagen y una barra de carga:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingConstants;
public class SplashScreen2 extends JDialog{
private JProgressBar barraProgreso;
public SplashScreen2() {
setBounds(100, 100, 637, 566);
JPanel contentPane = new JPanel();
contentPane.setLayout(new BorderLayout());
setContentPane(contentPane);
//Creo una etiqueta con la imagen en el centro
JLabel lblImagen = new JLabel();
//Indico la imagen que quiero mostrar en la label
lblImagen.setIcon(new ImageIcon(SplashScreen.class.getResource("/gui/splash.jpg")));
contentPane.add(lblImagen, BorderLayout.CENTER);
//Creo un panel al sur con una barra de carga y una label para el autor
JPanel panelInferior = new JPanel();
panelInferior.setLayout(new GridLayout(2, 1, 0, 0));
barraProgreso = new JProgressBar();
//Muestra el % de carga
barraProgreso.setStringPainted(true);
panelInferior.add(barraProgreso);
JLabel lblFersoft = new JLabel("FerSoft 2020");
lblFersoft.setForeground(Color.BLUE);
lblFersoft.setHorizontalAlignment(SwingConstants.CENTER);
panelInferior.add(lblFersoft);
//Anado el panel inferior al principal
contentPane.add(panelInferior, BorderLayout.SOUTH);
setResizable(false); //Impedir redimensionar la ventana
setUndecorated(true); //Eliminar la barra de título y sus botones
setLocationRelativeTo(null); //Mostrar en el centro
setVisible(true);
try {
iniciarBarraCarga();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//Al terminar la carga cierro la ventana
dispose();
}
private void iniciarBarraCarga() throws InterruptedException {
for(int i = 0; i <= 100; i++){
Thread.sleep(20);
actualizarBarraProgreso(i);
}
}
private void actualizarBarraProgreso(int valor) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressBar1.setValue(valor);
}
});
}
}
Para arrancar este hilo desde el método main:
public static void main(String[] args){
Modelo modelo = new Modelo();
//La vista se inicia pero no se muestra (visible = false)
Vista vista = new Vista();
//El controlador arranca
Controlador controlador = new Controlador(vista, modelo);
SplashScreen splash = new SplashScreen();
//Cuando ha terminado la ejecucion del SplashScreen
// se muestra la ventana principal
vista.setVisible(true);
}
Esta //SplashScreen// es simplemente decorativa, pero podría realizar un trabajo en algún otro hilo de la propia clase y que solo se cierre cuando se haya terminado, dando paso a la ventana principal.
===== Ayuda y manuales de usuario =====
Cuando trabajamos con alguna aplicación de software hay una parte común en todas ellas y es la sección de ayuda. Esta sección nos ofrece la documentación de usuario del software, y la forma de mostrarla y acceder a ella puede ser más o menos interactiva. En nuestro caso debemos crear una sección que permita al menos visualizar los manuales de usuario de nuestro software.
Las tres formas más sencillas son:
- Permitir que nuestra aplicación abra un sitio web desde el navegador del sistema
- Mostrar el manual en algún formato como ''.pdf'' desde nuestra aplicación
- Mostrar una página web en una ventana de nuestra aplicación
En el [[bloque5:documentacion|bloque 5]] sobre distribución de aplicaciones, se trabaja la creación de manuales y veremos otras formas de mostrar la ayuda.
Por ahora, podemos hacer que nuestra aplicación use el navegador por defecto del sistema y redirija a una web, creando una JLabel con aspecto de link http:
// Creo una etiqueta de color azul
JLabel lblFersoft = new JLabel("FerSoft 2020");
lblFersoft.setForeground(Color.BLUE.darker());
// Modifico el puntero del ratón al pasar sobre ella (Hand_cursor)
lblFersoft.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
...
//Añadir un MouseListener a la JLabel para hacer click sobre ella
//Debemos controlar las excepciones de la siguiente instrucción
Desktop.getDesktop().browse(new URI("http://www.bitbucket.org/fvaldeon"));
----
(c) {{date> %Y}} Fernando Valdeón