Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Focus debugging in Java

Tags:

java

focus

swing

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 with KeyboardFocusManager (for focusOwner). 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 KeyboardFocusManager. But in that too I can intervene only when it receives events. By that time the call stack of the call to requestFocus is already lost.

  • A custom EventQueue. But there too I am able to intervene in the dispatchEvent method which is again called from the EDT. Again the call stack is lost (interestingly the postEvent(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?

like image 718
Parag Avatar asked Feb 08 '10 17:02

Parag


2 Answers

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

like image 193
gb96 Avatar answered Oct 18 '22 00:10

gb96


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.

like image 27
finnw Avatar answered Oct 18 '22 01:10

finnw