Page 2 of 3
If the event that is caught is a mouse click or keypress, then the Blocker checks the source component of the event. When a mouse click comes from a blocked component, the code sounds a system beep
and disregards the actual event. When a key event comes from a blocked component, the code transfers focus to the next focusable
component. If there are no other focusable components, the focus returns to the source component.
Therefore, you need to set the focusEnabled flag for each blocked component to false. You also check all blocked components for instances of AbstractButtons. These should have their setFocusPainted flags set to false as well. This prevents focus transfers from being visible. When you are trying to find the next focusable component, the
focus may land on a blocked component, from which it is immediately transferred. If a component's focus paint is visible,
you will see the split second when this blocked component has focus before relinquishing it to the next component in line.
Thus, when we turn off the focus paint, it is simply for aesthetic purposes. Once the focus finds a component that isn't blocked,
the traversal process stops.
Again, you know that this loop must come to an end since it can only be invoked from a focused component. Therefore, you know that at least one component -- the component that invoked it -- is focusable. Even if this is the only one that is available, the loop will find it after jumping over all other blocked components, and then the loop will terminate.
The Blocker is my team's version of an EventQueue, with the added ability to intercept events, determine their source, and dispatch or discard them accordingly.
The Blocker created in this Java Tip catches mouse clicks and keyboard events. To block other types of events, simply register the event type in the extended
dispatchEvent method of Blocker. This will also require that we specify a way to find the source of this new type of event in the getSource method.
Finding the source of events can be tricky. For mouse events, using event.getSource() may not return the individual button or checkbox from where the event derived. Instead, it will likely return the parent
container to which the individual component belongs. To get around this problem, use:
event.getSource().getDeepestComponentAt(event.getX(), event.getY() )
For key events, use the following to find the event source:
SwingUtilities.findFocusOwner( ( Component )( event.getSource() ) )
But to avoid ClassCastExceptions, make sure that event.getSource() does return a Component object first.
Putting this all together, the getSource method looks like this:
private Component getSource( AWTEvent event ) {
Component source = null;
// each of these five MouseEvents will still be valid (regardless
// of their source), so we still want to process them.
if( ( event instanceof MouseEvent )
&& ( event.getID() != MouseEvent.MOUSE_DRAGGED )
&& ( event.getID() != MouseEvent.MOUSE_ENTERED )
&& ( event.getID() != MouseEvent.MOUSE_EXITED )
&& ( event.getID() != MouseEvent.MOUSE_MOVED )
&& ( event.getID() != MouseEvent.MOUSE_RELEASED ) ) {
MouseEvent mouseEvent = ( MouseEvent )event;
source = SwingUtilities.getDeepestComponentAt(
mouseEvent.getComponent(),
mouseEvent.getX(),
mouseEvent.getY() );
}
else if( event instanceof KeyEvent
&& event.getSource() instanceof Component ) {
source = SwingUtilities.findFocusOwner(
( Component )( event.getSource() ) );
}
return source;
}
Obviously, we only want components to remain blocked while the overlay dialog is still active on the screen. Therefore, once
the overlay is closed, the previously blocked components should be unblocked and made readily accessible again. In order to
provide this functionality, we maintained an array of components that can be blocked and unblocked, called restrictedComponents.
When you are going into a blocked state, each of the elements in the restrictedComponents array must have its setFocusEnabled and setFocusPainted flags set to false. To move into an unblocked state, simply reset these two flags to true.
You must also keep track of which state you are currently in (i.e., whether you are blocked or unblocked). This is important,
because if you are already in a blocked state, you do not want to execute all of the blocking steps again. Similarly, you
do not want to repeat all of the unblocking steps if you are already in an unblocked state. But most important, you need to
know what state you are in so you can decide whether to treat your restricted components as blocked components or accessible
components. Hence, you need to maintain an inBlockedState flag.
The following method is used to place the Blocker in a blocked or unblocked state, depending on the boolean value passed in as the parameter. The first time you go into a
blocked state, you push your Blocker object onto the SystemQueue. From this point on, the SystemQueue will be a singleton instance of the Blocker class. Since you extended the EventQueue class with your Blocker, the SystemQueue will maintain all of its original functionality. But now, with the adjustments introduced in the Blocker class, you will be able to tell the SystemQueue to ignore certain events.
public void setBlockingEnabled( boolean block ) {
// this methods must be called from the AWT thread to avoid
// toggling between states while events are being processed
if( block && !inBlockedState
&& restrictedComponents != null ) {
adjustFocusCapabilities( true );
// "adjustFocusCapabilities" is a private helper function that
// sets the focusEnabled & focusPainted flags for the
// appropriate components. Its boolean parameter signifies
// whether we are going into a blocked or unblocked state
// (true = blocked, false = unblocked)
if( !alreadyBlockedOnce ) {
// here is where we replace the SystemQueue
sysQ.push( this );
alreadyBlockedOnce = true;
}
inBlockedState = true;
}
else if( !block && inBlockedState ) {
adjustFocusCapabilities( false );
inBlockedState = false;
}
}
It is important to ensure that only one instance of the Blocker class is created. Multiple instances meddle dangerously with the SystemQueue, and also create confusion as to which components (under Blocker) are currently being blocked. The technique for creating singleton classes has been worked into a design pattern (see the
Resources section below for more information).
Therefore, when a Blocker is instantiated, you maintain a reference to this Blocker so that it can be returned whenever a user attempts to create additional Blockers. This way, the user may create numerous Blocker references, which must all point to the same Blocker object.
You can achieve this functionality by making the Blocker class's constructor method private, and implementing the following method for creating a new instance:
public static synchronized Blocker Instance() {
if( instance == null ) {
// instance is a static global variable of type Blocker
instance = new Blocker();
}
return instance;
}
For our GUI, we needed a mechanism for telling the Blocker object components to which access should be restricted when we were in a blocked state. And since we had limited the Blocker class to being a singleton, we also needed a mechanism for changing the components that will be restricted. Basically, for
different frames, we needed to block different components.