在 Java 中进行焦点调试

2022-09-03 13:26:02

问题:

我正在尝试在我的Java Swing应用程序中调试一些与焦点相关的问题。有时,某些组件似乎正在吸引焦点,我无法弄清楚代码中发生这种情况的位置。

我尝试过:

  • 带有 (for ) 的 A。这确实为我提供了有关哪些组件丢失和获得焦点的信息,但它并不能帮助我确定代码中请求焦点的位置。VetoableChangeListenerKeyboardFocusManagerfocusOwner

  • 自定义 .但在这方面,我也只能在它收到事件时进行干预。到那时,调用 的调用堆栈已经丢失。KeyboardFocusManagerrequestFocus

  • 自定义 .但是在那里,我也能够干预再次从EDT调用的方法。调用堆栈再次丢失(有趣的是,没有调用)。EventQueuedispatchEventpostEvent(AWTEvent)

问题:

我正在寻找的是调用时的调用堆栈。是否有可能获得此信息。也许,如果我可以重新定义用于在 中发布事件的方法,那么我就可以打印堆栈转储。但是,不会被调用。requestFocusInWindowEventQueueEventQueue.postEvent(AWTEvent)

任何人都可以提出一个解决方案,帮助我在调用或可能已经调用时获得堆栈的状态?requestFocusrequestFocusInWIndow


答案 1

我遇到了这个优雅的问题解决方案,它没有给你调用堆栈,但确实告诉你哪个类已经抓住了焦点。

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);
}

或者,您可以通过更改全局 JRE logging.properties(或应用程序的自定义 logging.properties 文件)来实现此目的。通过这种方式,您可以跟踪 AWT 焦点事件,而无需源代码或编译器。下面的最后 2 行是必需的补充:

############################################################
#   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

另一个生成有关焦点事件的有用信息的记录器名为java.awt.focus.DefaultKeyboardFocusManager


答案 2

看来他们(太阳)真的不希望你这样做。乍一看,该路径中似乎没有任何虚拟方法可以轻松重写,不是在(仅用于和从应用程序代码中合成事件)也不是在(正如您发现的那样,可重写的方法稍后从调度循环中调用)。EventQueuepostEventinvokeLaterKeyboardFocusManager

幸运的是,如果您使用的是 Sun JRE,则可以在某个地方插入代码,但它并不漂亮:

Component.requestFocus()调用 static,它更新一个名为 的私有 static。KeyboardFocusManager.setMostRecentFocusOwner(Component)MapmostRecentFocusOwners

因此,如果可以使用反射访问该静态,则可以将其替换为跟踪对其方法的调用的转发:MapMapput

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);

这将捕获对堆栈的调用并为您提供堆栈跟踪。requestFocus