JavaTip 69: Press Escape to close your Java dialog windows
Discover how to program keystroke responses in your Dialogs' parent classes
Summary
Microsoft Windows users are accustomed to pressing the Escape key to close dialog windows. Likewise, the Escape key closes the active dialog in most commercial products, no matter which control of the dialog has the focus. This Java Tip offers an easy way to intercept the Escape-key event in all dialogs of your Java applet or application. Note: This article was updated on March 4 to improve the code's performance. (1,100 words)
Plus: Check out our index
of previously published tips.
By Eugene Podgorbunskikh
Java Tips
|
|
For a comprehensive list of Java Tips published in
JavaWorld, see the
Java Tips Index
Do you have a tip that would benefit JavaWorld's
readers? We would like to pass it on! Submit your tip to
javatips@javaworld.com.
|
hese days, computer users expect every application to follow the same function metaphors and have little patience with those apps that don't. Windows users, for example, expect their dialog windows to close whenever they press the Escape key. With that expectation in mind, we'll examine an easy method for intercepting the Escape-key event in Java applets and applications.
In Java, it's difficult to intercept key events in the dialog box because the
key event goes directly to the component possessing the focus. To work properly,
you have to add the Dialog object as a KeyListener to all its
components and containers (and the children and grandchildren of those
containers). If you have a lot of complex Dialog s with deep component
hierarchies, this becomes quite a tedious task. Maintenance of
the code is also a nightmare, because when you add a new component, or a
container full of components, to an existing Dialog you mustn't forget
to add the Dialog as a KeyListener to each new component.
However, there's an easy way to program a response to key events in the Dialog 's superclass, so you don't have to remember the Escape key handling when you develop your product further. All you have to do is create class EscapeDialog that extends the Dialog and automatically adds itself as a KeyListener to all
its descendant components. Thus, EscapeDialog will receive notification of all key events that happen in any of its components. So notified, EscapeDialog closes itself when the Escape key is pressed. If you derive all your Dialog s from the EscapeDialog class, hitting the Escape key will close them for you.
The hard part in all this is to add EscapeDialog as a
KeyListener to each of its components, because it doesn't
know which components belong to its subclasses. Besides,
components can be added and removed on the fly after the Dialog
creation. The trick is to make EscapeDialog a
ContainerListener so that it's notified when a new
component is added to it or to any of its descendant containers.
Whenever this happens, we'll add the EscapeDialog object
as a KeyListener to the newly added component and all its
descendants.
The componentAdded and componentRemoved functions
As a ContainerListener , EscapeDialog must
define functions componentAdded and componentRemoved , which will be called whenever a component is added to or removed from a container belonging to the EscapeDialog . However, these functions cannot merely add or remove the EscapeDialog as a listener to the
newly added component -- the component could actually be a container full of other components. The EscapeDialog needs to listen to them all. That's why we need to call a recursive function that adds EscapeDialog to the newly added component and all its descendants, if any. Here's the definition of the functions:
// This function is called whenever a Component or a Container is added to
// another Container belonging to this EscapeDialog
public void componentAdded(ContainerEvent e)
{
addKeyAndContainerListenerRecursively(e.getChild());
}
// This function is called whenever a Component or a Container is removed
// from another Container belonging to this EscapeDialog
public void componentRemoved(ContainerEvent e)
{
removeKeyAndContainerListenerRecursively(e.getChild());
}
The two recursive functions called from componentAdded and
componentRemoved receive the newly added or removed component
as an argument. They do all the work of adding or removing
EscapeDialog as a listener to the component and all its
descendants. The two functions look exactly the same with the exception
that they handle the opposite actions. If you understand one, you understand
both.
Here's the code for the componentAdded function:
private void addKeyAndContainerListenerRecursively(Component c)
{
//Add KeyListener to the Component passed as an argument
c.addKeyListener(this);
//Check if the Component is a Container
if(c instanceof Container) {
//Component c is a Container. The following cast is safe.
Container cont = (Container)c;
//Add ContainerListener to the Container.
cont.addContainerListener(this);
//Get the Container's array of children Components.
Component[] children = cont.getComponents();
//For every child repeat the above operation.
for(int i = 0; i < children.length; i++){
addKeyAndContainerListenerRecursively(children[i]);
}
}
}
Additional actions for container components
To accomplish our task, we need to add the EscapeDialog as a
KeyListener to the component passed as an argument (this
is the newly added component if we're in the first recursion of the
function). Then we check whether or not the component is a container. If it isn't, we're
done, because the component doesn't contain any child components.
If the component turns out to be a container, the container requires two additional actions:
- Add
EscapeDialog as a ContainerListener
to the container, so the EscapeDialog receives
notification if other components are added to the container in the future
- Call this function recursively for every child of the container, so
all the components of the container receive
EscapeDialog
as a KeyListener
The keyPressed function
Because the EscapeDialog object is a KeyListener of
all its descendent components, the function keyPressed will be
called whenever a key is pressed and the focus belongs to the Dialog or
one of its components:
public void keyPressed(KeyEvent e)
{
int code = e.getKeyCode();
if(code == KeyEvent.VK_ESCAPE){
//Key pressed is the Escape key. Hide this Dialog.
setVisible(false);
}
else if(code == KeyEvent.VK_ENTER){
//Key pressed is the Enter key. Redefine performEnterAction() in subclasses
to respond to pressing the Enter key.
performEnterAction(e);
}
//Insert code to process other keys here
}
If the Escape key is pressed, we hide the dialog by calling function
setVisible(false) . In addition, you can program a response to any
key pressed. For example, on pressing the Enter key, function
performEnterAction is called. As it stands now, this function
doesn't do anything, but you can redefine it in a subclass to do something
useful.
In the constructor of the EscapeDialog we need to add this
EscapeDialog to itself as a KeyListener and a
ContainerListener :
public EscapeDialog(Frame frame, String title, boolean modal)
{
super(frame, title, modal);
addKeyAndContainerListenerRecursively(this);
}
The source code for this article can be accessed at EscapeDialog.java.txt.
Conclusion
If you derive all your dialog boxes from EscapeDialog , the
Escape key will automatically close them. Now you can concentrate
on creating specific layouts and functionality for your dialogs without
worrying about the basic functionality that today's users expect.
|
|
About the author
eugene.podgorbunskikh
Eugene Podgorbunskikh is a senior developer at Logica Advantage kbs (http://www.akbs.com), where he works
with Java and C++ to build customer-support applications.
|
|