2012-06-16 12 views
5

Estoy descubriendo que escribir un buen código OO con Swing es increíblemente difícil. Mi problema es esencialmente que tengo una vista (un JPanel) que tiene oyentes de acción. Los oyentes de acción determinan en qué botón se hizo clic y llaman al método de control apropiado. El problema es que este método de controlador necesita actualizar otra vista. Entonces, el problema que estoy teniendo es que pasé vistas a los controladores. Aquí hay un ejemplo.Cómo administrar ver actualizaciones desde controladores en una aplicación Java Swing

public class MyView extends JPanel implements ActionListener { 
    private final MyController controller = new MyController(); 

    @Override public void actionPerformed(ActionEvent e) { 
    this.controller.updateOtherView(); 
    } 
} 

Esto es esencialmente lo que quiero, pero esto es lo que termina sucediendo.

public class MyView extends JPanel implements ActionListener { 
    private MyController controller = new MyController(); 
    private OtherView otherView; 

    public MyView(MyOtherView otherView) { 
    this.otherView = otherView; 
    } 

    @Override public void actionPerformed(ActionEvent e) { 
    this.controller.updateOtherView(otherView); 
    } 
} 

Y se puede ver que a medida que el número de puntos de vista que necesitan ser actualizados y aumento del número de clases que se parecen a este aumento, las vistas son esencialmente variables globales y el código se vuelve compleja y poco clara. Otro problema con el que me estoy cruzando es que esta otra vista normalmente no se transfiere directamente a MyView, pero tiene que pasar por los padres de MyView para acceder a MyView, lo que realmente me fastidia.

Para un ejemplo real de esto, digamos que tengo un menú y este MyView. MyView tiene un botón de reproducción, que reproduce un poco de música durante un tiempo y desactiva (grises) el botón de reproducción hasta que finaliza la música. Si tengo una opción de menú llamada play, ahora necesito acceder al botón de reproducción de otras vistas para poder atenuarlo. ¿Cómo puedo hacer esto sin este paso molesto de puntos de vista en todas partes? Aunque puede haber soluciones específicas para este problema, estoy buscando algo que solucione este problema de acceso a la vista en el caso general.

No estoy seguro de cómo solucionar esto. Estoy utilizando la terminología de patrones MVC en este momento sin usar el patrón MVC, que puede o no ser necesario. Cualquier ayuda es apreciada.

+0

Consulte editar mi respuesta, así como el ejemplo del código. –

Respuesta

1

En tal situación, tiendo a usar Singletons. Por supuesto, esto depende de la singularidad de sus puntos de vista. Normalmente tengo Singletons para mis "ventanas" (JFrames), así que puedo navegar desde esos hasta los niños que necesito usando getters. Sin embargo, esta podría no ser la mejor idea en situaciones muy complejas.

12

Una solución: simplemente haga que el controlador actualice el modelo. Luego, los oyentes adjuntos al modelo actualizarían las vistas. También puede hacer que los JMenuItems y los JButton correspondientes compartan la misma Acción y, por lo tanto, cuando deshabilite la Acción, deshabilitará todos los botones/menús/etc. que usen esa Acción.

Por ejemplo, la clase principal:

import javax.swing.JFrame; 
import javax.swing.SwingUtilities; 

public class MvcExample { 

    private static void createAndShowGui() { 
     MyView view = new MyView(); 
     MyMenuBar menuBar = new MyMenuBar(); 
     MyModel model = new MyModel(); 
     MyControl control = new MyControl(model); 
     control.addProgressMonitor(view); 
     control.addView(view); 
     control.addView(menuBar); 

     model.setState(MyState.STOP); 

     JFrame frame = new JFrame("MVC Example"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.getContentPane().add(view.getMainPanel()); 
     frame.setJMenuBar(menuBar.getMenuBar()); 
     frame.pack(); 
     frame.setLocationByPlatform(true); 
     frame.setVisible(true); 

    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      createAndShowGui(); 
     } 
     }); 
    } 

    private static final byte[] DATA_ARRAY = { 0x43, 0x6f, 0x70, 0x79, 0x72, 
     0x69, 0x67, 0x68, 0x74, 0x20, 0x46, 0x75, 0x62, 0x61, 0x72, 0x61, 
     0x62, 0x6c, 0x65, 0x2c, 0x20, 0x30, 0x36, 0x2f, 0x31, 0x36, 0x2f, 
     0x32, 0x30, 0x31, 0x32, 0x2e, 0x20, 0x46, 0x75, 0x62, 0x61, 0x72, 
     0x61, 0x62, 0x6c, 0x65, 0x20, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x21 }; 

} 

El Control:

import java.awt.event.ActionEvent; 
import java.awt.event.KeyEvent; 
import java.beans.PropertyChangeEvent; 
import java.beans.PropertyChangeListener; 
import java.util.ArrayList; 
import java.util.List; 

