2012-06-16 2 views
5

Sto scoprendo che scrivere codice OO con Swing è incredibilmente difficile. Il mio problema è essenzialmente che ho una visione (un JPanel) che ha ascoltatori di azioni. I listener di azioni individuano il pulsante su cui è stato fatto clic e chiamano il metodo di controllo appropriato. Il problema è che questo metodo di controller deve aggiornare un'altra vista. Quindi il problema che sto avendo è che ho dei punti di vista passati ai controller. Ecco un esempio.Come gestire la visualizzazione degli aggiornamenti dai controller in una Java Swing app

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

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

Questo è essenzialmente ciò che voglio, ma questo è ciò che finisce per accadere.

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); 
    } 
} 

E si può vedere che il numero di punti di vista che devono essere aggiornati e aumentare il numero di classi che assomigliano a questo aumento, i panorami sono essenzialmente variabili globali e il codice diventa complessa e poco chiara. Un altro problema che sto incontrando è che quest'altra vista di solito non viene passata direttamente in MyView, ma deve passare attraverso i genitori di MyView per arrivare a MyView, il che mi infastidisce.

Per un esempio reale di questo, diciamo che ho un Menu e questo MyView. MyView ha un pulsante di riproduzione, che suona un po 'di musica per un po' e disabilita (grigi) il pulsante di riproduzione fino a quando la musica è finita. Se ho un'opzione di menu chiamata play, ora ho bisogno di accedere al pulsante di riproduzione altre visualizzazioni in modo da poterlo ombreggiare. Come posso fare questo senza questo noioso passaggio di opinioni ovunque? Anche se potrebbero esserci soluzioni specifiche per questo problema, sto cercando qualcosa che risolva questo problema di accesso alla vista nel caso generale.

Non sono sicuro di come risolvere questo problema. In questo momento sto usando la terminologia del pattern MVC senza utilizzare il pattern MVC, che potrebbe essere necessario o meno. Qualsiasi aiuto è apprezzato.

+0

Vedere la modifica alla mia risposta oltre all'esempio di codice. –

risposta

1

In tale situazione tendo ad usare Singletons. Ovviamente ciò dipende dall'unicità dei tuoi punti di vista. Di solito ho Singletons per le mie "finestre" (JFrames) in modo da poter navigare da quelle a qualsiasi bambino ho bisogno utilizzando getter. Tuttavia questa potrebbe non essere la migliore idea in situazioni molto complesse.

12

Una soluzione: è sufficiente che il controller aggiorni il modello. Quindi i listener collegati al modello aggiornerebbero le visualizzazioni. Puoi anche fare in modo che JMenuItems e JButtons corrispondenti condividano la stessa azione, e quindi quando disabiliti l'azione, disabiliterà tutti i pulsanti/menu/etc che usano quell'azione.

Ad esempio, la classe principale:

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 }; 

} 

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(); 
     } 
    } 
} 

Uno Stato Enum:

public enum MyState { 
    PLAY, STOP, PAUSE 
} 

Una delle interfacce vista:

import javax.swing.Action; 

public interface MySetActions { 

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

Un'altra interfaccia vista:

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

L'interfaccia grafica principale 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 porzione barra dei menu della 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); 
    } 

} 

E, infine, il modello:

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); 
    } 
} 

Si prega di chiedere se questo è il minimo po 'di confusione.

+0

La tua implementazione @Hovercraft Full Of Eels è davvero sorprendente, grazie. Sto iniziando a conoscere il pattern MVC e sto utilizzando il codice come punto di partenza per la mia applicazione. Tuttavia vorrei chiederti come implementeresti un JFileChooser per aprire un file e ottenere il suo percorso assoluto "stampato" sulla GUI. Ad esempio, non so se implementare JFileChooser come hai fatto con MyMenuBar o semplicemente dichiararlo nel modello. – DaveQuinn

+0

@PMMP: forse [questo esempio] (http://stackoverflow.com/a/15729267/522444) (fare clic sul collegamento) che utilizza JFileChooser potrebbe essere di aiuto. –