vista ventajas mvc modelo ejemplos ejemplo diagrama desventajas controlador java swing user-interface model-view-controller awt

ventajas - mvc java mysql ejemplo



Java y GUI: ¿Dónde pertenecen los ActionListeners según el patrón MVC? (3)

Actualmente estoy escribiendo una plantilla de aplicación Java y de alguna manera, no estoy seguro de a dónde pertenecen los Líderes de acción si quiero seguir limpiamente el patrón MVC.

El ejemplo está basado en Swing, pero no se trata del marco sino del concepto básico de MVC en Java, usando cualquier marco para crear GUI.

Comencé con una aplicación absolutamente simple que contiene un JFrame y un JButton (para eliminar el marco, cerrar la aplicación). El código detrás de esta publicación. Nada realmente especial, solo para aclarar de lo que estamos hablando. No comencé con la Modelo todavía porque esta pregunta me estaba molestando demasiado.

Ya ha habido más de una pregunta similar, como estas:
Patrón MVC con muchos ActionListeners
Java swing: ¿a dónde debería ir el ActionListener?

Pero ninguno de ellos fue realmente satisfactorio ya que me gustaría saber dos cosas:

  • ¿Es razonable tener todos los ActionListeners en un paquete separado?
    • Me gustaría hacerlo por el bien de la legibilidad de View y Controller, esp. si hay muchos oyentes
  • ¿Cómo podría ejecutar una función de controlador desde un ActionListener, si el oyente no es una subclase dentro del controlador? (siguiente pregunta)

Espero que esto no sea demasiado general o vago, estoy preguntando aquí, pero me hace pensar por un tiempo. Siempre utilicé mi propio modo, dejé que ActionHandler supiera sobre el controlador , pero esto no parece correcto, así que finalmente me gustaría saber cómo se hace esto correctamente.

Saludos cordiales,
jaySon

Controlador:

package controller; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import view.MainView; public class MainController { MainView mainView = new MainView(); public MainController() { this.initViewActionListeners(); } private void initViewActionListeners() { mainView.initButtons(new CloseListener()); } public class CloseListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { mainView.dispose(); } } }


Ver:

package view; import java.awt.Dimension; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; public class MainView extends JFrame { JButton button_close = new JButton(); JPanel panel_mainPanel = new JPanel(); private static final long serialVersionUID = 5791734712409634055L; public MainView() { setDefaultCloseOperation(DISPOSE_ON_CLOSE); this.setSize(500, 500); this.add(panel_mainPanel); setVisible(true); } public void initButtons(ActionListener actionListener) { this.button_close = new JButton("Close"); this.button_close.setSize(new Dimension(100, 20)); this.button_close.addActionListener(actionListener); this.panel_mainPanel.add(button_close); } }


Esa es una pregunta muy difícil de responder con Swing, ya que Swing no es una implementación pura de MVC, la vista y el controlador son mixtos.

Técnicamente, un modelo y un controlador deberían poder interactuar y el controlador y la vista deberían poder interactuar, pero la vista y el modelo nunca deberían interactuar, lo que claramente no es cómo funciona Swing, pero ese es otro debate ...

Otro problema es que realmente no desea exponer los componentes UI a nadie, al controlador no le debe importar cómo ocurren ciertas acciones, solo que sí pueden.

Esto sugeriría que la vista debe mantener el ActionListener adjunto a los controles de UI. La vista debería alertar al controlador de que se ha producido algún tipo de acción. Para esto, puede usar otro ActionListener , administrado por la vista, al cual se suscribe el controlador.

Mejor aún, tendría un oyente de vista dedicado, que describía las acciones que podría producir esta vista, por ejemplo ...

public interface MainViewListener { public void didPerformClose(MainView mainView); }

El controlador se suscribiría a la vista a través de este oyente y la vista llamaría a didPerformClose cuando (en este caso) se presiona el botón de cierre.

Incluso en este ejemplo, me sentiría tentado de crear una interfaz de "vista principal", que describiera las propiedades (setters y getters) y las acciones (listeners / callbacks) que cualquier implementación garantiza proporcionar, entonces no te importa cómo estas las acciones ocurren, solo que cuando lo hacen, se espera que hagas algo ...

En cada nivel que quiera preguntarse, ¿qué tan fácil sería cambiar cualquier elemento (cambiar el modelo o el controlador o la vista) por otra instancia? Si te encuentras teniendo que desacoplar el código, entonces tienes un problema. Comuníquese a través de interfaces y trate de reducir la cantidad de acoplamiento entre las capas y la cantidad que cada capa conoce sobre las otras hasta el punto en que simplemente mantienen contratos.

