The Source for Java Technology Collaboration
User: Password:



Scott Violet's Blog

November 2005 Archives


Changes to Actions in 1.6

Posted by zixle on November 21, 2005 at 08:16 AM | Permalink | Comments (15)

In my last two blogs I've covered various aspects of Actions. I had originally wanted to write about the changes to Actions in 1.6 but felt the background would make interesting blogs. I promise this is the last blog on Actions for a while;)

In 1.6 we've overhauled Actions adding new features and fixing a handful of annoying bugs. For this blog I'm going to cover the new features and when you might use them.

Action.SELECTED_KEY

The single biggest request for Actions was to add support for Actions to represent selected state. In particular when an Action is attached to a JRadioButton, JCheckBox, JToggleButton and the like there was no way to change the selected state of the component from the Action and have it reflected in the component. The new key Action.SELECTED_KEY takes care of that. All components that visually represent a selection will now listen for changes to the SELECTED_KEY and reflect that in the component.

The following code creates a JRadioButton from an Action that is initially selected:

  Action action = new MyAction();
  // Make the action selected. 
  action.putValue(Action.SELECTED_KEY, Boolean.TRUE);
  JRadioButton selectedRadioButton = new JRadioButton(action);
The SELECTED_KEY property is a bit different than other Action properties. In particular the SELECTED_KEY property is both read and set from the component; none of the other Action properties have this characteristic. For example, if the user clicks on the radio button to make it selected, the SELECTED_KEY property is set on the Action from the component.

The following example shows how an Action could customize it's behavior based on the selected state:

  class BoldAction implements Action {
    public void actionPerformed(ActionEvent e) {
      toggleBold((Boolean)getValue(Action.SELECTED_KEY));
    }
  }

Action.LARGE_ICON_KEY

All buttons and menu items have the ability to show an Icon. When a button or menu item is associated with an Action the button or menu item uses the Icon from the Action. This is problematic because typically you want two different icons for these two components. To address this in 1.6 we added LARGE_ICON_KEY. Menu items will only use the Icon from the SMALL_ICON property. Buttons will first look for the LARGE_ICON_KEY property, if it's value is non-null it will be used, otherwise the value from SMALL_ICON is used. This enables you to customize the Icon for the two different components.

The following example creates an Action with two different icons. The menu item will use smallIcon and the button largeIcon:

  Action action = new MyAction();
  action.putValue(Action.SMALL_ICON, smallIcon);
  action.putValue(Action.LARGE_ICON_KEY, largeIcon);
  // menu item will use smallIcon
  new JMenuItem(action);
  // button will use largeIcon
  new JButton(action);
Note: the term button is a bit overloaded. AbstractButton is the superclass of both menu items and classes such as JButton, JToggleButton, JRadioButton ... In this context when referring to button I mean all non JMenuItem subclasses that extend AbstractButton.

Action.DISPLAYED_MNEMONIC_INDEX_KEY

This is useful for the rare cases where you don't want the first occurence of a character in the text to be underlined. For example, a menu item with the text 'Save As' typically has the second 'a' underlined. The following code shows how to do this:

  Action action = new MyAction();
  action.putValue(Action.NAME, "Save As");
  // Notice the autoboxing here.
  action.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_A);
  action.putValue(Action.DISPLAYED_MNEMONIC_INDEX_KEY, 5);
  new JMenuItem(action);
Be careful when using DISPLAYED_MNEMONIC_INDEX_KEY. In particular if you change the mnemonic you should be sure you change the DISPLAYED_MNEMONIC_INDEX_KEY as well.

AbstractButton.setHideActionText

This property allows you to specify whether or not AbstractButton should show the NAME property from the Action as the buttons text. Consider if you have a menu item and toolbar button attached to the same Action. To have non-empty text on the menu item the Action will have a non-null value for it's NAME property. If you don't want the toolbar button to show the text you would invoke setHideActionText(true). The following code illustates this:
  Action action = new MyAction();
  action.putValue(Action.NAME, "Save As");
  new JMenuItem(action);
  JButton toolbarButton = new JButton(action);
  // We don't want the button to mirror the text from the Action.
  toolbarButton.setHideActionText(true);

System property swing.actions.reconfigureOnNull

The beans specification indicates that a null property name can be used to indicate multiple values have changed. By default Swing components that take an Action do not handle such a change. To indicate that Swing should treat null according to the beans specification set the system property swing.actions.reconfigureOnNull to the String value true.

This property is useful for cases where you want to do a bunch of changes to an Action without sending individual property changes, and then have all listeners refetch properties when done. I honestly can't say I have ever had a need for this functionality, but it's now there.

The javadoc for Action now details all this information as well as how the various components support the different Action properties.

