2013-07-06 11 views
6

Często konieczna jest zmiana zachowania innych obiektów GUI w zależności od stanu innego obiektu GUI. Na przykład. po naciśnięciu przycisku etykieta zmieni nazwę. Jednak, gdy używam obiektu AbstractAction, takiego jak JButton myButton = new JButton(myButtonAction);, potrzebuję odwołania do obiektów GUI w obiekcie dziedziczącym z AbstractAction. Czy powinienem po prostu utworzyć obiekty AbstractAction w GUI, a następnie przekazać wszystkie niezbędne odniesienia GUI do obiektów AbstractAction, czy może to być uznane za zły styl?Java: Jak odwoływać się do komponentów GUI z obiektu AbstractAction?

Aby uczynić go bardziej konkretne:

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

Zobacz edytuj, aby odpowiedzieć na inny przykład. –

Odpowiedz

6

Chcesz poluzować sprzęgło jak najwięcej, nie dokręcić jak sugeruje swoje pytanie, a aby to zrobić, myślę, że należy zrobić dalsze abstrakcji, przez oddzielając porcje jeszcze bardziej w pełnoprawnym programie MVC. Następnie słuchacz (Akcja) może zmienić model, a widok, który jest twoim GUI, może nasłuchiwać zmian modelu i odpowiednio reagować.

Na przykład:

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

    } 

} 

Kluczem w głowie to, że model nie wie nic o widoku, a widok wie niewiele (tu nic) dotyczącą tego modelu.

+0

Dzięki :-) Nie mogę jednak ocenić zalet i wad wersji Observer i PropertyChangeEvent. – user1812379

+0

Widzę, że 'MyButtonAction' ma dostęp do' model.incrementStatus(); 'ponieważ' MyButtonAction' jest deklarowana wewnątrz 'MvcEgControl'. Ale jak by to zrobić, gdyby "MyButtonAction" została zadeklarowana w osobnym pliku? Jak więc dałbyś mu dostęp do modelu? – trusktr

+1

@trusktr: można po prostu przekazać konstruktorowi MyButtonAction inny parametr, parametr Modelu i użyć go do ustawienia pola klasy Model, a tym samym pozwolić, aby jego metoda ActionPerformed była odwołaniem do Modelu, do którego należy wywoływać publiczne metody modelowania. –

5

Wzmocnienie sugerowanego podejścia @Hovercraft, pozwól, aby przycisk i etykieta miały dostęp do wspólnego modelu. Przycisk Action przycisku aktualizuje model, a model powiadamia nasłuchującą etykietę, być może używając PropertyChangeListener, zgodnie z opisem here. Bardziej wyszukany przykład jest widoczny w konkretnych implementacjach javax.swing.text.EditorKit, które działają na wspólnym modelu Document używanym przez komponenty tekstu zmiennego.

+0

Dzięki. Znam MVC, ale jest to abstrakcyjna koncepcja, której implementacje są bardzo różne. Są nawet ludzie, którzy twierdzą, że MVC jest anty wzorcem i że MVP powinien być preferowany. Złą rzeczą jest to, że nie ma jednoznacznej odpowiedzi na to pytanie. W moim przykładzie, właściwość PropertyChangeListener byłaby najłatwiejszym sposobem na wdrożenie? Niektórzy korzystają również z obserwatorów. – user1812379

+1

@ user1812379: klucz, który myślę, nie jest jakim konkretnym wzorcem, którego używasz, ale że używasz interfejsów i że starasz się zmaksymalizować spójność przy jednoczesnym zminimalizowaniu sprzężenia. –

+1

Dziękuję wam. Moim problemem jest tutaj dostosowanie tej abstrakcyjnej koncepcji do konkretnego przypadku. Może powinienem zacząć od tego standardowego podejścia obserwatora, które pokazał mi trashgod. – user1812379

Powiązane problemy