如何侦听特定节点的JavaFX场景图的可见更改

如何侦听特定节点的JavaFX场景图的可见更改,javafx,listener,javafx-8,scenegraph,Javafx,Listener,Javafx 8,Scenegraph,我们在JavaFX中创建了一个小的绘画应用程序。出现了一个新的需求,我们必须警告用户,他所做的更改尚未持久化,并询问他是否希望在关闭之前先保存 快照示例: 不幸的是,有很多不同的节点,节点可以通过多种方式更改,例如多边形点可以移动。可以拖动节点本身。它们可以旋转,甚至更多。因此,在为画布上节点对象的每一个可能更改触发无数事件之前,我想问一下,是否有人知道如何简化这种方法。我很好奇,如果有监听器,我可以在JavaFX的场景图中听到画布对象的任何变化 特别是因为我只是想知道是否有什么变化,而不是真

我们在JavaFX中创建了一个小的绘画应用程序。出现了一个新的需求,我们必须警告用户,他所做的更改尚未持久化,并询问他是否希望在关闭之前先保存

快照示例:

不幸的是,有很多不同的节点,节点可以通过多种方式更改,例如多边形点可以移动。可以拖动节点本身。它们可以旋转,甚至更多。因此,在为画布上节点对象的每一个可能更改触发无数事件之前,我想问一下,是否有人知道如何简化这种方法。我很好奇,如果有监听器,我可以在JavaFX的场景图中听到画布对象的任何变化

特别是因为我只是想知道是否有什么变化,而不是真的需要知道具体的变化

此外,我也不希望获得每个事件,比如简单的选择,它会导致在所选节点周围显示边框(如图所示),这并不意味着用户必须在离开前保存其应用程序


有人有主意吗?或者我真的需要为节点中的每一个更改触发事件吗?

我认为您处理这个问题的方式是错误的。屏幕上显示的节点应该只是底层模型的可视表示。您真正需要知道的是,底层模型已经改变


例如,如果您正在编写文本编辑器,屏幕上显示的文本将由某种模型支持。假设模型是一个
字符串
。您无需检查屏幕上显示的任何文本节点是否已更改,只需将原始字符串数据与当前字符串数据进行比较,以确定是否需要提示用户保存。

Benjamin的答案可能是这里的最佳答案:您应该使用基础模型,该模型可以很容易地检查相关状态是否发生了变化。在应用程序开发的某个阶段,您会意识到这是正确的方法。看来你已经到了那个地步

但是,如果你想延迟你的应用程序的不可避免的重新设计(当你到达那个点时会使它更痛苦一些),这是另一个你可以考虑的方法。 显然,您有某种类型的
窗格
,用于保存正在绘制的对象。用户必须正在创建这些对象,而您正在某个时候将它们添加到窗格中。只需创建一个处理该添加的方法,并在执行此操作时使用感兴趣的属性注册一个无效侦听器。结构将如下所示:

private final ReadOnlyBooleanWrapper unsavedChanges = 
    new ReadOnlyBooleanWrapper(this, "unsavedChanged", false);

private final ChangeListener<Object> unsavedChangeListener = 
    (obs, oldValue, newValue) -> unsavedChanges.set(true);

private Pane drawingPane ;

// ...

Button saveButton = new Button("Save");
saveButton.disableProperty().bind(unsavedChanges.not());

// ...
@SafeVarArgs
private final <T extends Node> void addNodeToDrawingPane(
        T node, Function<T, ObservableValue<?>>... properties) {

    Stream.of(properties).forEach(
        property -> property.apply(node).addListener(unsavedChangeListener));
    drawingPane.getChildren().add(node);
}

也就是说,添加新节点时,只需指定要观察的属性。您可以创建一个remove方法来删除侦听器。在您已经拥有的基础上额外的代码量非常少,因为(可能,我还没有看到您的代码)是重构


同样,你真的应该有一个单独的视图模型,等等。我想发布这篇文章,以表明@kleopatra对这个问题的第一个评论(“倾听相关状态的无效性”),如果你以正确的方式处理它,就不一定涉及很多工作。起初,我认为这种方法与@Tomas Mikula提到的撤销/重做功能不兼容,但您甚至可以使用这种方法作为基础

“我真的需要触发事件”触发事件的不是你(也不是你的代码):-)监听相关状态的无效性这确实是实现它的另一种可能的方法,而不需要创建特定的视图模型,但对于这样的一个小功能来说,似乎仍然需要付出很多努力。如果你打算支持撤消/重做,无论如何,您都需要跟踪确切的更改。我们也考虑过这一点,但目前我们只是使用一些优化的序列化程序在xml中序列化画布,因此根本不存在底层模型,因为它从来没有真正必要。也许有一天这是必要的,但对于“难道你不想保存你的地图吗?”这样的状态来说,这似乎有太多的开销。@ymene你能使用xml作为基础模型吗?缓存原始加载的xml文件,当用户试图关闭窗口时,序列化当前画布并比较它们以确定是否应该通知用户。这是一个有趣的想法。我要试试看。我们考虑了一些类似的东西,但由于我们首先希望以禁用保存按钮的形式显示“脏状态”,因此我们搜索了不同的策略。但在不显示“脏状态”的情况下,这是一个非常简单的解决方案,目前可能令人满意。多谢各位@嗯。。即使有一个监听器处于某种非特定的“脏”状态,按钮也几乎总是处于启用状态,不是吗?在没有某种控制器的情况下,什么是相关的更改(如更改形状的位置)和什么不是(如f.i.选择项目)脏总是正确的。有了这样一个控制器,你就接近了一个视图模型:-)@kleopatra你说得绝对正确!这个解决方案确实是一个折衷方案,没有向用户显示画布当前处于脏状态,但至少我可以确保用户在关闭应用程序时不会因为检查而丢失数据。为了尽快得到结果,我会走那条路。肮脏的状态可能是对未来的一种增强,目前这并不重要。我只是觉得这会是一件很有意义的事情,我可能不费吹灰之力就能免费获得。不幸的是,现在时间很重要,所以这个按钮一直保持启用状态。首先,我同意你在这里提到的每一点。感谢分享您对如何实施它的想法。我真的明白这是g
    Rectangle rect = new Rectangle();

    addNodeToDrawingPane(rect, 
            Rectangle::xProperty, Rectangle::yProperty, 
            Rectangle::widthProperty, Rectangle::heightProperty);
    Text text = new Text();
    addNodeToDrawingPane(text, 
            Text::xProperty, Text::yProperty, Text::textProperty);