2013-07-06 4 views
6

Spesso è necessario modificare il comportamento di altri oggetti della GUI in base allo stato di un altro oggetto della GUI. Per esempio. quando viene premuto un pulsante, un'etichetta deve cambiare il suo nome. Tuttavia, quando uso un oggetto AbstractAction come JButton myButton = new JButton(myButtonAction); ho bisogno di un riferimento agli oggetti della GUI nell'oggetto che eredita da AbstractAction. Devo solo creare gli oggetti AbstractAction nella GUI e quindi passare tutti i riferimenti necessari della GUI agli oggetti AbstractAction o potrebbero essere considerati di cattivo stile?Java: come fare riferimento ai componenti della GUI da un oggetto AbstractAction?

per renderlo più concreto:

// AbstractAction 
    public class MyAction extends AbstractAction { 
     public MyAction(String name, 
          String description, Integer mnemonic, JLabel) { 
      super(name); 
      putValue(SHORT_DESCRIPTION, description); 
      putValue(MNEMONIC_KEY, mnemonic); 
     } 
     public void actionPerformed(ActionEvent e) { 

       // do something  
      } 
     } 
    } 

public class GUI{ 
    public Action myAction = null; 

    public GUI(){  
     JLabel label = new JLabel("text"); 
     //This is not a good idea: 
     myAction = new MyAction("some text" , desc, new Integer(KeyEvent.VK_Q), label); 

     JButton myButton = new JButton(myAction); 
    } 
} 
+0

Vedere la modifica per rispondere ad un altro esempio –

risposta

6

