对话框导致拖动完成事件不传播
Dialog causes drag done events to not propagate
我在拖放操作期间使用弹出对话框。当拖放发生时,会弹出一个对话框,当它被关闭时,事件链应该继续并允许在拖动操作结束时发生一些事情。如果弹出对话框是 FX,则没有问题,但如果是 Gluon,则不会发生拖动完成操作。
这是一个示例代码:
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Label;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import com.gluonhq.charm.glisten.mvc.View;
public class MainView extends View {
HBox root;
public MainView(String name) {
super(name);
Label source = new Label("Source");
configureDragSource(source);
Label target = new Label("Target");
configureDragTarget(target);
Label popupTarget = new Label("Popup Target");
configureDragPopupTarget(popupTarget);
root = new HBox(40, source, target, popupTarget);
setCenter(root);
}
private void configureDragSource(Label source) {
source.setOnDragDetected(e -> {
root.setBackground(new Background(new BackgroundFill(Color.RED, null, null)));
Dragboard db = source.startDragAndDrop(TransferMode.ANY);
ClipboardContent content = new ClipboardContent();
content.put(DataFormat.PLAIN_TEXT, source.getText());
db.setContent(content);
});
source.setOnDragDone(e -> root.setBackground(new Background(new BackgroundFill(null, null, null))));
}
private void configureDragTarget(Label target) {
target.setOnDragOver(e -> e.acceptTransferModes(TransferMode.ANY));
}
private void configureDragPopupTarget(Label popupTarget) {
popupTarget.setOnDragOver(e -> e.acceptTransferModes(TransferMode.ANY));
popupTarget.setOnDragDropped(e -> {
javafx.scene.control.Alert popup1 = new javafx.scene.control.Alert(AlertType.INFORMATION);
com.gluonhq.charm.glisten.control.Alert popup2 = new com.gluonhq.charm.glisten.control.Alert(AlertType.INFORMATION);
popup1.showAndWait();
});
}
}
应拖动源,背景变为红色。拖动操作完成后,背景应 return 为默认值。常规放置目标什么都不做,颜色变化有效。但是,当拖放到弹出目标上时,对话框会出现,当它关闭时,颜色仅针对 FX 对话框发生变化,而不会针对胶子对话框发生变化。将 popup1.showAndWait();
更改为 popup2
。
如果重要,这就是应用程序 class
import com.gluonhq.charm.glisten.application.MobileApplication;
public class TestApplication extends MobileApplication {
@Override
public void init() {
addViewFactory(HOME_VIEW, () -> new MainView(HOME_VIEW));
}
public static void main(String[] args) {
launch(args);
}
}
这是 gradle 构建文件:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.javafxports:jfxmobile-plugin:1.3.5'
}
}
apply plugin: 'org.javafxports.jfxmobile'
apply plugin: 'eclipse'
jar {
manifest {
attributes 'Main-Class': 'com.test.TestApplication'
}
}
jfxmobile {
downConfig {
version = '3.3.0'
plugins 'display', 'lifecycle', 'statusbar', 'storage'
}
android {
compileSdkVersion = 19
// manifest = 'src/android/AndroidManifest.xml'
}
ios {
infoPList = file('src/ios/Default-Info.plist')
forceLinkClasses = [
'com.gluonhq.**.*',
'javax.annotations.**.*',
'javax.inject.**.*',
'javax.json.**.*',
'org.glassfish.json.**.*'
]
}
}
eclipse {
classpath {
downloadJavadoc = true
downloadSources = true
}
}
repositories {
jcenter()
maven {
url 'http://nexus.gluonhq.com/nexus/content/repositories/releases'
}
}
mainClassName = 'com.test.TestApplication'
dependencies {
compile 'com.gluonhq:charm:4.3.5'
}
task wrapper(type: Wrapper) {
gradleVersion = '4.2'
}
也发生在 compile 'com.gluonhq:charm:4.3.7'
和 4.4.0.
运行 Java 8 b141。为什么会这样?这是一个错误吗?
JavaFX 内置对话框和 Gluon 对话框不一样。事实上,后者从 Layer
扩展而来,同时也是模态和阻塞的,就像前者一样。
运行 Mac 发布的代码,我在 popupTarget
后得到一个 NPE,可以使用以下方法轻松解决:
Platform.runLater(() -> popup2.showAndWait());
另外,考虑到对话框消耗了拖动事件,可能的解决方案可能是:
Platform.runLater(() ->
popup2.showAndWait()
.ifPresent(r ->
root.setBackground(new Background(new BackgroundFill(null, null, null)))));
因此您可以重构 setOnDragDone
方法,并创建一个可以在对话框关闭后调用的方法。
编辑
这些是 dnd 事件开始后目标和源都收到的拖动事件:
目标通过拖放手势接收这些事件:
DRAG_OVER
DRAG_OVER
... // until drop is done:
// show Dialog
...
// hide Dialog
DRAG_DROPPED
DRAG_EXITED
DRAG_EXITED_TARGET
它们与 JavaFX 和 Gluon 对话框的工作完全相同(至少在 Windows 上。在 Mac 上,由于 NPE,使用 Platform.runLater()
显然会延迟显示对话框和隐藏对话框事件,但我们现在只关注 Windows)。
在最后一个事件之后,源收到:
DRAG_DONE
但仅限于 JavaFX 对话框。
经过一番调试,Gluon的Dialog中止drag done事件的原因可以解释如下:
Scene
class 有一个 DnDGesture
私人 class:
/**
* A Drag and Drop gesture has a lifespan that lasts from mouse
* PRESSED event to mouse RELEASED event.
*/
class DnDGesture {
...
}
正如评论中所解释的那样,它具有从鼠标按下事件到鼠标释放事件的生命周期。
使用 JavaFX 内置对话框,它显示在一个新的舞台中,因此也是一个新的场景。这里的关键是这个对话框(以及所有鼠标事件集)显示在一个新的模态阶段,所以一旦对话框被隐藏,主阶段再次获得焦点并恢复,正确完成 dnd 手势,因为鼠标是已发布。
但是使用Gluon的对话,没有第二阶段。一切都发生在同一个阶段,一旦鼠标被释放,Scene.DnDGesture
变为空,因此当目标中发生 DRAG_EXITED_TARGET
事件时,将完成正确的调用以完成 dnd 过程,但在此点 dndGesture
为空,该调用不再到达源。
我不认为这是一个错误,而更像是一种权衡,因为有很多原因可以避免在移动环境中出现第二个 stage/scene 并保持 View/Layer(在单级)设计。
我在拖放操作期间使用弹出对话框。当拖放发生时,会弹出一个对话框,当它被关闭时,事件链应该继续并允许在拖动操作结束时发生一些事情。如果弹出对话框是 FX,则没有问题,但如果是 Gluon,则不会发生拖动完成操作。
这是一个示例代码:
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Label;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import com.gluonhq.charm.glisten.mvc.View;
public class MainView extends View {
HBox root;
public MainView(String name) {
super(name);
Label source = new Label("Source");
configureDragSource(source);
Label target = new Label("Target");
configureDragTarget(target);
Label popupTarget = new Label("Popup Target");
configureDragPopupTarget(popupTarget);
root = new HBox(40, source, target, popupTarget);
setCenter(root);
}
private void configureDragSource(Label source) {
source.setOnDragDetected(e -> {
root.setBackground(new Background(new BackgroundFill(Color.RED, null, null)));
Dragboard db = source.startDragAndDrop(TransferMode.ANY);
ClipboardContent content = new ClipboardContent();
content.put(DataFormat.PLAIN_TEXT, source.getText());
db.setContent(content);
});
source.setOnDragDone(e -> root.setBackground(new Background(new BackgroundFill(null, null, null))));
}
private void configureDragTarget(Label target) {
target.setOnDragOver(e -> e.acceptTransferModes(TransferMode.ANY));
}
private void configureDragPopupTarget(Label popupTarget) {
popupTarget.setOnDragOver(e -> e.acceptTransferModes(TransferMode.ANY));
popupTarget.setOnDragDropped(e -> {
javafx.scene.control.Alert popup1 = new javafx.scene.control.Alert(AlertType.INFORMATION);
com.gluonhq.charm.glisten.control.Alert popup2 = new com.gluonhq.charm.glisten.control.Alert(AlertType.INFORMATION);
popup1.showAndWait();
});
}
}
应拖动源,背景变为红色。拖动操作完成后,背景应 return 为默认值。常规放置目标什么都不做,颜色变化有效。但是,当拖放到弹出目标上时,对话框会出现,当它关闭时,颜色仅针对 FX 对话框发生变化,而不会针对胶子对话框发生变化。将 popup1.showAndWait();
更改为 popup2
。
如果重要,这就是应用程序 class
import com.gluonhq.charm.glisten.application.MobileApplication;
public class TestApplication extends MobileApplication {
@Override
public void init() {
addViewFactory(HOME_VIEW, () -> new MainView(HOME_VIEW));
}
public static void main(String[] args) {
launch(args);
}
}
这是 gradle 构建文件:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.javafxports:jfxmobile-plugin:1.3.5'
}
}
apply plugin: 'org.javafxports.jfxmobile'
apply plugin: 'eclipse'
jar {
manifest {
attributes 'Main-Class': 'com.test.TestApplication'
}
}
jfxmobile {
downConfig {
version = '3.3.0'
plugins 'display', 'lifecycle', 'statusbar', 'storage'
}
android {
compileSdkVersion = 19
// manifest = 'src/android/AndroidManifest.xml'
}
ios {
infoPList = file('src/ios/Default-Info.plist')
forceLinkClasses = [
'com.gluonhq.**.*',
'javax.annotations.**.*',
'javax.inject.**.*',
'javax.json.**.*',
'org.glassfish.json.**.*'
]
}
}
eclipse {
classpath {
downloadJavadoc = true
downloadSources = true
}
}
repositories {
jcenter()
maven {
url 'http://nexus.gluonhq.com/nexus/content/repositories/releases'
}
}
mainClassName = 'com.test.TestApplication'
dependencies {
compile 'com.gluonhq:charm:4.3.5'
}
task wrapper(type: Wrapper) {
gradleVersion = '4.2'
}
也发生在 compile 'com.gluonhq:charm:4.3.7'
和 4.4.0.
运行 Java 8 b141。为什么会这样?这是一个错误吗?
JavaFX 内置对话框和 Gluon 对话框不一样。事实上,后者从 Layer
扩展而来,同时也是模态和阻塞的,就像前者一样。
运行 Mac 发布的代码,我在 popupTarget
后得到一个 NPE,可以使用以下方法轻松解决:
Platform.runLater(() -> popup2.showAndWait());
另外,考虑到对话框消耗了拖动事件,可能的解决方案可能是:
Platform.runLater(() ->
popup2.showAndWait()
.ifPresent(r ->
root.setBackground(new Background(new BackgroundFill(null, null, null)))));
因此您可以重构 setOnDragDone
方法,并创建一个可以在对话框关闭后调用的方法。
编辑
这些是 dnd 事件开始后目标和源都收到的拖动事件:
目标通过拖放手势接收这些事件:
DRAG_OVER
DRAG_OVER
... // until drop is done:
// show Dialog
...
// hide Dialog
DRAG_DROPPED
DRAG_EXITED
DRAG_EXITED_TARGET
它们与 JavaFX 和 Gluon 对话框的工作完全相同(至少在 Windows 上。在 Mac 上,由于 NPE,使用 Platform.runLater()
显然会延迟显示对话框和隐藏对话框事件,但我们现在只关注 Windows)。
在最后一个事件之后,源收到:
DRAG_DONE
但仅限于 JavaFX 对话框。
经过一番调试,Gluon的Dialog中止drag done事件的原因可以解释如下:
Scene
class 有一个 DnDGesture
私人 class:
/**
* A Drag and Drop gesture has a lifespan that lasts from mouse
* PRESSED event to mouse RELEASED event.
*/
class DnDGesture {
...
}
正如评论中所解释的那样,它具有从鼠标按下事件到鼠标释放事件的生命周期。
使用 JavaFX 内置对话框,它显示在一个新的舞台中,因此也是一个新的场景。这里的关键是这个对话框(以及所有鼠标事件集)显示在一个新的模态阶段,所以一旦对话框被隐藏,主阶段再次获得焦点并恢复,正确完成 dnd 手势,因为鼠标是已发布。
但是使用Gluon的对话,没有第二阶段。一切都发生在同一个阶段,一旦鼠标被释放,Scene.DnDGesture
变为空,因此当目标中发生 DRAG_EXITED_TARGET
事件时,将完成正确的调用以完成 dnd 过程,但在此点 dndGesture
为空,该调用不再到达源。
我不认为这是一个错误,而更像是一种权衡,因为有很多原因可以避免在移动环境中出现第二个 stage/scene 并保持 View/Layer(在单级)设计。