import javax.swing.AbstractAction; 

@SuppressWarnings("serial") 
public class MyControl { 
    private MyModel model; 
    private PlayAction playAction = new PlayAction(); 
    private PauseAction pauseAction = new PauseAction(); 
    private StopAction stopAction = new StopAction(); 
    private List<MyProgressMonitor> progMonitorList = new ArrayList<MyProgressMonitor>(); 

    public MyControl(MyModel model) { 
     this.model = model; 

     model.addPropertyChangeListener(new MyPropChangeListener()); 
    } 

    public void addProgressMonitor(MyProgressMonitor progMonitor) { 
     progMonitorList.add(progMonitor); 
    } 

    public void addView(MySetActions setActions) { 
     setActions.setPlayAction(playAction); 
     setActions.setPauseAction(pauseAction); 
     setActions.setStopAction(stopAction); 
    } 

    private class MyPropChangeListener implements PropertyChangeListener { 
     @Override 
     public void propertyChange(PropertyChangeEvent pcEvt) { 
     if (MyState.class.getName().equals(pcEvt.getPropertyName())) { 
      MyState state = (MyState) pcEvt.getNewValue(); 

      if (state == MyState.PLAY) { 
       playAction.setEnabled(false); 
       pauseAction.setEnabled(true); 
       stopAction.setEnabled(true); 
      } else if (state == MyState.PAUSE) { 
       playAction.setEnabled(true); 
       pauseAction.setEnabled(false); 
       stopAction.setEnabled(true); 
      } else if (state == MyState.STOP) { 
       playAction.setEnabled(true); 
       pauseAction.setEnabled(false); 
       stopAction.setEnabled(false); 
      } 
     } 
     if (MyModel.PROGRESS.equals(pcEvt.getPropertyName())) { 
      for (MyProgressMonitor progMonitor : progMonitorList) { 
       int progress = (Integer) pcEvt.getNewValue(); 
       progMonitor.setProgress(progress); 
      }    
     } 
     } 
    } 

    private class PlayAction extends AbstractAction { 
     public PlayAction() { 
     super("Play"); 
     putValue(MNEMONIC_KEY, KeyEvent.VK_P); 
     } 

     @Override 
     public void actionPerformed(ActionEvent e) { 
     model.play(); 
     } 
    } 

    private class StopAction extends AbstractAction { 
     public StopAction() { 
     super("Stop"); 
     putValue(MNEMONIC_KEY, KeyEvent.VK_S); 
     } 

     @Override 
     public void actionPerformed(ActionEvent e) { 
     model.stop(); 
     } 
    } 
    private class PauseAction extends AbstractAction { 
     public PauseAction() { 
     super("Pause"); 
     putValue(MNEMONIC_KEY, KeyEvent.VK_A); 
     } 

     @Override 
     public void actionPerformed(ActionEvent e) { 
     model.pause(); 
     } 
    } 
} 

Un Estado Enum:

public enum MyState { 
    PLAY, STOP, PAUSE 
} 

una de las interfaces de vista:

import javax.swing.Action; 

public interface MySetActions { 

    void setPlayAction(Action playAction); 
    void setPauseAction(Action pauseAction); 
    void setStopAction(Action stopAction); 
} 

Otra interfaz de vista:

public interface MyProgressMonitor { 
    void setProgress(int progress); 
} 

La interfaz de usuario principal Vista:

import java.awt.BorderLayout; 
import java.awt.GridLayout; 

import javax.swing.Action; 
import javax.swing.BorderFactory; 
import javax.swing.JButton; 
import javax.swing.JComponent; 
import javax.swing.JPanel; 
import javax.swing.JProgressBar; 

public class MyView implements MySetActions, MyProgressMonitor { 
    private JButton playButton = new JButton(); 
    private JButton stopButton = new JButton(); 
    private JButton pauseButton = new JButton(); 
    private JPanel mainPanel = new JPanel(); 
    private JProgressBar progressBar = new JProgressBar(); 

    public MyView() { 
     progressBar.setBorderPainted(true); 

     JPanel btnPanel = new JPanel(new GridLayout(1, 0, 5, 0)); 
     btnPanel.add(playButton); 
     btnPanel.add(pauseButton); 
     btnPanel.add(stopButton); 

     mainPanel.setLayout(new BorderLayout(0, 5)); 
     mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 15, 5, 15)); 
     mainPanel.add(btnPanel, BorderLayout.CENTER); 
     mainPanel.add(progressBar, BorderLayout.PAGE_END); 
    } 

    @Override 
    public void setPlayAction(Action playAction) { 
     playButton.setAction(playAction); 
    } 

    @Override 
    public void setStopAction(Action stopAction) { 
     stopButton.setAction(stopAction); 
    } 

    @Override 
    public void setPauseAction(Action pauseAction) { 
     pauseButton.setAction(pauseAction); 
    } 

    @Override 
    public void setProgress(int progress) { 
     progressBar.setValue(progress); 
    } 

    public JComponent getMainPanel() { 
     return mainPanel; 
    } 

} 

