Newsletter sign-up
View all newsletters

Sign up for our technology specific newsletters.

Enterprise Java
Email Address:

Java Tip 89: Manipulate EventQueues for semimodal dialogs

A technique to give users more freedom when modal dialogs are too restrictive

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
When designing graphical user interfaces (GUIs), you might come across a few situations that require the use of semimodal dialogs. This Java Tip outlines how to create them. With the construction of just one additional class, you can use this technique to customize your own GUIs.

What are semimodal dialogs, and when do you need them?

While building a new GUI at Plexus Systems Design, the programming team on which I worked discovered the need for semimodal dialogs. The GUI we were building masked an existing green-screen application that our customers used. The new GUI needed to look just like the old text-based UI, so customers could use it immediately. Unfortunately, this meant synchronizing the new GUI with the old application, which led to some interesting situations. Consider the following:

In our green-screen application, users were prompted via a small overlay screen to enter additional data, as shown in the figure below. Typically, this happened when they were performing a search or sending customer information.

A situation that calls for a semimodal dialog

The user could either enter the requested data or press special function keys to navigate back to a particular menu. In the new GUI, portrayed in the figure above, all the navigation keys were replaced with a toolbar on the user's main screen. When the dialog box popped up, we still wanted the user to have access to the toolbar, which would be blocked if the overlay dialog was modal.

At the same time, we didn't want the rest of the main GUI to be accessible while the overlay window was up. A modal dialog box blocked access to all of the underlying main UI. A modeless dialog permitted access to all of the underlying main UI. How could we duplicate the behavior of the old application?

We could have presented the overlay screen as a modal dialog box, forcing the user to enter the requested data before proceeding. But remember, our navigational toolbar was on the window underneath the overlay. The user wouldn't have had the option of canceling and backing up to a previous menu.

So then what? Well, if modal dialogs were too restricting, then we had to use nonmodal dialogs. So assume the overlay was nonmodal and the user could select anything from the underlying frame. But the information requested in the overlay was required before we could receive any other function requests or input from the user. Obviously, modeless dialogs were not restricting enough.

The answer? Semimodal dialogs were exactly what we needed. We wanted the ability to present an overlay window and to let the user access only certain components on the underlying frame. Here's how we did it.

Possible (but dead-end) solutions

At first, the glassPane attribute seemed like the perfect solution. The glassPane is an attribute of all JFrame objects. When the glassPane is set to be visible, it intercepts all MouseEvents occurring within that JFrame. The programmer becomes responsible for catching and executing the events. By default, events occurring over the glassPane are ignored.

Here's how the glassPane could be useful: Suppose we set the overlay dialog to be modeless. Then the user has full access to the underlying main UI. So, we determined which components on the main UI should be blocked and then set their glassPane components to be visible. Then, any MouseEvents sent to those blocked components would be ignored. Wonderful! But what could we do about the keyboard? We could block all the mouse clicks we wanted, but we still needed to prevent blocked components from becoming focused via tab presses.

Of course, we could have come up with some sort of solution for dealing with keyboard events, but the project was already starting to become quite complicated. Why implement one solution for dealing with mouse events and another one for dealing with key events? Wouldn't it be easier to come up with one solution that worked for both, and that could easily be extended to handle other event types?

OK, here was our next idea: How about enabling and disabling the components using the setEnabled method of all Component and JComponent objects? That would have been a great solution if it hadn't affected the appearance of the specified components; but, as it happened, this technique would have grayed them out. It would look strange for the appearance of the parent frame to change as soon as an overlay was presented.

So, what did we do?

Semimodal dialogs via EventQueue manipulation

To solve our problem, we developed a Blocker class. The Blocker class extends the Java EventQueue class, and replaces the default SystemQueue.

In essence, it catches all events and checks whether or not they are instances of MouseEvents or KeyEvents. If the event is not an instance of a mouse click or key event, then we allow the event to pass through and be handled by the dispatchEvent method of the Blocker's superclass (EventQueue).

We wanted a dispatchEvent method, which overrides the same method in the EventQueue, within our Blocker class. This method performs the event-type checking and deals with all incoming events accordingly. The following code segment illustrates how to implement the Blocker's dispatchEvent method, along with the private isSourceBlocked helper method. The isSourceBlocked method performs a preliminary check to see whether or not the source of an event is blocked, before that particular event is examined any further.

private boolean isSourceBlocked( Component source ) {
  boolean blocked = false;
  if( ( restrictedComponents != null ) && ( source != null ) ) {
    int i = 0;
    while( i < restrictedComponents.length && 
           ( restrictedComponents[i].equals( source ) == false ) )
      i++;
    
    blocked = i < restrictedComponents.length; 
  }
  return blocked; 
}
protected void dispatchEvent( AWTEvent event ) {
  boolean blocked = false;
 
  if( inBlockedState ) {
    // getSource is a private helper method
    blocked = isSourceBlocked( getSource( event ) );
  }
  if( blocked && ( event.getID() == MouseEvent.MOUSE_CLICKED 
                   || event.getID() == MouseEvent.MOUSE_PRESSED ) ) {
    Toolkit.getDefaultToolkit().beep(); 
  }
 
  else if( blocked && event instanceof KeyEvent 
             && event.getSource() instanceof Component ) {
    DefaultFocusManager dfm = new DefaultFocusManager();
    dfm.getCurrentManager();
    Component currentFocusOwner = getSource( event );
    
    boolean focusNotFound = true;
    do {
      dfm.focusNextComponent( currentFocusOwner );
      currentFocusOwner = SwingUtilities.findFocusOwner(
          ( Component )event.getSource() );
      if( currentFocusOwner instanceof JComponent ) {
      focusNotFound = 
          ( ( ( JComponent )currentFocusOwner ).isRequestFocusEnabled() == false );
      }
    } while( focusNotFound );
  }
  else {
    super.dispatchEvent( event );
  }
}


How did we know which components to block and which to keep available? Within the Blocker class, we maintained an array of components, access to which is blocked when the Blocker object is in a blocked state. The components stored in this array can be specified, cleared, and respecified by the user at any point in time by calling the Blocker's setRestrictedComponents method (which I will discuss momentarily).

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Comment
Login
Forgot your account info?
Add comment
Anonymous comments subject to approval. Register here for member benefits.
Have a JavaWorld account? Log in here. Register now for a free account.
Resources