As usual, if you want to try out these changes (and the rest of the goodness in 1.6) download the source and binaries from mustang snapshot release.

WeakReferences and Actions

Posted by zixle on November 09, 2005 at 01:25 AM | Permalink | Comments (10)

In my last blog I delved into why one might use Actions. In this article I'm going to cover how Swing's components support Actions. Eventually I'll wind up in why you should know about WeakReferences.

Actions can be dynamically changed. This is a key feature of Action. The most common use case is changing the Action's enabled property based on some criteria. For example, the back button in a browser is enabled based on whether or not you can navigate to the previous page. To get this to work requires toggling the enabled property.

Actions communicate changes through the ProperyChangeListener interface. That is, any time you change a property on an Action the Action will notify all its PropertyChangeListeners on the change. When you set an Action on a Swing component the Swing component installs a PropertyChangeListener on the Action. The PropertyChangeLisener allows for the Swing component to update it's state to match that of the Action.

An implementation of this might look like:

public class AbstractButton extends JComponent {
  public void setAction(Action action) {
    action.addPropertyChangeListener(new ActionPropertyChangeListener());
  }
  private class ActionPropertyChangeListener implements PropertyChangeListener {
    public void propertyChange(PropertyChangeEvent e) {
      if ("enabled".equals(e.getPropertyName())) {
        setEnabled(action.isEnabled());
      }
      ...
    }
  }
}
This code looks fine, but it turns out this code is wrong!

One of Java's many claims to fame is garbage collection. With garbage collection developers no longer need to explicitly allocate or deallocate memory, it's taken care of for you. None-the-less developers still need to be cognizant of the lifetime of objects.

If we had implemented support for Actions as highlighted in the previous code the lifetime of the component would be tied to that of the Action. This results from the Action keeping a reference to the PropertyChangeListener, which in turn references the component, which references the Action... In other words we would have a cycle. When this happens the lifetime of each object is the same. As such it's not possible for one to be freed without the other being freed. Here's a picture showing what's going on:

Cycles are only problematic when you want the life cycle of one of the objects in the cycle to be longer than that of the others. For example, the components in a containment hierarchy form a cycle, but this is not problematic because you wouldn't want a button in a visible window to go away suddenly if you didn't explicitly reference it. With Actions this isn't the case, in particular the Action should be able to outlive the lifetime of the component. How do we do this while keeping the cycle?

WeakReferences are a way of indirectly referencing an object. The advantage of referencing an Object via a WeakReference is that the reference isn't considered by the gc. In particular if the only references to an object are via WeakReferences then that object is fair game for the gc to free.

This is exactly what we want for Actions. Rather than having the PropertyChangeListener directly reference the Component it'll indirectly reference the Component through a WeakReference. This way the life time of the component is not tied to that of the Action. For example, if you dispose a window containing a component with an Action the component can now be gc'd.

Here's an image illustrating what is going on now. Notice the extra reference. There is still a cycle, but because of the magic of WeakRefernces the Component can now be gc'd.

And here's the code:

public class AbstractButton extends JComponent {
  public void setAction(Action action) {
    action.addPropertyChangeListener(new ActionPropertyChangeListener(this, action));
  }
  private static class ActionPropertyChangeListener implements PropertyChangeListener {
    private WeakReference<AbstractButton> buttonRef;
    private Action action;

    ActionPropertyChangeListener(AbstractButton button, Action action) {
        buttonRef = new WeakReference<AbstractButton>(button);
        this.action = action;
    }
    public void propertyChange(PropertyChangeEvent e) {
        AbstractButton button = buttonRef.get();
        if (button == null) {
          // The button has gcd, remove listener
          action.removePropertyChangeListener(this);
        } else {
          if ("enabled".equals(e.getPropertyName())) {
            button.setEnabled(action.isEnabled());
          }
        ...
        }
    }
  }
}
There's a couple of interesting things to mention here. Because we're using WeakReferences it's possible for the referenced object, the button in this case, to be gc'd. If this happens, the get method of WeakReferences will return null. If get returns null ActionPropertyChangeListener removes itself from the set of PropertyChangeListeners on the Action. The second key thing is that this inner class must be static. If the class weren't static an implicit reference would be kept to the button so that the lifetime of the button and Action would again be the same, defeating all the work we've done.

For brevitys sake I've isolated this code all in one class and taken a few shortcuts. Internally in Swing we've factored this out into a common class of the same name. If you're curious, download the latest mustang source and poke around.

WeakReferences aren't for everything, but they are necessary in cases like this. In particular if you have a cycle and want some objects to live shorten than others in that cycle, WeakReferences are for you!

The Usefulness of Actions

Posted by zixle on November 01, 2005 at 07:58 AM | Permalink | Comments (10)