Si desidera per allentare l'accoppiamento, per quanto possibile, non stringerlo come suggerisce la tua domanda, e per fare questo, penso che si dovrebbe fare ulteriore astrazione, da separando ulteriormente le porzioni in un vero e proprio programma MVC. Quindi l'ascoltatore (l'azione) può modificare il modello e la vista, che è la GUI, può ascoltare le modifiche del modello e rispondere di conseguenza.

Ad esempio:

import java.awt.BorderLayout; 
import java.awt.Component; 
import java.awt.Dimension; 
import java.awt.GridBagLayout; 
import java.awt.event.ActionEvent; 
import java.awt.event.KeyEvent; 
import java.beans.PropertyChangeEvent; 
import java.beans.PropertyChangeListener; 

import javax.swing.*; 
import javax.swing.event.SwingPropertyChangeSupport; 

public class MvcEg { 

    private static void createAndShowGui() { 
     View view = new MvcEgView(); 
     Model model = new MvcEgModel(); 
     new MvcEgControl(model, view); 

     JFrame frame = new JFrame("MvcEg"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.getContentPane().add(view.getMainPanel()); 
     frame.pack(); 
     frame.setLocationByPlatform(true); 
     frame.setVisible(true); 
    } 

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

interface View { 

    void setMyButtonAction(Action action); 

    Component getMainPanel(); 

    void setStatusLabelText(String text); 

} 

@SuppressWarnings("serial") 
class MvcEgView implements View { 
    private static final int PREF_W = 500; 
    private static final int PREF_H = 400; 
    private static final String STATUS_TEXT = "Status: "; 
    private JPanel mainPanel = new JPanel() { 
     @Override 
     public Dimension getPreferredSize() { 
     return new Dimension(PREF_W, PREF_H); 
     } 
    }; 
    private JLabel statusLabel = new JLabel(STATUS_TEXT, SwingConstants.CENTER); 
    private JButton myButton = new JButton(); 

    public MvcEgView() { 
     JPanel btnPanel = new JPanel(new GridBagLayout()); 
     btnPanel.add(myButton); 

     mainPanel.setLayout(new BorderLayout()); 
     mainPanel.add(btnPanel, BorderLayout.CENTER); 
     mainPanel.add(statusLabel, BorderLayout.SOUTH); 
    } 

    @Override 
    public void setMyButtonAction(Action action) { 
     myButton.setAction(action); 
    } 

    @Override 
    public void setStatusLabelText(String text) { 
     statusLabel.setText(STATUS_TEXT + text); 
    } 

    @Override 
    public Component getMainPanel() { 
     return mainPanel; 
    } 
} 

interface Model { 
    public static final String MOD_FIVE_STATUS = "mod five status"; 

    void incrementStatus(); 

    ModFiveStatus getModFiveStatus(); 

    void removePropertyChangeListener(PropertyChangeListener listener); 

    void addPropertyChangeListener(PropertyChangeListener listener); 

    void setModFiveStatus(ModFiveStatus modFiveStatus); 

} 

class MvcEgModel implements Model { 
    private ModFiveStatus modFiveStatus = ModFiveStatus.ZERO; 
    private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(
     this); 

    @Override 
    public void incrementStatus() { 
     int value = modFiveStatus.getValue(); 
     value++; 
     value %= ModFiveStatus.values().length; 
     setModFiveStatus(ModFiveStatus.getValuesStatus(value)); 
    } 

    @Override 
    public void setModFiveStatus(ModFiveStatus modFiveStatus) { 
     ModFiveStatus oldValue = this.modFiveStatus; 
     ModFiveStatus newValue = modFiveStatus; 
     this.modFiveStatus = modFiveStatus; 
     pcSupport.firePropertyChange(MOD_FIVE_STATUS, oldValue, newValue); 
    } 

    @Override 
    public ModFiveStatus getModFiveStatus() { 
     return modFiveStatus; 
    } 

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

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

} 

enum ModFiveStatus { 
    ZERO(0, "Zero"), ONE(1, "One"), TWO(2, "Two"), THREE(3, "Three"), FOUR(4, "Four"); 
    private int value; 
    private String text; 

    private ModFiveStatus(int value, String text) { 
     this.value = value; 
     this.text = text; 
    } 

    public int getValue() { 
     return value; 
    } 

    public String getText() { 
     return text; 
    } 

    public static ModFiveStatus getValuesStatus(int value) { 
     if (value < 0 || value >= values().length) { 
     throw new ArrayIndexOutOfBoundsException(value); 
     } 

     for (ModFiveStatus modFiveStatus : ModFiveStatus.values()) { 
     if (modFiveStatus.getValue() == value) { 
      return modFiveStatus; 
     } 
     } 
     // default that should never happen 
     return null; 
    } 

} 

@SuppressWarnings("serial") 
class MvcEgControl { 
    private Model model; 
    private View view; 

    public MvcEgControl(final Model model, final View view) { 
     this.model = model; 
     this.view = view; 

     view.setMyButtonAction(new MyButtonAction("My Button", KeyEvent.VK_B)); 
     view.setStatusLabelText(model.getModFiveStatus().getText()); 
     System.out.println("model's status: " + model.getModFiveStatus()); 
     System.out.println("model's status text: " + model.getModFiveStatus().getText()); 

     model.addPropertyChangeListener(new ModelListener()); 
    } 

    private class MyButtonAction extends AbstractAction { 


     public MyButtonAction(String text, int mnemonic) { 
     super(text); 
     putValue(MNEMONIC_KEY, mnemonic); 
     } 

     @Override 
     public void actionPerformed(ActionEvent e) { 
     model.incrementStatus(); 
     System.out.println("button pressed"); 
     } 
    } 

    private class ModelListener implements PropertyChangeListener { 

     @Override 
     public void propertyChange(PropertyChangeEvent evt) { 
     if (evt.getPropertyName().equals(Model.MOD_FIVE_STATUS)) { 
      String status = model.getModFiveStatus().getText(); 
      view.setStatusLabelText(status); 
      System.out.println("status is: " + status); 
     } 
     } 

    } 

} 

La chiave nella mia mente è che il modello non sa nulla della vista, e la vista conosce poco (qui nulla) sul modello.

+0

Grazie :-) Tuttavia, non riesco a valutare i pro e i contro della versione di Observer e PropertyChangeEvent. – user1812379

+0

Vedo che 'MyButtonAction' ha accesso a' model.incrementStatus(); 'perché' MyButtonAction' è dichiarato all'interno di 'MvcEgControl'. Ma come lo faresti se 'MyButtonAction' fosse dichiarato in un file separato? Come vorresti allora dare accesso al modello? – trusktr

+1

@trusktr: si potrebbe semplicemente dare al costruttore di MyButtonAction un altro parametro, un parametro Model, e usarlo per impostare un campo di classe Model, e quindi consentire al suo metodo actionPerformed un riferimento a Model con cui chiamare i metodi Model pubblici. –

5

Amplificando l'approccio suggerito da @ Hovercraft, consente al pulsante e all'etichetta di accedere a un modello comune. Il pulsante Action aggiorna il modello e il modello notifica l'etichetta in ascolto, forse utilizzando uno PropertyChangeListener come indicato here. Un esempio più elaborato è rappresentato dalle implementazioni concrete di javax.swing.text.EditorKit, che operano su un modello comune Document utilizzato da componenti di testo mobile.

+0

Grazie. Conosco MVC, ma è un concetto astratto le cui implementazioni variano fortemente. Ci sono anche persone che dicono che MVC è un modello anti e che MVP dovrebbe essere preferito. La cosa brutta è che non c'è una risposta definitiva a questo. Nel mio esempio, un PropertyChangeListener sarebbe il modo più semplice per implementarlo? Alcuni usano anche osservatori. – user1812379

+1

@ user1812379: la chiave penso non sia il modello preciso che si usa, ma che si usano le interfacce e si cerca di massimizzare la coesione riducendo al minimo l'accoppiamento. –

+1

Grazie ragazzi. Il mio problema qui è di adattare questo concetto astratto a un caso concreto. Forse dovrei iniziare con questo approccio standard da osservatore che mi ha mostrato trashgod. – user1812379