Application vs ApplicationTest:不同的事件调度?
Application vs ApplicationTest: different event dispatch?
目前我正在研究 TextField 和 default/cancel 按钮的问题。在使用 TestFX 测试 presumed fix 时,我 运行 发现事件分派 (?) 的差异导致测试失败,而应用程序似乎正在运行。
下面是一个非常简化的版本:
- 只是一个简单的ui,由一个框内的文本字段组成,用于Application/Test
- textField 有一个(键-)处理程序,当 a 被按下时触发 actionEvent
- textField 有一个使用 actionEvent 的动作处理程序
- (key-) 处理程序检查操作是否被消费(此处:简单日志,在实际上下文中,如果操作被消费,则 (key-) 事件必须被消费)
修复的关键部分是创建 actionEvent 并将 textField 作为源和目标(以防止在调度期间将事件复制到新实例):
ActionEvent action = new ActionEvent(field, field);
当 运行 启动应用程序时,这似乎足以使其正常运行。当 运行ning 测试失败时 - 事件被复制到另一个实例,这样使用的事件与在调度中传递的事件不同(只能在调试期间看到)。无法确定发生的确切点 where/why。
问题:这种差异是预期的吗?如果是 why/where?或者我做错了什么(概率远非零)?
重现
- 运行 应用程序,按 a:注意日志中指出触发的 actionEvent 已被消耗
- 运行 测试,注意日志说明触发的 actionEvent 没有被消耗
代码:
public class ActionApp extends Application {
// create a simple ui - static because must be same for ActionTest
public static Parent createContent() {
TextField field = new TextField();
// some handler to fire an actionEvent
field.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.A) {
ActionEvent action = new ActionEvent(field, field);
field.fireEvent(action);
LOG.info("action/consumed? " + action + action.isConsumed());
}
});
// another handler to consume the fired action
field.addEventHandler(ActionEvent.ACTION, e -> {
e.consume();
LOG.info("action received " + e + e.isConsumed());
});
VBox actionUI = new VBox(field);
return actionUI;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(ActionApp.class.getName());
}
测试:
public class ActionTest extends ApplicationTest {
/**
* Does not really test anything, just to see the output.
*/
@Test
public void testConsumeA() {
// sanity: focused to receive the key
verifyThat(".text-field", NodeMatchers.isFocused());
press(KeyCode.A);
}
@Override
public void start(Stage stage) {
Parent root = ActionApp.createContent();
Scene scene = new Scene(root, 100, 100);
stage.setScene(scene);
stage.show();
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(ActionTest.class.getName());
}
我的环境是 2018 年 10 月在 win10 上的 fx11 和 TestFX。仅供参考:打开了 issue in testFX
不同之处在于,TestFx 为 EventType.ROOT
在存储所有触发事件的舞台上注入了一个 eventFilter。黑客是删除该过滤器,变脏如:
public static void stopStoringFiredEvents() {
FxToolkitContext context = FxToolkit.toolkitContext();
// reflectively access the private field of the context
FiredEvents fired =(FiredEvents) FXUtils.invokeGetFieldValue(FxToolkitContext.class, context, "firedEvents");
// stop recording
fired.stopStoringFiredEvents();
}
/**
* Updated hack, now reaaally dirty: need to manually clear the handler map :(
*/
public static void stopStoringFiredEvents(Stage stage) {
// remove the event-logging filter
stopStoringFiredEvents();
// really cleanup:
// removing the filter only nulls the eventHandler in CompositeEventHandler
// but does not remove the Composite from EventHandlerManager.handlerMap
// as a result, handlerManager.dispatchCapturingEvent runs into the fixForSource
// block which copies the event even though there is no filter
WindowEventDispatcher windowDispatcher = (WindowEventDispatcher) stage.getEventDispatcher();
EventHandlerManager manager = windowDispatcher.getEventHandlerManager();
Map<?, ?> handlerMap = (Map<?, ?>) FXUtils.invokeGetFieldValue(EventHandlerManager.class, manager, "eventHandlerMap");
handlerMap.clear();
}
虽然绕过了这个特定的上下文,但它在一般情况下并不能可靠地提供帮助:只要在父层次结构中的任何位置存在 eventFilter(与触发事件具有相同或超 eventType),就会发生同样的情况。根本原因似乎是事件分派在为 eventFilters 分派事件时创建了新的事件实例(通过 event.copyFor)。因此,在动作处理程序中消耗的事件实例与火灾发出的实例不同。
更新:
- 注意到简单地删除 firedEvents 过滤器没有帮助(不知道为什么它看起来像几天前那样.. ;):一旦有了过滤器,事件就会永远发送到它的包含处理程序,即使稍后清空。
- filed an issue
目前我正在研究 TextField 和 default/cancel 按钮的问题。在使用 TestFX 测试 presumed fix 时,我 运行 发现事件分派 (?) 的差异导致测试失败,而应用程序似乎正在运行。
下面是一个非常简化的版本:
- 只是一个简单的ui,由一个框内的文本字段组成,用于Application/Test
- textField 有一个(键-)处理程序,当 a 被按下时触发 actionEvent
- textField 有一个使用 actionEvent 的动作处理程序
- (key-) 处理程序检查操作是否被消费(此处:简单日志,在实际上下文中,如果操作被消费,则 (key-) 事件必须被消费)
修复的关键部分是创建 actionEvent 并将 textField 作为源和目标(以防止在调度期间将事件复制到新实例):
ActionEvent action = new ActionEvent(field, field);
当 运行 启动应用程序时,这似乎足以使其正常运行。当 运行ning 测试失败时 - 事件被复制到另一个实例,这样使用的事件与在调度中传递的事件不同(只能在调试期间看到)。无法确定发生的确切点 where/why。
问题:这种差异是预期的吗?如果是 why/where?或者我做错了什么(概率远非零)?
重现
- 运行 应用程序,按 a:注意日志中指出触发的 actionEvent 已被消耗
- 运行 测试,注意日志说明触发的 actionEvent 没有被消耗
代码:
public class ActionApp extends Application {
// create a simple ui - static because must be same for ActionTest
public static Parent createContent() {
TextField field = new TextField();
// some handler to fire an actionEvent
field.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.A) {
ActionEvent action = new ActionEvent(field, field);
field.fireEvent(action);
LOG.info("action/consumed? " + action + action.isConsumed());
}
});
// another handler to consume the fired action
field.addEventHandler(ActionEvent.ACTION, e -> {
e.consume();
LOG.info("action received " + e + e.isConsumed());
});
VBox actionUI = new VBox(field);
return actionUI;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(ActionApp.class.getName());
}
测试:
public class ActionTest extends ApplicationTest {
/**
* Does not really test anything, just to see the output.
*/
@Test
public void testConsumeA() {
// sanity: focused to receive the key
verifyThat(".text-field", NodeMatchers.isFocused());
press(KeyCode.A);
}
@Override
public void start(Stage stage) {
Parent root = ActionApp.createContent();
Scene scene = new Scene(root, 100, 100);
stage.setScene(scene);
stage.show();
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(ActionTest.class.getName());
}
我的环境是 2018 年 10 月在 win10 上的 fx11 和 TestFX。仅供参考:打开了 issue in testFX
不同之处在于,TestFx 为 EventType.ROOT
在存储所有触发事件的舞台上注入了一个 eventFilter。黑客是删除该过滤器,变脏如:
public static void stopStoringFiredEvents() {
FxToolkitContext context = FxToolkit.toolkitContext();
// reflectively access the private field of the context
FiredEvents fired =(FiredEvents) FXUtils.invokeGetFieldValue(FxToolkitContext.class, context, "firedEvents");
// stop recording
fired.stopStoringFiredEvents();
}
/**
* Updated hack, now reaaally dirty: need to manually clear the handler map :(
*/
public static void stopStoringFiredEvents(Stage stage) {
// remove the event-logging filter
stopStoringFiredEvents();
// really cleanup:
// removing the filter only nulls the eventHandler in CompositeEventHandler
// but does not remove the Composite from EventHandlerManager.handlerMap
// as a result, handlerManager.dispatchCapturingEvent runs into the fixForSource
// block which copies the event even though there is no filter
WindowEventDispatcher windowDispatcher = (WindowEventDispatcher) stage.getEventDispatcher();
EventHandlerManager manager = windowDispatcher.getEventHandlerManager();
Map<?, ?> handlerMap = (Map<?, ?>) FXUtils.invokeGetFieldValue(EventHandlerManager.class, manager, "eventHandlerMap");
handlerMap.clear();
}
虽然绕过了这个特定的上下文,但它在一般情况下并不能可靠地提供帮助:只要在父层次结构中的任何位置存在 eventFilter(与触发事件具有相同或超 eventType),就会发生同样的情况。根本原因似乎是事件分派在为 eventFilters 分派事件时创建了新的事件实例(通过 event.copyFor)。因此,在动作处理程序中消耗的事件实例与火灾发出的实例不同。
更新:
- 注意到简单地删除 firedEvents 过滤器没有帮助(不知道为什么它看起来像几天前那样.. ;):一旦有了过滤器,事件就会永远发送到它的包含处理程序,即使稍后清空。
- filed an issue