2013-07-06 12 views
6

Es ist oft erforderlich, das Verhalten anderer GUI-Objekte abhängig vom Status eines anderen GUI-Objekts zu ändern. Z.B. Wenn eine Taste gedrückt wird, ändert ein Etikett seinen Namen. Wenn ich jedoch ein AbstractAction-Objekt wie JButton myButton = new JButton(myButtonAction); verwende, brauche ich einen Verweis auf die GUI-Objekte in dem Objekt, das von AbstractAction erbt. Soll ich einfach die AbstractAction-Objekte in der GUI erstellen und dann alle notwendigen GUI-Referenzen an die AbstractAction-Objekte übergeben oder könnte das als schlechter Stil betrachtet werden?Java: Wie verweist man auf GUI-Komponenten von einem AbstractAction-Objekt?

Um es konkreter:

// 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

Siehe bearbeiten für ein anderes Beispiel zu beantworten –

Antwort

6

Sie wollen Kupplung lösen, so viel wie möglich, ziehen Sie es nicht als Frage vermuten lässt, und dies zu tun, denke ich, dass Sie weitere Abstraktion tun sollen, das durch Teile noch weiter in ein vollwertiges MVC-Programm aufteilen. Dann kann der Listener (die Aktion) das Modell ändern, und die Ansicht, bei der es sich um Ihre GUI handelt, kann die Änderungen des Modells abhören und entsprechend reagieren.

Zum Beispiel:

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

    } 

} 

Der Schlüssel in meinem Kopf ist, dass das Modell nichts von der Ansicht kennt, und der Blick weiß wenig (hier nichts) über das Modell.

+0

Danke :-) Allerdings kann ich nicht die Vor- und Nachteile der Observer und der PropertyChangeEvent-Version bewerten. – user1812379

+0

Ich sehe, dass 'MyButtonAction' Zugriff auf' model.incrementStatus(); 'hat, weil' MyButtonAction' in 'MvcEgControl' deklariert ist. Aber wie würden Sie es tun, wenn 'MyButtonAction' in einer separaten Datei deklariert wäre? Wie würden Sie ihm dann Zugang zum Modell geben? – trusktr

+1

@trusktr: Sie könnten einfach den Konstruktor von MyButtonAction mit einem anderen Parameter, einem Model-Parameter, versehen und damit ein Model-Klassenfeld setzen und damit seiner actionPerformed-Methode eine Model-Referenz zuweisen, mit der öffentliche Model-Methoden aufgerufen werden. –

5

Amplifying on @ Hovercraft's vorgeschlagenen Ansatz, lassen Sie Ihre Taste und Label-Zugriff ein gemeinsames Modell. Die Schaltfläche Action aktualisiert das Modell, und das Modell benachrichtigt das hörende Etikett, möglicherweise unter Verwendung eines PropertyChangeListener wie beschrieben here. Ein ausführlicheres Beispiel wird in den konkreten Implementierungen von javax.swing.text.EditorKit gesehen, die auf einem gemeinsamen Document-Modell arbeiten, das von Swing-Text-Komponenten verwendet wird.

+0

Danke. Ich kenne die MVC, aber es ist ein abstraktes Konzept, dessen Implementierungen stark variieren. Es gibt sogar Leute, die sagen, dass MVC ein Anti-Muster ist und dass MVP bevorzugt werden sollte. Das Schlimme daran ist, dass es keine eindeutige Antwort darauf gibt. In meinem Beispiel wäre ein PropertyChangeListener der einfachste Weg, dies zu implementieren? Manche benutzen auch Beobachter. – user1812379

+1

@ user1812379: Der Schlüssel, den ich denke, ist nicht, welches genaue Muster Sie verwenden, sondern dass Sie Schnittstellen verwenden, und dass Sie versuchen, die Kohäsion zu maximieren und gleichzeitig die Kopplung zu minimieren. –

+1

Danke Jungs. Mein Problem hier ist, dieses abstrakte Konzept an einen konkreten Fall anzupassen. Vielleicht sollte ich mit diesem Standardbeobachteransatz beginnen, den mir der Trashgod gezeigt hat. – user1812379