views:

113

answers:

3

I have recently implemented an Action that toggles the enabled status of a business function. Whenever the user invokes the action I set a boolean flag: "willEnable" to determine whether the next invocation will enable or disable the function. Likewise I update the action name, short description and icon to reflect the new state. Then within my actionPerformed(...) method I take a different action based on the state of willEnable.

  1. Is this the correct approach or can anyone recommend a better one (as I suspect this is a common problem)? (I can see that JToggleButton acts as a two-state button but I want this Action to be visible on a JMenuBar as well as a JButton, so do not think this is appropriate).

EDIT

Specifically, how do applications like IDEA deal with this? Would they use multi-state actions (as above) or would they swap a different Action into a given JButton using setAction(Action)? Perhaps this approach is better?

  1. When updating an action's properties can I rely on GUI components initialised with that Action (e.g. JButton) automatically repainting themselves? What if the JButton size changes as a result? Should I be revalidating the containing JPanel myself?
  2. Is changing the action's name a bad thing to do? This is the only way I can make the JButton text change, but am concious that the name should probably remain constant if the action is being placed in an ActionMap.

Thanks in advance.

+3  A: 

You may want to look at the State Pattern.

Basically, you create an interface and two (or more) implementations of it for each state. As a simple example, the disable state might just implement the interface to do nothing while the enabled state does some actions. To switch state you would simply do

interface IState {
  void doAction();
  boolean isEnabled();
}

class EnabledState implement IState {
  void doAction() {
    setState(new DisabledState());
    // do something
  }
  boolean isEnabled() {return true;}
}

class DisabledState implement IState {
  void doAction() {
    setState(new EnabledState());
    // do nothing
  }
  boolean isEnabled() {return false;}
}

private IState state = new DisabledState(); // default is disabled
private PropertyChangeSupport support = new PropertyChangeSupport(this);

void setState(IState state) {
  if (this.state != state) {
    IState oldState = this.state;
    this.state = state;
    support.firePropertyChange("enabled", oldState.isEnabled(), state.isEnabled());
  }
}

void doAction() {
  state.doAction();
}

While it is a little overhead for a single method, it certainly pays of as soon as you have several methods that change it's behavior depending on a single state.

sfussenegger
Thanks - Yes I could consider this although TBH the logic run based on the value of willEnable is only 1-2 lines of code anyway. I'm more concerned about whether there's a better solution in dealing with the UI-related side-effects of the state change, and whether there's an idiomatic approach of dealing with this problem when you have multiple "actions" based off one button.
Adamski
It's a long time since I've last done Swing, but can't you use the observer pattern to notify any GUI components of state changes in your controller?
sfussenegger
Well I could use PropertyChangeListener (I'd recommend avoiding Observer) to detect changes in the Action, and then revalidate the JPanel. I'm just not sure if it's required.
Adamski
I said "observer pattern" not observer. `PropertyChangeListener` (and all the other listeners) follow the observer pattern. And looking at the Javadoc for `javax.swing.Action.addPropertyChangeListener(PropertyChangeListener)` seems to prove me right: "When its enabled state or other property changes, the registered listeners are informed of the change". So your button should implement `PropertyChangeListener` and listen for changes of the enabled state.
sfussenegger
I've updated the code to fire a PropertyChangeEvent
sfussenegger
Yes that's what I thought (re. the JButton listening for changes). However, I'm not sure if the containing JPanel will ... I would hope so.
Adamski
+1  A: 

I would expect that any GUI component which is created with some data "model" (I would include an Action as a data model) should register itself as a listener to that model. It must take appropriate, erm, action on a PropertyChangeEvent being fired: any other behaviour would constitute a bug in my opinion.

The questionable thing here is whether it is legitimate to change the name of the action; I think that it is not legitimate. Your action logically is ToggleEnabledStatus and that does not change because the action has been invoked. Any component which needs to display any other text should register itself as a listener to the Action and then check your willEnable flag to take the appropriate, erm, action.

Alternately you could write your own class which implemented ToggleButtonModel and Action at the same time and controlled the change events from within it. This is a lot of code, however, for such little benefit

oxbow_lakes
This is what I thought (regarding not changing the action name). However, this means I need to explicitly set my JButton text to "Enable XYZ" or "Disable XYZ" each time the action is "toggled", as JButton uses the Action name as text by default. The alternative would be to use two different Actions and call setAction on the JButton each time. Not sure if this is better or worse ...
Adamski
A: 

As an aside, while building a Java GUI for the first time in a while, I realized that my JTable wasn't getting displayed in a JScrollPane properly - I create certain elements on demand - until I ran validate on the container holding the JScrollPane.

I'm now firmly of the opinion that a simple call to validate whenever you wholesale replace elements like this, is worthwhile. I wouldn't want to call validate one every single window, but if you take a container, delete it, and then replace it with a new one, it's probably best if the parent runs a quick validate on itself.

Anyway

parentContainer.validate()

solved my problem when nothing else did. Maybe I'm just screwing up somewhere else? Why does JScrollPane have to act differently from every other Swing container object?

Chris Kaminski