Saltar al contenido principal

Diálogos y ventanas Modales

Los diálogos son ventanas secundarias que aparecen sobre la ventana principal para pedir confirmación, mostrar información o recoger datos.

JOptionPane — Diálogos rápidos

JOptionPane proporciona diálogos estándar con muy poco código.

Diálogos de mensaje

// Información
JOptionPane.showMessageDialog(parent, "Operación completada.", "Éxito", JOptionPane.INFORMATION_MESSAGE);

// Advertencia
JOptionPane.showMessageDialog(parent, "Datos incompletos.", "Aviso", JOptionPane.WARNING_MESSAGE);

// Error
JOptionPane.showMessageDialog(parent, "Ha ocurrido un error.", "Error", JOptionPane.ERROR_MESSAGE);

// Sin icono
JOptionPane.showMessageDialog(parent, "Mensaje simple.", "Info", JOptionPane.PLAIN_MESSAGE);

// Parámetros:
// parent → componente padre (para centrar el diálogo; null = centrado en pantalla)
// message → texto (String, JComponent, Object[]...)
// title → título de la ventana
// messageType → tipo de icono

Diálogos de confirmación

int respuesta = JOptionPane.showConfirmDialog(
parent,
"¿Deseas guardar los cambios?",
"Confirmar",
JOptionPane.YES_NO_CANCEL_OPTION, // Opciones de botones
JOptionPane.QUESTION_MESSAGE // Icono
);

// Opciones de botones: YES_NO_OPTION, YES_NO_CANCEL_OPTION, OK_CANCEL_OPTION
// Respuesta: YES_OPTION (0), NO_OPTION (1), CANCEL_OPTION (2), CLOSED_OPTION (-1)

switch (respuesta) {
case JOptionPane.YES_OPTION -> guardar();
case JOptionPane.NO_OPTION -> descartar();
case JOptionPane.CANCEL_OPTION -> { /* no hacer nada */ }
}

Diálogos de entrada

// Entrada de texto
String nombre = JOptionPane.showInputDialog(parent, "Introduce tu nombre:");
if (nombre != null && !nombre.isEmpty()) {
lblNombre.setText(nombre);
}

// Con valor por defecto
String valor = JOptionPane.showInputDialog(parent, "Cantidad:", "10");

// Con opciones predefinidas
String[] opciones = {"Rojo", "Verde", "Azul"};
String color = (String) JOptionPane.showInputDialog(
parent,
"Elige un color:",
"Selección",
JOptionPane.QUESTION_MESSAGE,
null, // Icono personalizado (null = usar por defecto)
opciones,
opciones[0] // Valor inicial seleccionado
);

Diálogos totalmente personalizados

JPanel panel = new JPanel(new GridLayout(2, 2, 5, 5));
panel.add(new JLabel("Usuario:"));
JTextField txtUsuario = new JTextField(15);
panel.add(txtUsuario);
panel.add(new JLabel("Contraseña:"));
JPasswordField txtPass = new JPasswordField(15);
panel.add(txtPass);

int resultado = JOptionPane.showConfirmDialog(
parent,
panel, // El componente a mostrar como mensaje
"Iniciar sesión",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE
);

if (resultado == JOptionPane.OK_OPTION) {
String usuario = txtUsuario.getText();
String pass = new String(txtPass.getPassword());
autenticar(usuario, pass);
}

JDialog — Diálogo personalizado

Para diálogos más complejos se crea un JDialog propio.

public class DialogoConfigurar extends JDialog {

private boolean confirmado = false;
private JTextField txtHost;
private JSpinner spinnerPuerto;

public DialogoConfigurar(JFrame padre) {
super(padre, "Configuración", true); // true = modal
construirUI();
pack();
setLocationRelativeTo(padre);
}

private void construirUI() {
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 10, 15));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(4, 4, 4, 4);
gbc.fill = GridBagConstraints.HORIZONTAL;

gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0;
panel.add(new JLabel("Host:"), gbc);
gbc.gridx = 1; gbc.weightx = 1;
txtHost = new JTextField("localhost", 20);
panel.add(txtHost, gbc);

gbc.gridx = 0; gbc.gridy = 1; gbc.weightx = 0;
panel.add(new JLabel("Puerto:"), gbc);
gbc.gridx = 1; gbc.weightx = 1;
spinnerPuerto = new JSpinner(new SpinnerNumberModel(8080, 1, 65535, 1));
panel.add(spinnerPuerto, gbc);

JPanel botones = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JButton btnCancelar = new JButton("Cancelar");
JButton btnAceptar = new JButton("Aceptar");

btnCancelar.addActionListener(e -> dispose());
btnAceptar.addActionListener(e -> {
confirmado = true;
dispose();
});

botones.add(btnCancelar);
botones.add(btnAceptar);

setLayout(new BorderLayout());
add(panel, BorderLayout.CENTER);
add(botones, BorderLayout.SOUTH);

// Cerrar con Escape
getRootPane().registerKeyboardAction(
e -> dispose(),
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
JComponent.WHEN_IN_FOCUSED_WINDOW
);
// Botón por defecto (Enter)
getRootPane().setDefaultButton(btnAceptar);
}

public boolean isConfirmado() { return confirmado; }
public String getHost() { return txtHost.getText(); }
public int getPuerto() { return (Integer) spinnerPuerto.getValue(); }
}

// Uso:
DialogoConfigurar dialogo = new DialogoConfigurar(ventanaPrincipal);
dialogo.setVisible(true); // Bloquea hasta que se cierra (modal)

if (dialogo.isConfirmado()) {
conectar(dialogo.getHost(), dialogo.getPuerto());
}

JFileChooser — Selector de archivos

JFileChooser fc = new JFileChooser();

