Ejemplo 4: Estilizando componentes Swing con Delegadores
En este ejemplo, se muestra cómo estilizar componentes Swing utilizando delegadores para personalizar su apariencia. Los delegadores permiten separar la lógica de presentación de la lógica de negocio, lo que facilita la personalización de los componentes sin afectar su funcionalidad.
Asumiremos la siguiente paleta de colores para nuestro tema:
public class MyPalette {
public static final Color PRIMARY_COLOR = new Color(0x2B97ED);
public static final Color PRIMARY_HOVER_COLOR = new Color(0x2581C9);
public static final Color PRIMARY_ACTIVE_COLOR = new Color(0x1665A5);
public static final Color PRIMARY_DISABLED_COLOR = new Color(0xCCCCCC);
public static final Color BACKGROUND_COLOR = new Color(0xF3F4F6);
public static final Color ERROR_COLOR = new Color(0xE74C3C);
public static final Color SUCCESS_COLOR = new Color(0x27AE60);
public static final Color WARNING_COLOR = new Color(0xF39C12);
}
A continuación, se muestra cómo crear un delegador para estilizar un botón personalizado derivado de BasicButtonUI:
import javax.swing.*;
import javax.swing.plaf.basic.BasicButtonUI;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class MyButtonUI extends BasicButtonUI {
private Color backgroundColor = MyPalette.PRIMARY_COLOR;
private Color hoverColor = MyPalette.PRIMARY_HOVER_COLOR;
private Color activeColor = MyPalette.PRIMARY_ACTIVE_COLOR;
private Color disabledColor = MyPalette.PRIMARY_DISABLED_COLOR;
@Override
public void installUI(JComponent c) {
super.installUI(c);
c.setOpaque(false);
c.setBackground(backgroundColor);
c.setForeground(Color.WHITE);
c.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20));
c.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
if (c.isEnabled()) {
c.setBackground(hoverColor);
}
}
@Override
public void mouseExited(MouseEvent e) {
if (c.isEnabled()) {
c.setBackground(backgroundColor);
}
}
@Override
public void mousePressed(MouseEvent e) {
if (c.isEnabled()) {
c.setBackground(activeColor);
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (c.isEnabled()) {
c.setBackground(hoverColor);
}
}
});
}
@Override
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
c.removeMouseListener(c.getMouseListeners()[0]);
}
@Override
public void paint(Graphics g, JComponent c) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setColor(c.getBackground());
g2d.fillRoundRect(0, 0, c.getWidth(), c.getHeight(), 20, 20);
super.paint(g, c);
}
}
Con este delegador, hemos personalizado la apariencia de un botón para que tenga un fondo redondeado y cambie de color en diferentes estados (normal, hover, activo, deshabilitado). Para usar este delegador en un botón, simplemente asignamos la UI personalizada al botón:
JButton myButton = new JButton("Click Me");
myButton.setUI(new MyButtonUI());
Ahora, el botón myButton tendrá la apariencia personalizada definida en MyButtonUI, utilizando los colores de la paleta que hemos establecido.
Estilizando campos de texto
Ahora veamos como estilizar un TextField utilizando un delegador similar para que el campo tenga automáticamente una etiqueta flotante y un borde redondeado que cambie de color según el estado del campo (normal, enfocado, error).
import javax.swing.*;
import javax.swing.plaf.basic.BasicTextFieldUI;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
public class MyTextFieldUI extends BasicTextFieldUI {
private String labelText;
private String placeholderText;
private Color backgroundColor = MyPalette.BACKGROUND_COLOR;
private Color borderColor = MyPalette.PRIMARY_COLOR;
private Color focusBorderColor = MyPalette.PRIMARY_HOVER_COLOR;
private Color errorBorderColor = MyPalette.ERROR_COLOR;
public MyTextFieldUI(String labelText, String placeholderText) {
this.labelText = labelText;
this.placeholderText = placeholderText;
}
@Override
public void installUI(JComponent c) {
super.installUI(c);
c.setOpaque(false);
c.setBackground(backgroundColor);
c.setForeground(Color.LIGHT_GRAY);
c.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
c.setBackground(null);
c.setForeground(Color.BLACK);
c.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
// Agregamos un margen adicional para la etiqueta
c.setBorder(BorderFactory.createCompoundBorder(c.getBorder(),
BorderFactory.createEmptyBorder(20, 0, 0, 0)));
// Agregamos un listener para cambiar el borde al enfocar o perder el enfoque
c.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
c.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(focusBorderColor, 2),
BorderFactory.createEmptyBorder(20, 0, 0, 0)));
}
@Override
public void focusLost(FocusEvent e) {
c.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(borderColor, 1),
BorderFactory.createEmptyBorder(20, 0, 0, 0)));
}
});
}
@Override
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
c.removeFocusListener(c.getFocusListeners()[0]);
}
@Override
public void paintBackground(Graphics g, JComponent c) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// Dibujamos en primer lugar la etiqueta sobre el borde del campo de texto
g2d.setColor(MyThemeColors.PRIMARY_COLOR);
FontMetrics fm = g2d.getFontMetrics();
int labelHeight = fm.getAscent();
int labelY = labelHeight + 5; // Posición de la etiqueta sobre el borde
g2d.drawString(outlineLabel, 5, labelY);
// Luego dibujamos el fondo del campo de texto
g2d.translate(0, labelHeight + 5); // Movemos el origen para dibujar el fondo debajo de la etiqueta
g2d.setColor(c.getBackground());
g2d.fillRoundRect(0, 0, c.getWidth(), c.getHeight() - labelHeight - 5, 10, 10);
// Luego dibujamos un borde redondeado para envolver el campo de texto, asegurándonos de que la etiqueta quede por encima del borde
g2d.setColor(MyThemeColors.PRIMARY_COLOR);
g2d.setStroke(new BasicStroke(2));
g2d.drawRoundRect(0, 0, c.getWidth(), c.getHeight() - labelHeight - 5, 10, 10);
}
}
Para usar este delegador en un campo de texto, simplemente asignamos la UI personalizada al campo:
JTextField myTextField = new JTextField();
myTextField.setUI(new MyTextFieldUI());
Radio Buttons y Checkboxes
De manera similar, podemos crear delegadores personalizados para JRadioButton y JCheckBox para estilizar su apariencia. Por ejemplo, para un JRadioButton, podríamos crear un delegador que dibuje un círculo personalizado en lugar del círculo predeterminado, y para un JCheckBox, podríamos dibujar un cuadro personalizado con una marca de verificación estilizada.
Veamos el ejemplo de un delegador para JRadioButton:
import javax.swing.*;
import javax.swing.plaf.basic.BasicRadioButtonUI;
import java.awt.*;
public class MyRadioButtonUI extends BasicRadioButtonUI {
private Color selectedColor = MyPalette.PRIMARY_COLOR;
private Color unselectedColor = MyPalette.BACKGROUND_COLOR;
private Color borderColor = MyPalette.PRIMARY_COLOR;
@Override
public void installUI(JComponent c) {
super.installUI(c);
c.setOpaque(false);
c.setBackground(unselectedColor);
c.setForeground(Color.BLACK);
c.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
}
@Override
public void paint(Graphics g, JComponent c) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// Dibujamos el círculo del radio button
int diameter = Math.min(c.getWidth(), c.getHeight()) - 10;
int x = (c.getWidth() - diameter) / 2;
int y = (c.getHeight() - diameter) / 2;
g2d.setColor(borderColor);
g2d.drawOval(x, y, diameter, diameter);
if (((JRadioButton) c).isSelected()) {
g2d.setColor(selectedColor);
g2d.fillOval(x + 4, y + 4, diameter - 8, diameter - 8);
}
// Luego dibujamos el texto al lado del círculo
g2d.setColor(c.getForeground());
FontMetrics fm = g2d.getFontMetrics();
int textX = x + diameter + 10;
int textY = (c.getHeight() + fm.getAscent()) / 2 - 2;
g2d.drawString(((JRadioButton) c).getText(), textX, textY);
}
}
Para usar este delegador en un JRadioButton, simplemente asignamos la UI personalizada al botón:
JRadioButton myRadioButton = new JRadioButton("Option 1");
myRadioButton.setUI(new MyRadioButtonUI());
Por su lado para un JCheckBox, podríamos crear un delegador similar que dibuje un cuadro personalizado con una marca de verificación estilizada de la siguiente manera:
import javax.swing.*;
import javax.swing.plaf.basic.BasicCheckBoxUI;
import java.awt.*;
public class MyCheckBoxUI extends BasicCheckBoxUI {
private Color selectedColor = MyPalette.PRIMARY_COLOR;
private Color unselectedColor = MyPalette.BACKGROUND_COLOR;
private Color borderColor = MyPalette.PRIMARY_COLOR;
@Override
public void installUI(JComponent c) {
super.installUI(c);
c.setOpaque(false);
c.setBackground(unselectedColor);
c.setForeground(Color.BLACK);
c.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
}
@Override
public void paint(Graphics g, JComponent c) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// Dibujamos el cuadro del checkbox
int size = Math.min(c.getWidth(), c.getHeight()) - 10;
int x = (c.getWidth() - size) / 2;
int y = (c.getHeight() - size) / 2;
g2d.setColor(borderColor);
g2d.drawRect(x, y, size, size);
if (((JCheckBox) c).isSelected()) {
g2d.setColor(selectedColor);
g2d.fillRect(x + 4, y + 4, size - 8, size - 8);
g2d.setColor(Color.GREEN);
g2d.setStroke(new BasicStroke(2));
g2d.drawLine(x + 6, y + size / 2, x + size / 3, y + size - 6);
g2d.drawLine(x + size / 3, y + size - 6, x + size - 6, y + 6);
}
// Luego dibujamos el texto al lado del cuadro
g2d.setColor(c.getForeground());
FontMetrics fm = g2d.getFontMetrics();
int textX = x + size + 10;
int textY = (c.getHeight() + fm.getAscent()) / 2 - 2;
g2d.drawString(((JCheckBox) c).getText(), textX, textY);
}
Para usar este delegador en un JCheckBox, simplemente asignamos la UI personalizada al checkbox:
JCheckBox myCheckBox = new JCheckBox("Accept Terms");
myCheckBox.setUI(new MyCheckBoxUI());
Con estos ejemplos, hemos visto cómo estilizar componentes Swing utilizando delegadores personalizados para crear una apariencia única y coherente con la paleta de colores definida en nuestro tema.
Ejemplo 03: Creando una Aplicación con GUI en Java Swing
En este ejemplo, crearemos una aplicación simple con una interfaz gráfica utilizando Java Swing. La aplicación consistirá en una ventana con un botón que, al hacer clic, mostrará un mensaje en la consola.
Ejemplo 5: Uso de Hilos con Thread y Runnable
Ejemplo de cómo usar hilos en Java usando Thread y Runnable, además de las funciones sleep y join e interrupciones.