【问题标题】:How to listen to visible changes to the JavaFX SceneGraph for specific node如何监听特定节点的 JavaFX SceneGraph 的可见更改
【发布时间】:2015-01-04 00:45:11
【问题描述】:

我们在 JavaFX 中创建了一个小型绘画应用程序。出现了一个新要求,我们必须警告用户他进行了更改,这些更改尚未持久化并询问他,用户是否愿意在关闭之前先保存。

示例快照:

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

特别是因为我只是想知道是否有任何变化,并不需要知道具体的变化。

此外,我也不想获取每个事件,例如简单的选择,这会导致所选节点周围显示边框(如图所示),这并不一定意味着用户拥有在离开前保存他的申请。

有人有想法吗?还是我真的需要为节点中的每一个更改触发事件?

【问题讨论】:

  • “我真的需要触发事件”触发事件的不是(也不是你的代码):-) 监听相关状态的失效
  • 这确实是另一种可能的实现方式,无需创建特定的视图模型,但对于这样的小功能来说似乎仍然需要付出很多努力。
  • 如果您要支持撤消/重做,无论如何您都需要跟踪确切的更改。

标签: javafx listener javafx-8 scenegraph


【解决方案1】:

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

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

【讨论】:

  • 这也是我们考虑的问题,但目前我们只是用一些优化的序列化器在 xml 中序列化画布,因此不存在底层模型,因为它从来没有真正必要。有可能,它需要有一天,但对于像“你不想保存你的地图吗?”这样的状态。看起来开销太大了。
  • @ymene 你可以使用 xml 作为你的底层模型吗?缓存原始加载的 xml 文件,当用户试图关闭窗口时,序列化当前画布并比较它们以确定是否应该通知用户。
  • 这是一个有趣的想法。我要试一试。我们考虑了一些类似的东西,但由于我们首先想以禁用保存按钮的形式显示“脏状态”,因此我们搜索了不同的策略。但是在不显示“脏状态”的情况下,这是一个非常简单的解决方案,目前可能令人满意。非常感谢!
  • @ymene hmm .. 即使监听到某个非特定的“脏”状态,该按钮几乎总是处于启用状态,不是吗?如果没有某种控制器来控制什么是相关的变化(比如改变形状的位置。fi)和什么不是(比如fi选择一个项目)脏总是真的。有了这样一个控制器,你就接近了一个视图模型:-)
  • @kleopatra 你是绝对正确的!这个解决方案确实是一种妥协,没有向用户显示画布当前处于脏状态,但至少我可以确保,由于 Benjamin 建议的检查,用户在关闭应用程序时不会丢失数据。为了获得快速的结果,我会这样做。脏状态可能是未来的增强,目前这并不重要。我只是认为这会是一些东西,我可能会毫不费力地免费获得。不幸的是,时间目前很重要,因此按钮始终保持启用状态。
【解决方案2】:

Benjamin 的答案可能是这里最好的答案:您应该使用底层模型,该模型可以轻松检查相关状态是否已更改。在应用程序开发的某个阶段,您会意识到这是做事的正确方法。看来您已经达到了这一点。

但是,如果您想进一步推迟不可避免地重新设计应用程序(并在您确实到了那个点时让它变得更加痛苦;)),您可以考虑另一种方法。

显然,您有某种Pane 持有正在绘制的对象。用户必须正在创建这些对象,并且您在某个时候将它们添加到窗格中。只需创建一个处理该添加的方法,并在您这样做时使用感兴趣的属性注册一个失效侦听器。结构将如下所示:

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

现在你可以做类似的事情

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

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

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

【讨论】:

  • 首先,我同意你在这里提到的每一点。感谢您分享有关如何实施它的想法。我真的知道这是怎么回事,我喜欢我在这里看到的。一旦有足够的时间,我将使用您的想法来包括功能性。非常感谢您的努力,谢谢!
猜你喜欢
  • 2015-06-02
  • 2013-07-05
  • 2016-03-26
  • 2019-01-30
  • 1970-01-01
  • 1970-01-01
  • 2014-01-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多