La porción de barra de menú de la Vista:

import java.awt.event.KeyEvent; 
import javax.swing.Action; 
import javax.swing.JMenu; 
import javax.swing.JMenuBar; 
import javax.swing.JMenuItem; 

public class MyMenuBar implements MySetActions { 
    private JMenuItem playMenItem = new JMenuItem(); 
    private JMenuItem pauseMenuItem = new JMenuItem(); 
    private JMenuItem stopMenItem = new JMenuItem(); 
    private JMenuBar menuBar = new JMenuBar(); 

    public MyMenuBar() { 
     JMenu menu = new JMenu("Main Menu"); 
     menu.setMnemonic(KeyEvent.VK_M); 
     menu.add(playMenItem); 
     menu.add(pauseMenuItem); 
     menu.add(stopMenItem); 
     menuBar.add(menu); 
    } 

    public JMenuBar getMenuBar() { 
     return menuBar; 
    } 

    @Override 
    public void setPlayAction(Action playAction) { 
     playMenItem.setAction(playAction); 
    } 

    @Override 
    public void setStopAction(Action stopAction) { 
     stopMenItem.setAction(stopAction); 
    } 

    @Override 
    public void setPauseAction(Action pauseAction) { 
     pauseMenuItem.setAction(pauseAction); 
    } 

} 

Y, por último, el modelo:

import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.beans.PropertyChangeListener; 
import javax.swing.Timer; 
import javax.swing.event.SwingPropertyChangeSupport; 

public class MyModel { 
    public final static String PROGRESS = "progress"; 
    protected static final int MAX_PROGRESS = 100; 
    private MyState state = null; 
    private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(
     this); 
    private Timer timer; 
    private int progress = 0; 

    public MyState getState() { 
     return state; 
    } 

    public void setState(MyState state) { 
     MyState oldValue = this.state; 
     MyState newValue = state; 
     this.state = newValue; 
     pcSupport.firePropertyChange(MyState.class.getName(), oldValue, newValue); 
    } 

    public int getProgress() { 
     return progress; 
    } 

    public void setProgress(int progress) { 
     Integer oldValue = this.progress; 
     Integer newValue = progress; 
     this.progress = newValue; 
     pcSupport.firePropertyChange(PROGRESS, oldValue, newValue); 
    } 

    public void play() { 
     MyState oldState = getState(); 
     setState(MyState.PLAY); 

     if (oldState == MyState.PAUSE) { 
     if (timer != null) { 
      timer.start(); 
      return; 
     } 
     } 
     int timerDelay = 50; 
     // simulate playing .... 
     timer = new Timer(timerDelay, new ActionListener() { 
     int timerProgress = 0; 

     @Override 
     public void actionPerformed(ActionEvent actEvt) { 
      timerProgress++; 
      setProgress(timerProgress); 
      if (timerProgress >= MAX_PROGRESS) { 
       setProgress(0); 
       MyModel.this.stop(); 
      } 
     } 
     }); 
     timer.start(); 
    } 

    public void pause() { 
     setState(MyState.PAUSE); 
     if (timer != null && timer.isRunning()) { 
     timer.stop(); 
     } 
    } 

    public void stop() { 
     setState(MyState.STOP); 
     setProgress(0); 
     if (timer != null && timer.isRunning()) { 
     timer.stop(); 
     } 
     timer = null; 
    } 

    public void addPropertyChangeListener(PropertyChangeListener listener) { 
     pcSupport.addPropertyChangeListener(listener); 
    } 

    public void removePropertyChangeListener(PropertyChangeListener listener) { 
     pcSupport.removePropertyChangeListener(listener); 
    } 
} 

Pregunte si algo de esto es lo menos confuso.

+0

Su implementación @Hovercraft Full Of Eels es bastante impresionante, gracias. Estoy empezando a aprender sobre el patrón MVC y estoy usando su código como punto de partida para mi aplicación. Sin embargo, me gustaría preguntarle cómo implementaría un JFileChooser para abrir un archivo y obtener su ruta absoluta "impresa" en la GUI. Por ejemplo, no sé si implementar JFileChooser como lo hizo con MyMenuBar o simplemente declararlo en el Modelo. – DaveQuinn

+0

@PMMP: tal vez [este ejemplo] (http://stackoverflow.com/a/15729267/522444) (haga clic en el enlace) que use un JFileChooser podría ayudarlo. –

Cuestiones relacionadas