Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: How to reference GUI components from an AbstractAction object?

It is often necessary to change the behaviour of other GUI objects depending on the state of another GUI object. E.g. when a button is pressed, a label shall change its name. However, when I use an AbstractAction object like JButton myButton = new JButton(myButtonAction); I need a reference to the GUI objects in the object that inherits from AbstractAction. Should I just create the AbstractAction objects in the GUI and then pass all the necessary GUI references to the AbstractAction objects or could that be considered bad style?

To make it more concrete:

// 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);
   }
}
like image 927
user1812379 Avatar asked Jul 06 '13 13:07

user1812379


2 Answers

You want to loosen coupling as much as possible, not tighten it as your question suggests, and to do this, I think that you should do further abstraction, by separating portions even further into a full-fledged MVC program. Then the listener (the Action) can change the model, and the view, which is your GUI, can listen for model's changes and respond accordingly.

For example:

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

   }

}

The key in my mind is that the Model knows nothing of the view, and the view knows little (here nothing) about the model.

like image 106
Hovercraft Full Of Eels Avatar answered Sep 22 '22 02:09

Hovercraft Full Of Eels


Amplifying on @Hovercraft's suggested approach, let your button and label access a common model. The button's Action updates the model, and the model notifies the listening label, perhaps using a PropertyChangeListener as outlined here. A more elaborate example is seen in the concrete implementations of javax.swing.text.EditorKit, which operate on a common Document model used by swing text components.

like image 34
trashgod Avatar answered Sep 20 '22 02:09

trashgod