Focus debugging in Java
The problem:
I am trying to debug some focus-related problems in my Java Swing application. There are times when some components seem to be grabbing focus and I cannot figure out where in code this is happening.
What I have tried:
A
VetoableChangeListener
withKeyboardFocusManager
(forfocusOwner
). This does give me information about which components lose and gain focus, but it does not help me pin point where in code the focus is being requested.A custom
Keyboard开发者_如何学运维FocusManager
. But in that too I can intervene only when it receives events. By that time the call stack of the call torequestFocus
is already lost.A custom
EventQueue
. But there too I am able to intervene in thedispatchEvent
method which is again called from the EDT. Again the call stack is lost (interestingly thepostEvent(AWTEvent)
is not called).
Question:
What I am looking for is the call stack when the call to requestFocusInWindow
is made. Is it possible to get this information. Perhaps, if I could redefine the method used to post the event in EventQueue
, then I can print the stack dump. However the EventQueue.postEvent(AWTEvent)
does not get invoked.
Can anyone suggest a solution which will help me get the state of the stack when the call to requestFocus
or requestFocusInWIndow
may have been made?
I came across this elegant solution to your Problem that does not give you the call stack but does tell you which class has grabbed focus.
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
// ...
private static void enableFocusLogging() {
// Obtain a reference to the logger
Logger focusLog = Logger.getLogger("java.awt.focus.Component");
// The logger should log all messages
focusLog.setLevel(Level.ALL);
// Create a new handler
ConsoleHandler handler = new ConsoleHandler();
// The handler must handle all messages
handler.setLevel(Level.ALL);
// Add the handler to the logger
focusLog.addHandler(handler);
}
Alternatively you can acheive this via a change to the global JRE logging.properties (or a custom logging.properties file for your application). In this way you can track AWT focus events without needing source code or compiler. Last 2 lines below are the required additions:
############################################################
# Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.
# For example java -Djava.util.logging.config.file=myfile
############################################################
handlers= java.util.logging.ConsoleHandler
# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers. For any given facility this global level
# can be overriden by a facility specific level
.level= INFO
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
# Log AWT Focus Events
java.util.logging.ConsoleHandler.level = FINEST
java.awt.focus.Component.level = FINEST
Another logger that produces useful information regarding focus events is named java.awt.focus.DefaultKeyboardFocusManager
It seems they (Sun) really don't want you to do that. At first glance there don't appear to be any virtual methods in that path that can easily be overridden, not in EventQueue
(postEvent
is used only for invokeLater
and synthesising events from application code) nor in KeyboardFocusManager
(as you've discovered, the overridable methods are called later from the dispatch loop.)
Fortunately, if you are using the Sun JRE, there is a place you can insert code, but it's not pretty:
Component.requestFocus()
calls the static KeyboardFocusManager.setMostRecentFocusOwner(Component)
, which updates a private static Map
called mostRecentFocusOwners
.
So if you can access that static Map
using reflection, you can replace it with a forwarding Map
that traces calls to its put
method:
import com.google.common.collect.ForwardingMap;
// ...
Field mrfoField = KeyboardFocusManager.class.getDeclaredField("mostRecentFocusOwners");
mrfoField.setAccessible(true);
final Map delegate = (Map) mrfoField.get(null);
Map mrfo = new ForwardingMap() {
public Object put(Object key, Object value) {
new Throwable().printStackTrace();
return super.put(key, value);
}
protected Map delegate() {
return delegate;
}
};
mrfoField.set(null, mrfo);
And this will catch calls to requestFocus
and give you the stack traces.
精彩评论