/**
* ComponentDependencyHandler.java
* copyright 2001, 2006 by Melinda Green
* Superliminal Software
*
* This class is meant to help avoid the confusion about the different types
* of listeners the different Swing and AWT components support. Additional
* listener interfaces can be easily added. It also helps solve the perennial
* problem of components being enabled or disabled, visible or invisible etc.
* at the wrong times.
*
* ComponentDependencyHandler objects add themselves as listeners to a given
* set of components. Then when any of those components generate any of the
* usual events, the ComponentDependencyHandler object calls its
* dependencyNotification method. Users only need to implement that abstract
* method and update whatever state of its dependent components may need to
* change, without having to worry about which type of event occurred.
*
* Applications will typically create anonymous subclasses of this class and
* implement the abstract dependencyNotification method to set whatever state
* may need to change in response to state changes in their dependent components.
* For example the following code shows how to create a dependency between two
* buttons such that the user can only select the "Next Page" button once they've
* indicated that they've read some license agreement by clicking an "I Accept"
* button. In other words, the "enabled" property of one component is made to be
* dependent upon the "selected" property of another component:
*
* final JToggleButton accept_button = new JToggleButton("I Accept");
* final JButton next_page = new JButton("Next Page");
* next_page.setEnabled(false);
* // create ComponentDependencyHandler that enforces that the next_page button
* // is only enabled when the accept_button is selected:
* new ComponentDependencyHandler(accept_button) {
* public void dependencyNotification() {
* next_page.setEnabled(accept_button.isSelected());
* }
* }
*
* The dependencyNotification method takes no arguments. It's therefore assumed that
* your callback code has access to the components it's dependent upon and can
* inquire the states of those dependencies in order to make appropriate state
* changes to the target component or components as shown above. Note that your
* callback method will likely be called more often than needed since it will be
* triggered by any number of state changes in its dependent objects, but since these
* events will likely be due to user actions that should almost never be a problem.
* Note also that the new ComponentDependencyHandler object wasn't even saved in a
* local variable. It will simply exist on the heap only referenced by the components
* it's listening to. It exists only to keep some component states in synch according
* to your business logic.
*
* Using this pattern, dependency graphs of arbitrary complexity can be generated
* which are easily maintained. The test code of the main method here shows a non-
* trivial example using this dataflow pattern.
*/
package com.superliminal.uiutil;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
import java.beans.*;
public abstract class ComponentDependencyHandler implements
ActionListener,
ChangeListener,
ItemListener,
ListSelectionListener,
PropertyChangeListener,
FocusListener,
KeyListener
{
// The control list of events we want to respond to.
// Just add or remove listener classes and recompile.
private static Class listener_classes[] = {
ActionListener.class,
ItemListener.class,
ChangeListener.class,
ListSelectionListener.class,
PropertyChangeListener.class,
FocusListener.class,
KeyListener.class,
};
/**
* Constructs a ComponentDependencyHandler with a set of dependent components.
*/
public ComponentDependencyHandler(Component... dependents) {
for(Component comp : dependents)
for(Class cls : listener_classes)
tryToAddListener(comp, cls);
}
/**
* User subclasses only need to implement this method to receive
* notification of changes to dependent components.
*/
public abstract void dependencyNotification();
private void tryToAddListener(Component comp, Class listener_class) {
String listener_name = listener_class.getName();
int last_dot = listener_name.lastIndexOf('.');
listener_name = listener_name.substring(last_dot+1);
try {
Method adder = comp.getClass().getMethod("add" + listener_name, listener_class);
adder.invoke(comp, this);
}
catch(NoSuchMethodException nsme){} // getMethod
catch(IllegalAccessException iae){} // invoke
catch(InvocationTargetException ite){} // invoke
}
// all the listener implementations that all forward notification to subclasses
public void stateChanged(ChangeEvent e) { dependencyNotification();}
public void itemStateChanged(ItemEvent e) { dependencyNotification(); }
public void valueChanged(ListSelectionEvent e) { dependencyNotification(); }
public void actionPerformed(ActionEvent e) { dependencyNotification(); }
public void propertyChange(PropertyChangeEvent e) { dependencyNotification(); }
public void focusLost(FocusEvent e) { dependencyNotification(); }
public void focusGained(FocusEvent e) { dependencyNotification(); }
public void keyTyped(KeyEvent e) { dependencyNotification(); }
public void keyPressed(KeyEvent e) { dependencyNotification(); }
public void keyReleased(KeyEvent e) { dependencyNotification(); }
/**
* A simple example program.
*/
public static void main(String args[]) {
// build the components
final JLabel
use_password_label = new JLabel("Use Password"),
enter_label = new JLabel("Enter Password:"),
instruction_label = new JLabel("Hit <space> to toggle checkbox");
final JCheckBox password_checkbox = new JCheckBox();
final JTextField password_text = new JTextField("");
final JButton submit_button = new JButton("Submit");
// set initial component states
enter_label.setVisible(false);
instruction_label.setVisible(false);
// Now set up the dependencies.
// This is the meat of the example. Notice that the ComponentDependencyHandler
// objects only need to be created in order to work. Each one implements
// the business logic associated with changes to the states of the
// component or components given to it's constructor.
// "enter" text visibility depends upon checkbox selection
new ComponentDependencyHandler(password_checkbox) {
public void dependencyNotification() {
enter_label.setVisible(password_checkbox.isSelected());
}
};
// text entry field enabled depends upon checkbox selection
new ComponentDependencyHandler(password_checkbox) {
public void dependencyNotification() {
password_text.setEnabled(password_checkbox.isSelected());
}
};
// instruction text visibility depends upon checkbox FOCUS
new ComponentDependencyHandler(password_checkbox) {
public void dependencyNotification() {
instruction_label.setVisible(password_checkbox.hasFocus());
}
};
// "select" button enabling depends upon checkbox selected AND password text
new ComponentDependencyHandler(password_checkbox, password_text) {
public void dependencyNotification() {
submit_button.setEnabled(
password_checkbox.isSelected()
&& password_text.getText().length() > 0
);
}
};
// build and display the dialog
JPanel first_row = new JPanel();
first_row.setLayout(new BoxLayout(first_row, BoxLayout.X_AXIS));
first_row.add(use_password_label);
first_row.add(password_checkbox);
first_row.add(enter_label);
first_row.add(password_text);
JFrame password_dialog = new JFrame("ComponentDependencyHandler Test");
Container cont = password_dialog.getContentPane();
cont.setLayout(new BorderLayout());
cont.add("North", first_row);
cont.add("Center", instruction_label);
cont.add("South", submit_button);
password_dialog.setSize(300, 100);
password_dialog.setVisible(true);
password_dialog.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}