|
|
||
Scott Violet's BlogNovember 2005 ArchivesChanges to Actions in 1.6Posted 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_KEYThe 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_KEYAll 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_KEYThis 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.setHideActionTextThis 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.reconfigureOnNullThe beans specification indicates that anull 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 ActionsPosted 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 ActionsPosted by zixle on November 01, 2005 at 07:58 AM | Permalink | Comments (10)I originally intended to blog on the usefullness of WeakReferences for An ActionListener encapsulates application specific functionality that
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 Consider what happens if you want to conditionally enable the button The following code shows configuring a menu item and wiring it to a 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
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 Those that haven't followed the latest changes in 1.6 will notice some When using ActionListener we had to explicitly update the enabled backButton.setEnabled(...); backMenuItem.setEnabled(...); With Action the code becomes: goBackAction.setEnabled(...); Any user interface components attached to the Action will update The ability to change the Action directly and have it reflected If this is the first time you've read about Actions you should read | ||
|
|