Actualizado...

Tomemos esto como ejemplo ...

En realidad, hay dos vistas (descontando el diálogo real), está la vista de credenciales y la vista de inicio de sesión, sí, son diferentes como verá.

CredentialsView

La vista de credenciales es responsable de recopilar los detalles que deben autenticarse, el nombre de usuario y la contraseña. Proporcionará información al controlador para informarle cuándo se han cambiado esas credenciales, ya que el controlador puede querer realizar alguna acción, como habilitar el botón "iniciar sesión" ...

La vista también querrá saber cuándo se va a realizar la autenticación, ya que deseará deshabilitar sus campos, por lo que el usuario no podrá actualizar la vista mientras se realiza la autenticación, igualmente, deberá saber cuándo se realizará la autenticación. falla o tiene éxito, ya que tendrá que tomar medidas para esas eventualidades.

public interface CredentialsView { public String getUserName(); public char[] getPassword(); public void willAuthenticate(); public void authenticationFailed(); public void authenticationSucceeded(); public void setCredentialsViewController(CredentialsViewController listener); } public interface CredentialsViewController { public void credientialsDidChange(CredentialsView view); }

Panel de credenciales

El CredentialsPane es la implementación física de un CredentialsView , implementa el contrato, pero administra su propio estado interno. Cómo se gestiona el contrato es irrelevante para el controlador, solo le preocupa que el contrato se haya mantenido ...

public class CredentialsPane extends JPanel implements CredentialsView { private CredentialsViewController controller; private JTextField userNameField; private JPasswordField passwordField; public CredentialsPane(CredentialsViewController controller) { setCredentialsViewController(controller); setLayout(new GridBagLayout()); userNameField = new JTextField(20); passwordField = new JPasswordField(20); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.insets = new Insets(2, 2, 2, 2); gbc.anchor = GridBagConstraints.EAST; add(new JLabel("Username: "), gbc); gbc.gridy++; add(new JLabel("Password: "), gbc); gbc.gridx = 1; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; add(userNameField, gbc); gbc.gridy++; add(passwordField, gbc); DocumentListener listener = new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { getCredentialsViewController().credientialsDidChange(CredentialsPane.this); } @Override public void removeUpdate(DocumentEvent e) { getCredentialsViewController().credientialsDidChange(CredentialsPane.this); } @Override public void changedUpdate(DocumentEvent e) { getCredentialsViewController().credientialsDidChange(CredentialsPane.this); } }; userNameField.getDocument().addDocumentListener(listener); passwordField.getDocument().addDocumentListener(listener); } @Override public CredentialsViewController getCredentialsViewController() { return controller; } @Override public String getUserName() { return userNameField.getText(); } @Override public char[] getPassword() { return passwordField.getPassword(); } @Override public void willAuthenticate() { userNameField.setEnabled(false); passwordField.setEnabled(false); } @Override public void authenticationFailed() { userNameField.setEnabled(true); passwordField.setEnabled(true); userNameField.requestFocusInWindow(); userNameField.selectAll(); JOptionPane.showMessageDialog(this, "Authentication has failed", "Error", JOptionPane.ERROR_MESSAGE); } @Override public void authenticationSucceeded() { // Really don''t care, but you might want to stop animation, for example... } public void setCredentialsViewController(CredentialsViewController controller){ this.controller = controller; } }

LoginView

El LoginView es responsable de administrar un CredentialsView , pero también de notificar al LoginViewController cuando debe realizarse la autenticación o si el proceso fue cancelado por el usuario, por algún medio ...

Del mismo modo, LoginViewController le dirá a la vista cuándo se va a realizar la autenticación y si la autenticación falló o fue exitosa.

public interface LoginView { public CredentialsView getCredentialsView(); public void willAuthenticate(); public void authenticationFailed(); public void authenticationSucceeded(); public void dismissView(); public LoginViewController getLoginViewController(); } public interface LoginViewController { public void authenticationWasRequested(LoginView view); public void loginWasCancelled(LoginView view); }

LoginPane

El LoginPane es algo especial, está actuando como la vista para el LoginViewController , pero también actúa como el controlador para el CredentialsView . Esto es importante, ya que no hay nada que diga que una vista no puede ser un controlador, pero tendría cuidado con la forma en que implemente tales cosas, ya que no siempre tiene sentido hacerlo de esta manera, sino porque las dos vistas son trabajando juntos para reunir información y gestionar eventos, tiene sentido en este caso.

Debido a que LoginPane necesitará cambiar su propio estado en función de los cambios en CredentialsView , tiene sentido permitir que el LoginPane actúe como controlador en este caso, de lo contrario, necesitaría suministrar más métodos que controlaran ese estado del botones, pero esto comienza a sangrar la lógica UI sobre el controlador ...

public static class LoginPane extends JPanel implements LoginView, CredentialsViewController { private LoginViewController controller; private CredentialsPane credientialsView; private JButton btnAuthenticate; private JButton btnCancel; private boolean wasAuthenticated; public LoginPane(LoginViewController controller) { setLoginViewController(controller); setLayout(new BorderLayout()); setBorder(new EmptyBorder(8, 8, 8, 8)); btnAuthenticate = new JButton("Login"); btnCancel = new JButton("Cancel"); JPanel buttons = new JPanel(); buttons.add(btnAuthenticate); buttons.add(btnCancel); add(buttons, BorderLayout.SOUTH); credientialsView = new CredentialsPane(this); add(credientialsView); btnAuthenticate.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { getLoginViewController().authenticationWasRequested(LoginPane.this); } }); btnCancel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { getLoginViewController().loginWasCancelled(LoginPane.this); // I did think about calling dispose here, // but''s not really the the job of the cancel button to decide what should happen here... } }); validateCreientials(); } public static boolean showLoginDialog(LoginViewController controller) { final LoginPane pane = new LoginPane(controller); JDialog dialog = new JDialog(); dialog.setTitle("Login"); dialog.setModal(true); dialog.add(pane); dialog.pack(); dialog.setLocationRelativeTo(null); dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); dialog.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { pane.getLoginViewController().loginWasCancelled(pane); } }); dialog.setVisible(true); return pane.wasAuthenticated(); } public boolean wasAuthenticated() { return wasAuthenticated; } public void validateCreientials() { CredentialsView view = getCredentialsView(); String userName = view.getUserName(); char[] password = view.getPassword(); if ((userName != null && userName.trim().length() > 0) && (password != null && password.length > 0)) { btnAuthenticate.setEnabled(true); } else { btnAuthenticate.setEnabled(false); } } @Override public void dismissView() { SwingUtilities.windowForComponent(this).dispose(); } @Override public CredentialsView getCredentialsView() { return credientialsView; } @Override public void willAuthenticate() { getCredentialsView().willAuthenticate(); btnAuthenticate.setEnabled(false); } @Override public void authenticationFailed() { getCredentialsView().authenticationFailed(); validateCreientials(); wasAuthenticated = false; } @Override public void authenticationSucceeded() { getCredentialsView().authenticationSucceeded(); validateCreientials(); wasAuthenticated = true; } public LoginViewController getLoginViewController() { return controller; } public void setLoginViewController(LoginViewController controller) { this.controller = controller; } @Override public void credientialsDidChange(CredentialsView view) { validateCreientials(); } }

Ejemplo de trabajo

import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.border.EmptyBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import sun.net.www.protocol.http.HttpURLConnection; public class Test { protected static final Random AUTHENTICATION_ORACLE = new Random(); public static void main(String[] args) { new Test(); } public interface CredentialsView { public String getUserName(); public char[] getPassword(); public void willAuthenticate(); public void authenticationFailed(); public void authenticationSucceeded(); public CredentialsViewController getCredentialsViewController(); } public interface CredentialsViewController { public void credientialsDidChange(CredentialsView view); } public interface LoginView { public CredentialsView getCredentialsView(); public void willAuthenticate(); public void authenticationFailed(); public void authenticationSucceeded(); public void dismissView(); public LoginViewController getLoginViewController(); } public interface LoginViewController { public void authenticationWasRequested(LoginView view); public void loginWasCancelled(LoginView view); } public Test() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { ex.printStackTrace(); } LoginViewController controller = new LoginViewController() { @Override public void authenticationWasRequested(LoginView view) { view.willAuthenticate(); LoginAuthenticator authenticator = new LoginAuthenticator(view); authenticator.authenticate(); } @Override public void loginWasCancelled(LoginView view) { view.dismissView(); } }; if (LoginPane.showLoginDialog(controller)) { System.out.println("You shell pass"); } else { System.out.println("You shell not pass"); } System.exit(0); } }); } public class LoginAuthenticator { private LoginView view; public LoginAuthenticator(LoginView view) { this.view = view; } public void authenticate() { Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (AUTHENTICATION_ORACLE.nextBoolean()) { view.authenticationSucceeded(); view.dismissView(); } else { view.authenticationFailed(); } } }); } }); t.start(); } } public static class LoginPane extends JPanel implements LoginView, CredentialsViewController { private LoginViewController controller; private CredentialsPane credientialsView; private JButton btnAuthenticate; private JButton btnCancel; private boolean wasAuthenticated; public LoginPane(LoginViewController controller) { setLoginViewController(controller); setLayout(new BorderLayout()); setBorder(new EmptyBorder(8, 8, 8, 8)); btnAuthenticate = new JButton("Login"); btnCancel = new JButton("Cancel"); JPanel buttons = new JPanel(); buttons.add(btnAuthenticate); buttons.add(btnCancel); add(buttons, BorderLayout.SOUTH); credientialsView = new CredentialsPane(this); add(credientialsView); btnAuthenticate.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { getLoginViewController().authenticationWasRequested(LoginPane.this); } }); btnCancel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { getLoginViewController().loginWasCancelled(LoginPane.this); // I did think about calling dispose here, // but''s not really the the job of the cancel button to decide what should happen here... } }); validateCreientials(); } public static boolean showLoginDialog(LoginViewController controller) { final LoginPane pane = new LoginPane(controller); JDialog dialog = new JDialog(); dialog.setTitle("Login"); dialog.setModal(true); dialog.add(pane); dialog.pack(); dialog.setLocationRelativeTo(null); dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); dialog.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { pane.getLoginViewController().loginWasCancelled(pane); } }); dialog.setVisible(true); return pane.wasAuthenticated(); } public boolean wasAuthenticated() { return wasAuthenticated; } public void validateCreientials() { CredentialsView view = getCredentialsView(); String userName = view.getUserName(); char[] password = view.getPassword(); if ((userName != null && userName.trim().length() > 0) && (password != null && password.length > 0)) { btnAuthenticate.setEnabled(true); } else { btnAuthenticate.setEnabled(false); } } @Override public void dismissView() { SwingUtilities.windowForComponent(this).dispose(); } @Override public CredentialsView getCredentialsView() { return credientialsView; } @Override public void willAuthenticate() { getCredentialsView().willAuthenticate(); btnAuthenticate.setEnabled(false); } @Override public void authenticationFailed() { getCredentialsView().authenticationFailed(); validateCreientials(); wasAuthenticated = false; } @Override public void authenticationSucceeded() { getCredentialsView().authenticationSucceeded(); validateCreientials(); wasAuthenticated = true; } public LoginViewController getLoginViewController() { return controller; } public void setLoginViewController(LoginViewController controller) { this.controller = controller; } @Override public void credientialsDidChange(CredentialsView view) { validateCreientials(); } } public static class CredentialsPane extends JPanel implements CredentialsView { private CredentialsViewController controller; private JTextField userNameField; private JPasswordField passwordField; public CredentialsPane(CredentialsViewController controller) { setCredentialsViewController(controller); setLayout(new GridBagLayout()); userNameField = new JTextField(20); passwordField = new JPasswordField(20); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.insets = new Insets(2, 2, 2, 2); gbc.anchor = GridBagConstraints.EAST; add(new JLabel("Username: "), gbc); gbc.gridy++; add(new JLabel("Password: "), gbc); gbc.gridx = 1; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; add(userNameField, gbc); gbc.gridy++; add(passwordField, gbc); DocumentListener listener = new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { getCredentialsViewController().credientialsDidChange(CredentialsPane.this); } @Override public void removeUpdate(DocumentEvent e) { getCredentialsViewController().credientialsDidChange(CredentialsPane.this); } @Override public void changedUpdate(DocumentEvent e) { getCredentialsViewController().credientialsDidChange(CredentialsPane.this); } }; userNameField.getDocument().addDocumentListener(listener); passwordField.getDocument().addDocumentListener(listener); } @Override public CredentialsViewController getCredentialsViewController() { return controller; } @Override public String getUserName() { return userNameField.getText(); } @Override public char[] getPassword() { return passwordField.getPassword(); } @Override public void willAuthenticate() { userNameField.setEnabled(false); passwordField.setEnabled(false); } @Override public void authenticationFailed() { userNameField.setEnabled(true); passwordField.setEnabled(true); userNameField.requestFocusInWindow(); userNameField.selectAll(); JOptionPane.showMessageDialog(this, "Authentication has failed", "Error", JOptionPane.ERROR_MESSAGE); } @Override public void authenticationSucceeded() { // Really don''t care, but you might want to stop animation, for example... } public void setCredentialsViewController(CredentialsViewController controller) { this.controller = controller; } } }


A estoy actualmente aprendiendo Java en la escuela. Los maestros nos dijeron que los oyentes siempre tienen que ser declarados dentro de la clase de Controladores . La forma en que lo hago, es implementar un método, por ejemplo, oyentes () . En el interior están todas las declaraciones de oyentes utilizando clases anónimas. Esa es la forma en que mis profesores quieren que se vea, pero francamente, no estoy seguro de si lo corrigieron del todo.


Están asociados con el control, pero no tienen que ser una parte directa del control. Por ejemplo, vea el código publicado a continuación que estaba preparando para otra pregunta, una sobre clases internas anónimas y acoplamiento, aquí le doy a todos mis botones Acciones internas anónimas (que son ActionListeners, por supuesto), y luego uso las Acciones para cambiar el estado de la GUI. Cualquier oyente de la GUI (el control) será notificado de este cambio, y luego podrá actuar en consecuencia.

import java.awt.*; import java.awt.event.*; java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.*; import javax.swing.event.SwingPropertyChangeSupport; public class AnonymousInnerEg2 { private static void createAndShowUI() { GuiModel2 model = new GuiModel2(); GuiPanel2 guiPanel = new GuiPanel2(); GuiControl2 guiControl = new GuiControl2(); guiControl.setGuiPanel(guiPanel); guiControl.setGuiModel(model); try { guiControl.init(); } catch (GuiException2 e) { e.printStackTrace(); System.exit(-1); } JFrame frame = new JFrame("AnonymousInnerEg"); frame.getContentPane().add(guiPanel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } public static void main(String[] args) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { createAndShowUI(); } }); } } enum GuiState { BASE("Base"), START("Start"), END("End"); private String name; private GuiState(String name) { this.name = name; } public String getName() { return name; } } class GuiModel2 { public static final String STATE = "state"; private SwingPropertyChangeSupport support = new SwingPropertyChangeSupport(this); private GuiState state = GuiState.BASE; public GuiState getState() { return state; } public void setState(GuiState state) { GuiState oldValue = this.state; GuiState newValue = state; this.state = state; support.firePropertyChange(STATE, oldValue, newValue); } public void addPropertyChangeListener(PropertyChangeListener l) { support.addPropertyChangeListener(l); } public void removePropertyChangeListener(PropertyChangeListener l) { support.removePropertyChangeListener(l); } } @SuppressWarnings("serial") class GuiPanel2 extends JPanel { public static final String STATE = "state"; private String state = GuiState.BASE.getName(); private JLabel stateField = new JLabel("", SwingConstants.CENTER); public GuiPanel2() { JPanel btnPanel = new JPanel(new GridLayout(1, 0, 5, 0)); for (final GuiState guiState : GuiState.values()) { btnPanel.add(new JButton(new AbstractAction(guiState.getName()) { { int mnemonic = (int) getValue(NAME).toString().charAt(0); putValue(MNEMONIC_KEY, mnemonic); } @Override public void actionPerformed(ActionEvent e) { String name = getValue(NAME).toString(); setState(name); } })); } setLayout(new BorderLayout()); add(stateField, BorderLayout.PAGE_START); add(btnPanel, BorderLayout.CENTER); } public String getState() { return state; } public void setState(String state) { String oldValue = this.state; String newValue = state; this.state = state; firePropertyChange(STATE, oldValue, newValue); } public void setStateField(String name) { stateField.setText(name); } } class GuiControl2 { private GuiPanel2 guiPanel; private GuiModel2 model; private boolean allOK = false; public void setGuiPanel(GuiPanel2 guiPanel) { this.guiPanel = guiPanel; guiPanel.addPropertyChangeListener(GuiPanel2.STATE, new GuiPanelStateListener()); } public void init() throws GuiException2 { if (model == null) { throw new GuiException2("Model is null"); } if (guiPanel == null) { throw new GuiException2("GuiPanel is null"); } allOK = true; guiPanel.setStateField(model.getState().getName()); } public void setGuiModel(GuiModel2 model) { this.model = model; model.addPropertyChangeListener(new ModelListener()); } private class GuiPanelStateListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { if (!allOK) { return; } if (GuiPanel2.STATE.equals(evt.getPropertyName())) { String text = guiPanel.getState(); model.setState(GuiState.valueOf(text.toUpperCase())); } } } private class ModelListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { if (!allOK) { return; } if (GuiModel2.STATE.equals(evt.getPropertyName())) { GuiState state = (GuiState) evt.getNewValue(); guiPanel.setStateField(state.getName()); } } } } @SuppressWarnings("serial") class GuiException2 extends Exception { public GuiException2() { super(); } public GuiException2(String message) { super(message); } }

Sin embargo, tenga en cuenta lo siguiente: no soy un codificador profesional o incluso un codificador entrenado en la universidad, así que tome esto como solo mi opinión.