I originally intended to blog on the usefullness of WeakReferences for
client apps. This was to be motivated by the internals of Actions and
why Swing internally needs to use a WeakReference to support
Actions. In writing that blog I ended up spending more time describing
Actions that I've decided to split it into two blogs. This blog
motivates the need for Actions as well as the changes to Actions
in 1.6. Later on I'll followup with why we use WeakReferences to
support Actions and why you might use WeakReferences in similar
situations.

An ActionListener encapsulates application specific functionality that
is usually triggered by a user interface gesture (this isn't always
true, javax.swing.Timer also uses ActionListener). For example
clicking the back button in a browser might notify an implementation
of ActionListener this is responsible for navigating to the previous
page. This might look something like:

private class GoBackActionListener implements ActionListener {
  public void actionPerformed(ActionEvent e) {
    browserComponent.show(getLastURL());
  }
}
backButton.addActionListener(new GoBackActionListener());
backButton.setIcon(...);
backButton.setEnabled(false);

If you've written any Swing/AWT application chances are you have code
like this. It's simple, and it works.

Consider what happens if you want to conditionally enable the button
based on whether you can go back a page. In this case you need a
handle to the backButton so that you can invoke setEnabled on
it. If you also have a menu item with the same functionality you'll
need to keep a handle to that menu item as well, or any other user
interface component that is to use the GoBackActionListener
functionality. This is a bit painful. In particular it leads to all
sorts of references back to the hosting components in your application.

The following code shows configuring a menu item and wiring it to a
GoBackActionListener:

backMenuItem.addActionListener(new GoBackActionListener());
backMenuItem.setText(...);
backMenuItem.setMnemonic(...);
backMenuItem.setDisplayedMnemonicIndex(...);
backMenuItem.setAccelerator(...);
backMenuItem.setEnabled(false);

The Action interface is meant to address these problems. Action not
only encapsulates the functionality part of ActionListener, but also a
description of that functionality as appropriate for user interface
components. Included in that description is whether or not the user
interface component should be enabled. When the enabled state of the
Action changes the user interface components listening to the Action
will update appropriately. If the previous examples were replaced
with Action you would change the enabled state of the Action, and as
a consequence the button and menu item would update appropriately. The
code for this could be something like:

private class GoBackAction extends AbstractAction {
  public GoBackAction() {
    putValue(Action.NAME, "...");
    putValue(Action.LARGE_ICON_KEY, ...);
    putValue(Action.SMALL_ICON, ...);
    putValue(Action.MNEMONIC_KEY, ...);
    putValue(Action.DISPLAYED_MNEMONIC_INDEX_KEY, ...);
    putValue(Action.ACCELERATOR_KEY, ...);
    putValue(Action.SHORT_DESCRIPTION, ...);
    setEnabled(false);
  }
  public void actionPerformed(ActionEvent e) {
    browserComponent.show(getLastURL());
  }
}
Action goAction = new GoBackAction();
backButton.setHideActionText(true);
backButton.setAction(goAction);
backMenuItem.setAction(goAction);

You'll notice a number of differences with this example and the
earlier ones. In particular rather than configuring the text, icon and
other properties of the user interface component the Action contains
all this information. When the Action is attached to the user interface
component the user interface component configures its state from the
Action. For buttons this includes the icon, text, mnemonic, mnemonic
index, tooltip text, action command key and enabled state. This is
nice in that the description is encapsulated in a single place and
does not need to be replicated to all the hosting components.

Those that haven't followed the latest changes in 1.6 will notice some
new keys and methods. In particular DISPLAYED_MNEMONIC_INDEX_KEY,
LARGE_ICON_KEY and setHideActionText are all new in 1.6. Action got
one more new key in 1.6, that is SELECTED_KEY. Included in the 1.6
additions is centralizing the description of what properties each
component supports. See the class level javadoc of Action for all the
specifics, as well as bugs 4133141 and 4626632.

When using ActionListener we had to explicitly update the enabled
state of the user interface components, eg:

backButton.setEnabled(...);
backMenuItem.setEnabled(...);

With Action the code becomes:

goBackAction.setEnabled(...);

Any user interface components attached to the Action will update
appropriately whenever the Action changes! Now we just reference the
Action and not all the various components.

The ability to change the Action directly and have it reflected
appropriately in the user interface component comes in very handy in a
number of situations. For example, undo/redo menu items often have
their text reflect what the undo/redo operation will do. For example,
'Undo Paste' or 'Undo Edit'... When using Action you change the NAME
property of the Action and the menu item will change appropriately.

If this is the first time you've read about Actions you should read
the Swing tutorial section on Actions. It has more examples and
details. In a later blog I'll cover using the new SELECTED_KEY. It's
interesting enough that it warrants it's own blog.





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds