Debugging Java focus problems using the focus log

Posted on Wed 28 September 2011 in Coding

A while ago I set out to fix a couple of focus problems in a GUI at work. One of the problems was that after a certain user operation, all keyboard shortcuts stopped working (as you may know, keyboard events are dispatched up the focus chain until a component handles them). Another problem was that after the user clicked in a component, another component would start flickering. Actually, the problems are not that important, but the way to solving them are!

Code that is triggered by a focus change is especially painful to debug, at least in Windows, because as soon as the debugger kicks in, the main window focus is transferred from the GUI of the application you’re trying to debug to the debugger window. Short of littering the code with System.out.println or attaching with a remote debugger, there’s not much you can do.

Lucky for us,┬ámost (all?) focus events and actions in Java are logged to a focus log. In this post, I’ll show how to enable focus logging in your application.

Typically, we have a main class for running our program. Our approach is to create a separate main class that sets up the focus log and then calls the real main class:

public class StartWithFocusLogging {
    public static void main(String[] args) {
        enableFocusLogging();
        RealMainClass.main(args);
    }
    private static void enableFocusLogging() {
        // ...
    }
}

Now, let’s dig into the enableFocusLogging method. The name of the logger is java.awt.focus.Component, and the first thing we need to do is get the logger instance:

private static void enableFocusLogging() {
    // Obtain a reference to the logger
    Logger focusLog = Logger.getLogger("java.awt.focus.Component");

By default, the focus logger does not have any log level set, and will therefore inherit a log level from an ancestor (ultimately the root logger if no logger in between has had its level set). All focus logging will take place on one of the finer levels (FINE, FINER and FINEST), but since we’re debugging we’re interested in everything:

    // The logger should log all messages
    focusLog.setLevel(Level.ALL);

A logger sends all non-discarded log messages to one or more handlers. Without a handler, we won’t see anything. Therefore, the next thing to do is to create a handler. A handler must extend the java.util.logging.Handler class. Fortunately, there are existing handlers that we can use, and to keep things simple, let’s use the ConsoleHandler class:

    // Create a new handler
    ConsoleHandler handler = new ConsoleHandler();

Our new handler writes log messages to System.err, but not out of the box. While the handler will configure itself using the global log manager, it defaults to INFO level. (The benefit of having a handler level is that we can let different log outputs differ in verbosity.) Again, this won’t do in our case, so let’s up the level:

    // The handler must handle all messages
    handler.setLevel(Level.ALL);

Finally, the handle must be connected with the logger to receive the log messages it let’s through:

    // Add the handler to the logger
    focusLog.addHandler(handler);
}

And that’s it! When you run your application through our new main class, you will get a log of all focus events on the error console. Of course, you can easily use a different handler to redirect messages to another destination, a file for example.

The full source code in all its glory, including imports, is:

import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
public class StartWithFocusLogging {
    public static void main(String[] args) {
        enableFocusLogging();
        RealMainClass.main(args);
    }
    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);
    }
}