6

I have written a Swing GUI with several controls associated with the same Action subclass. The implementation of the Action subclass follows this psudocode:

public class MyGUI 
{
  Gizmo gizmo_;  // Defined elsewhere

  public class Action_StartPlayback extends AbstractAction 
  {
    /* ctor */
    public Action_StartPlayback(String text, ImageIcon icon, String desc, Integer mnem)
    {
      super(text, icon);
      putValue(SHORT_DESCRIPTION, desc);
      putValue(MNEMONIC_KEY, mnem);
    }

    @Override public boolean isEnabled()
    {
      return gizmo_ == null;
    }
    @Override public void actionPerformed(ActionEvent e) 
    {
      gizmo_ = new Gizmo();
    }

  Action_StartPlayback act_;
};

The action is associated with both a button and a menu item, in a way similar to this psudocode:

act_ = new Action_StartPlayback(/*...*/);
// ...
JButton btn = new JButton(act_);
JMenu mnu = new JMenu(act_);

When I click the button or the menu item, the action's actionPerformed is fired correctly, gizmo_ is initialized and is non-null and everything works as expected -- except that the button and menu item are still enabled.

I expected that isEnabled would have been called again "automagically" but this is obviously not happening. isEnabled() is never called again.

This evokes two questions:

  1. Is it OK for me to @Override the isEnabled() method as I have done here?
  2. Assuming the answer to #1 is yes, how do I trigger a refresh of the GUI so that isEnabled() is called again, resulting in the button & menu item being disabled?
John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • I would perhaps recommend posting a [SSCCE](http://sscce.org) for better help sooner – David Kroukamp Jul 09 '12 at 16:08
  • @DavidKroukamp: I can do that, though it will take some time. Having experience with other GUI frameworks like MFC, I had expected that this would be a fairly fundamental task that most Java GUI programmers face. Perhaps that's not the case. – John Dibling Jul 09 '12 at 16:09

4 Answers4

3

Instead of overriding setEnabled you could simply call setEnabled(false) after you intitialize your gizmo in your actionPerformed method:

@Override public void actionPerformed(ActionEvent e) 
{
  gizmo_ = new Gizmo();
  setEnabled(false);
}

Here's the setEnabled implementation from AbstractAction:

public void setEnabled(boolean newValue) {
  boolean oldValue = this.enabled;
  if (oldValue != newValue) {
    this.enabled = newValue;
    firePropertyChange("enabled", 
            Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
  }
}

The automagical you're looking for is the call to firePropertyChange, which notifies components based on this action that the state has changed, so the component can update its own state accordingly.

darri
  • 562
  • 1
  • 5
  • 10
  • Is that code from AbstractAction? If so, why don't they allow "enabled" to be a valid property name? There is no constant field of Action or AbstractAction that shows this is so. – Hovercraft Full Of Eels Jul 09 '12 at 16:20
  • My initial approach was to call `setEnabled()` from within `actionPerformed()`, and this worked. However, in my actual use case I have several controls whose enabled-ness is tied to some central reference variable (in this example, `gizmo_`). I was trying to seperate the buisness logic (what `actionPerformed()` actually does) from the GUI updating logic (setting/clearing various enabled states). – John Dibling Jul 09 '12 at 16:20
2

I am no pro at this, but I don't see a see an automatic way of doing this, of notifying listeners that the state of enabled has changed. Of course you can call setEnabled(false) at the start of the actionPerformed, and then code Gizmo (or a wrapper on Gizmo) to have property change support and then add a PropertyChangeListener to Gizmo, and in that listener, when the state changes to DONE, call setEnabled(true). A bit kludgy but it would work.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • If true, this makes me a sad coder. The kludgy-ness is exactly what I'm trying to avoid, since I'm ultimately going to have a dozen or so controls and a bunch of different actions. Maintaining all of this is the actions themselves are responsible for updating the enabled states of other actions would become quite troublesome from a maintenance perspective. – John Dibling Jul 09 '12 at 16:25
  • @Grunch: I consider Gizmo to be part of the "model" of your GUI, and it's certainly not unusual to add listeners to the model and change view state due to these listeners being fired. It's a little lower level then you're used to, but it's quite do-able and not too kludgy. – Hovercraft Full Of Eels Jul 09 '12 at 16:57
2

This is not strictly limited to Swing, but a more general Java principle. A lot of classes in the JDK (and in other libraries) have a getter and a setter for a property. Those methods are not meant to be overridden to return a dynamic value as most of the times the superclass accesses the corresponding field directly and does not go through the getters.

If you have dynamic behavior, you should call the corresponding setter each time the value changes. This will notify the super class changes have been made, and typically this will also fire a property change event to notify other interested parties.

You can find a bit more on this convention if you do a search on Java beans.

In your case, a possible solution is to let your UI class fire a PropertyChangeEvent when that gizmo instance changes, and let your actions listen for that event. When they receive such an event, they update their own enabled state.

Robin
  • 36,233
  • 5
  • 47
  • 99
  • +1: So basically what you're saying is I'm not doing this the "Java way," and that's why it's not working. I was afraid this might be the case. I'll look in to firing a `PropertyChangeEvent,` which is something I've not heard of before. (I'm a C++ guy, only been doing Java for a few days) – John Dibling Jul 09 '12 at 16:32
  • @Grunch indeed. Not sure how else you would implement this. Not firing any event would basically force all code to constantly check whether nothing has changed (for example a button to trigger your action would constantly need to check whether the `isEnabled` returns something different to update its enabled state) – Robin Jul 09 '12 at 17:40
0

The enabled-state is stored in both of your objects, in the AbstractAction and in the JButton.

This is important because you only need one instance of Action_StartPlayback for multiple Components like:

  1. In the menu's button.
  2. In a toolbar.
  3. In a Shortcut Strgp in example.

All of them can have the same instance of Action_startPlayback. The Action_startPlayback is the only source of truth. The components are responsible to respect this source of truth so every Component will ask the AbstractAction to notify them if something has been changed. The AbstractAction will remember all the components and will notify them using the Method firePropertyChange().

But how to repaint all pending components? You must force all pending Components to ask the Action_startPlayback for the actuall enabled-state! Look at this:

@Override public void actionPerformed(ActionEvent e) 
{
  gizmo_ = new Gizmo();
  // now, force the components to get notified.
  setEnabled(true);
}
Grim
  • 1,938
  • 10
  • 56
  • 123