// Directorio inicial
fc.setCurrentDirectory(new File(System.getProperty("user.home")));

// Filtros de tipo de archivo
fc.addChoosableFileFilter(new FileNameExtensionFilter("Imágenes", "jpg", "jpeg", "png", "gif"));
fc.addChoosableFileFilter(new FileNameExtensionFilter("Documentos PDF", "pdf"));
fc.setAcceptAllFileFilterUsed(false); // Ocultar "Todos los archivos"

// Modo: FILES_ONLY, DIRECTORIES_ONLY, FILES_AND_DIRECTORIES
fc.setFileSelectionMode(JFileChooser.FILES_ONLY);

// Selección múltiple
fc.setMultiSelectionEnabled(true);

// Diálogo de apertura
int resultado = fc.showOpenDialog(parent);
if (resultado == JFileChooser.APPROVE_OPTION) {
File archivo = fc.getSelectedFile();
File[] archivos = fc.getSelectedFiles(); // Si hay selección múltiple
System.out.println("Abriendo: " + archivo.getAbsolutePath());
}

// Diálogo de guardado
resultado = fc.showSaveDialog(parent);
if (resultado == JFileChooser.APPROVE_OPTION) {
File destino = fc.getSelectedFile();
// Añadir extensión si el usuario no la puso
if (!destino.getName().endsWith(".txt")) {
destino = new File(destino.getAbsolutePath() + ".txt");
}
guardarArchivo(destino);
}

JColorChooser — Selector de color

// Diálogo modal directo
Color color = JColorChooser.showDialog(parent, "Elige un color", Color.BLUE);
if (color != null) {
componente.setBackground(color);
}

// Diálogo personalizado (para más control)
JColorChooser chooser = new JColorChooser(Color.RED);
chooser.setPreviewPanel(new JPanel()); // Ocultar panel de preview

JDialog dialogoColor = JColorChooser.createDialog(
parent,
"Selecciona color",
true, // modal
chooser,
e -> componente.setBackground(chooser.getColor()), // OK
null // Cancel (null = sin acción)
);
dialogoColor.setVisible(true);

JFontChooser — No existe nativo (implementación manual)

Swing no incluye un selector de fuentes, pero se puede construir:

public class FontChooserDialog extends JDialog {
private Font fontSeleccionada;

public FontChooserDialog(Frame padre, Font fontInicial) {
super(padre, "Seleccionar fuente", true);
String[] familias = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getAvailableFontFamilyNames();

JList<String> listaFuentes = new JList<>(familias);
JSpinner spinnerTamano = new JSpinner(new SpinnerNumberModel(12, 6, 72, 1));
JCheckBox chkNegrita = new JCheckBox("Negrita");
JCheckBox chkCursiva = new JCheckBox("Cursiva");
JLabel preview = new JLabel("Texto de ejemplo", JLabel.CENTER);
preview.setPreferredSize(new Dimension(300, 60));

Runnable actualizar = () -> {
String familia = listaFuentes.getSelectedValue();
if (familia == null) return;
int estilo = Font.PLAIN;
if (chkNegrita.isSelected()) estilo |= Font.BOLD;
if (chkCursiva.isSelected()) estilo |= Font.ITALIC;
int tamano = (Integer) spinnerTamano.getValue();
fontSeleccionada = new Font(familia, estilo, tamano);
preview.setFont(fontSeleccionada);
};

listaFuentes.addListSelectionListener(e -> actualizar.run());
spinnerTamano.addChangeListener(e -> actualizar.run());
chkNegrita.addActionListener(e -> actualizar.run());
chkCursiva.addActionListener(e -> actualizar.run());

// ... (añadir al layout y mostrar)
}

public Font getFontSeleccionada() { return fontSeleccionada; }
}

Diálogos de progreso

// ProgressMonitor — progreso con cancelación automática
ProgressMonitor monitor = new ProgressMonitor(parent, "Procesando...", "Iniciando", 0, 100);
monitor.setMillisToDecideToPopup(0); // Mostrar inmediatamente
monitor.setMillisToPopup(0);

// Ejecutar tarea larga en hilo secundario
new Thread(() -> {
for (int i = 0; i <= 100; i++) {
final int progreso = i;
SwingUtilities.invokeLater(() -> {
monitor.setProgress(progreso);
monitor.setNote("Procesando " + progreso + "%");
});
if (monitor.isCanceled()) {
System.out.println("Cancelado");
break;
}
try { Thread.sleep(50); } catch (InterruptedException ex) { break; }
}
}).start();

Notificaciones del sistema (System Tray)

if (SystemTray.isSupported()) {
SystemTray tray = SystemTray.getSystemTray();
Image imagen = Toolkit.getDefaultToolkit().getImage("icon.png");
TrayIcon trayIcon = new TrayIcon(imagen, "Mi App");
trayIcon.setImageAutoSize(true);

// Menú emergente en el icono de la barra
PopupMenu popupTray = new PopupMenu();
popupTray.add(new MenuItem("Mostrar")).addActionListener(e -> ventana.setVisible(true));
popupTray.add(new MenuItem("Salir")).addActionListener(e -> System.exit(0));
trayIcon.setPopupMenu(popupTray);

try {
tray.add(trayIcon);
trayIcon.displayMessage("Mi App", "La aplicación está en ejecución.", TrayIcon.MessageType.INFO);
} catch (AWTException ex) {
ex.printStackTrace();
}
}

Buenas prácticas

  • Usa siempre un parent (la ventana principal) para que los diálogos se centren correctamente.
  • Define un setDefaultButton en JDialog para que Enter active el botón principal.
  • Registra Escape con un KeyboardAction para cerrar diálogos.
  • Para lógica compleja de diálogo, encapsula en una clase propia que extienda JDialog.