首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在JavaFX中重绘场景图的问题

在JavaFX中重绘场景图的问题
EN

Stack Overflow用户
提问于 2022-01-31 00:20:53
回答 1查看 225关注 0票数 3

我遇到了一个大场景图快速变化的问题。

在响应事件时,当我清除场景图(getChildren().clear)时,有时会得到以下异常:

代码语言:javascript
运行
复制
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: class javafx.scene.Scene cannot be cast to class javafx.scene.Node (javafx.scene.Scene and javafx.scene.Node are in module javafx.graphics of loader 'app')
    at javafx.graphics/javafx.scene.Scene$MouseHandler.handleNodeRemoval(Scene.java:3709)
    at javafx.graphics/javafx.scene.Scene.generateMouseExited(Scene.java:3581)
    at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:593)
    at javafx.base/com.sun.javafx.collections.VetoableListDecorator.clear(VetoableListDecorator.java:294)
    at bit.fxzoomer/bit.fxzoomer.SectorPane12.populateMap(SectorPane12.java:72)
    at bit.fxzoomer/bit.fxzoomer.SectorPane12.lambda$addListeners$1(SectorPane12.java:247)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(ObjectPropertyBase.java:106)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:113)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:147)
    at javafx.base/javafx.beans.property.ObjectProperty.setValue(ObjectProperty.java:72)
    at bit.fxzoomer/bit.fxzoomer.SectorPane12.setViewport(SectorPane12.java:62)
    at bit.fxzoomer/bit.fxzoomer.App.lambda$start$0(App.java:31)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(ObjectPropertyBase.java:106)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:113)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:147)
    at javafx.base/javafx.beans.property.ObjectProperty.setValue(ObjectProperty.java:72)
    at bit.fxzoomer/bit.fxzoomer.PanZoomPane.setViewport(PanZoomPane.java:99)
    at bit.fxzoomer/bit.fxzoomer.PanZoomPane.lambda$new$3(PanZoomPane.java:82)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
    at javafx.base/javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:170)
    at javafx.base/com.sun.javafx.binding.BindingHelperObserver.invalidated(BindingHelperObserver.java:52)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:136)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
    at javafx.graphics/javafx.scene.Node$LazyBoundsProperty.invalidate(Node.java:9785)
    at javafx.graphics/javafx.scene.Node$MiscProperties.invalidateBoundsInLocal(Node.java:6876)
    at javafx.graphics/javafx.scene.Node.invalidateBoundsInLocal(Node.java:3469)
    at javafx.graphics/javafx.scene.Node.localBoundsChanged(Node.java:4041)
    at javafx.graphics/javafx.scene.Node.doGeomChanged(Node.java:4028)
    at javafx.graphics/javafx.scene.Node$1.doGeomChanged(Node.java:461)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.geomChangedImpl(NodeHelper.java:184)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.geomChanged(NodeHelper.java:137)
    at javafx.graphics/javafx.scene.Parent.childBoundsChanged(Parent.java:1872)
    at javafx.graphics/javafx.scene.Node.notifyParentOfBoundsChange(Node.java:4099)
    at javafx.graphics/javafx.scene.Node.transformedBoundsChanged(Node.java:4060)
    at javafx.graphics/javafx.scene.Node.doTransformsChanged(Node.java:5003)
    at javafx.graphics/javafx.scene.Node$1.doTransformsChanged(Node.java:444)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.transformsChangedImpl(NodeHelper.java:170)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.transformsChanged(NodeHelper.java:119)
    at javafx.graphics/javafx.scene.transform.Transform.transformChanged(Transform.java:2109)
    at javafx.graphics/javafx.scene.transform.Affine$AffineAtomicChange.end(Affine.java:5778)
    at javafx.graphics/javafx.scene.transform.Affine.appendTranslation(Affine.java:2038)
    at javafx.graphics/javafx.scene.transform.Translate.appendTo(Translate.java:539)
    at javafx.graphics/javafx.scene.transform.Affine.append(Affine.java:1502)
    at bit.fxzoomer/bit.fxzoomer.PanZoomPane.translate(PanZoomPane.java:123)
    at bit.fxzoomer/bit.fxzoomer.PanZoomPane.lambda$new$1(PanZoomPane.java:58)
    at javafx.base/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
    at javafx.graphics/javafx.scene.Scene$MouseHandler.process(Scene.java:3862)
    at javafx.graphics/javafx.scene.Scene.processMouseEvent(Scene.java:1849)
    at javafx.graphics/javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2590)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:409)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:299)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:447)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:412)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:446)
    at javafx.graphics/com.sun.glass.ui.View.handleMouseEvent(View.java:556)
    at javafx.graphics/com.sun.glass.ui.View.notifyMouse(View.java:942)
    at javafx.graphics/com.sun.glass.ui.mac.MacView.notifyMouse(MacView.java:127)

首先是这个例外,然后我得到了一系列这样的例子:

代码语言:javascript
运行
复制
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException: Cannot invoke "javafx.scene.Scene.isDepthBuffer()" because the return value of "javafx.scene.Node.getScene()" is null
    at javafx.graphics/com.sun.javafx.scene.input.PickResultChooser.processOffer(PickResultChooser.java:185)
    at javafx.graphics/com.sun.javafx.scene.input.PickResultChooser.offer(PickResultChooser.java:143)
    at javafx.graphics/javafx.scene.Node.doComputeIntersects(Node.java:5263)
    at javafx.graphics/javafx.scene.Node$1.doComputeIntersects(Node.java:456)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.computeIntersectsImpl(NodeHelper.java:180)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.computeIntersects(NodeHelper.java:133)
    at javafx.graphics/javafx.scene.Node.intersects(Node.java:5234)
    at javafx.graphics/javafx.scene.Node.doPickNodeLocal(Node.java:5171)
    at javafx.graphics/javafx.scene.Node$1.doPickNodeLocal(Node.java:450)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.pickNodeLocalImpl(NodeHelper.java:175)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.pickNodeLocal(NodeHelper.java:128)
    at javafx.graphics/javafx.scene.Node.pickNode(Node.java:5203)
    at javafx.graphics/javafx.scene.Parent.pickChildrenNode(Parent.java:805)
    at javafx.graphics/javafx.scene.Parent$1.pickChildrenNode(Parent.java:136)
    at javafx.graphics/com.sun.javafx.scene.ParentHelper.pickChildrenNode(ParentHelper.java:113)
    at javafx.graphics/javafx.scene.layout.Region.doPickNodeLocal(Region.java:3160)
    at javafx.graphics/javafx.scene.layout.Region$1.doPickNodeLocal(Region.java:184)
    at javafx.graphics/com.sun.javafx.scene.layout.RegionHelper.pickNodeLocalImpl(RegionHelper.java:104)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.pickNodeLocal(NodeHelper.java:128)
    at javafx.graphics/javafx.scene.Node.pickNode(Node.java:5203)
    at javafx.graphics/javafx.scene.Parent.pickChildrenNode(Parent.java:805)
    at javafx.graphics/javafx.scene.Parent$1.pickChildrenNode(Parent.java:136)
    at javafx.graphics/com.sun.javafx.scene.ParentHelper.pickChildrenNode(ParentHelper.java:113)
    at javafx.graphics/javafx.scene.layout.Region.doPickNodeLocal(Region.java:3160)
    at javafx.graphics/javafx.scene.layout.Region$1.doPickNodeLocal(Region.java:184)
    at javafx.graphics/com.sun.javafx.scene.layout.RegionHelper.pickNodeLocalImpl(RegionHelper.java:104)
    at javafx.graphics/com.sun.javafx.scene.NodeHelper.pickNodeLocal(NodeHelper.java:128)
    at javafx.graphics/javafx.scene.Node.pickNode(Node.java:5203)
    at javafx.graphics/javafx.scene.Scene$MouseHandler.pickNode(Scene.java:4005)
    at javafx.graphics/javafx.scene.Scene.pick(Scene.java:2029)
    at javafx.graphics/javafx.scene.Scene$MouseHandler.process(Scene.java:3815)
    at javafx.graphics/javafx.scene.Scene.processMouseEvent(Scene.java:1849)
    at javafx.graphics/javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2590)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:409)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:299)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:447)
    at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:412)
    at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:446)
    at javafx.graphics/com.sun.glass.ui.View.handleMouseEvent(View.java:556)
    at javafx.graphics/com.sun.glass.ui.View.notifyMouse(View.java:942)
    at javafx.graphics/com.sun.glass.ui.mac.MacView.notifyMouse(MacView.java:127)

具体来说,我正在浏览场景图(请参见这里的答案,获取已翻译和缩放节点的视图 )。每次视口发生变化,我都会重做场景图。

场景图相当大,有20-30,000个节点。

当事情很忙的时候,当我快速地拖曳场景,点击很多,不经常的时候,我会得到那些异常。

这是单线程的,所以我不认为我看到了同步问题,但也许我正在与重新显示线程做斗争。很明显,有些事情发生在错误的时间。

有时,它会从这些错误中恢复过来(它会产生大量的噪音而不产生任何后果),而其他时候,它似乎会破坏现场,再也不能工作了。

我尝试通过一个在拖动时不断刷新的计时器来延迟更新,只有在拖放停止后(通过Platform.runLater)才会触发,但即使这样也会触发此异常。我把所有这些都放在主线程上,以排除同步问题。

是很零星的。我不知道是什么在打架。

是什么导致了这一切,我怎样才能阻止它?

编辑:这是一个自我包含的失败的例子。它甚至没有一个大的场景图。只要运行程序,抓取左下角,前后调整大小,就会失败。

用JDK 11和JFX 17.0.2进行测试。

我尝试了一个大的场景图(25000个元素),简单地绑定到一个调整大小的事件上,但却没能让它失败。这很容易失败。

代码语言:javascript
运行
复制
package bit.fxtest2;

import javafx.application.Application;
import javafx.beans.binding.Binding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform;
import javafx.stage.Stage;

public class TransformTest9 extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {

        BigGridPane gridPane = new BigGridPane();
        PanZoomPane pzPane = new PanZoomPane(gridPane);
        pzPane.getViewportProperty().addListener((ov, t, t1) -> {
            gridPane.setViewPort(t1);
        });

        Scene scene = new Scene(pzPane, 250, 250);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

    class BigGridPane extends Region {

        ObjectProperty<Bounds> viewPortProperty = new SimpleObjectProperty<>();

        public BigGridPane() {
            viewPortProperty.addListener((ov, t, t1) -> {
                populate();
            });
        }

        public void setViewPort(Bounds viewPort) {
            viewPortProperty.setValue(viewPort);
        }

        public void populate() {
            ObservableList<Node> children = getChildren();
            children.clear();
            for (int i = 0; i < 20; i++) {
                for (int j = 0; j < 20; j++) {
                    Rectangle r = new Rectangle(i * 20, j * 20, 20, 20);
                    r.setFill(Color.WHITE);
                    r.setStroke(Color.BLACK);
                    children.add(r);
                }
            }
            System.out.println(children.size());
        }
    }

    class PanZoomPane extends Region {

        private final Node content;

        private final Rectangle clip;

        private Affine transform;

        private Point2D mouseDown;

        private static final double SCALE = 1.01; // zoom factor per pixel scrolled

        Binding<Bounds> viewportBinding;
        ObjectProperty<Bounds> viewportProperty = new SimpleObjectProperty<>();

        public PanZoomPane(Node content) {
            Background background = new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY));
            setBackground(background);
            this.content = content;
            getChildren().add(content);
            clip = new Rectangle();
            setClip(clip);
            transform = Affine.affine(1, 0, 0, 1, 0, 0);
            content.getTransforms().setAll(transform);

            content.setOnMousePressed(event -> mouseDown = new Point2D(event.getX(), event.getY()));
            content.setOnMouseDragged(event -> {
                double deltaX = event.getX() - mouseDown.getX();
                double deltaY = event.getY() - mouseDown.getY();
                translate(deltaX, deltaY);
            });
            content.setOnScroll(event -> {
                double pivotX = event.getX();
                double pivotY = event.getY();
                double scale = Math.pow(SCALE, event.getDeltaY());
                scale(pivotX, pivotY, scale);
            });

            viewportBinding = new ObjectBinding<>() {
                {
                    bind(
                            localToSceneTransformProperty(),
                            boundsInLocalProperty(),
                            content.localToSceneTransformProperty()
                    );
                }

                @Override
                protected Bounds computeValue() {
                    return content.sceneToLocal(localToScene(getBoundsInLocal()));
                }
            };

            viewportBinding.addListener((obs, oldViewport, newViewport) -> setViewport(newViewport));

        }

        public ObjectProperty<Bounds> getViewportProperty() {
            return viewportProperty;
        }

        public void setViewportProperty(ObjectProperty<Bounds> viewportProperty) {
            this.viewportProperty = viewportProperty;
        }

        public Bounds getViewport() {
            return viewportProperty.getValue();
        }

        public void setViewport(Bounds bounds) {
            viewportProperty.setValue(bounds);
        }

        public Node getContent() {
            return content;
        }

        @Override
        protected void layoutChildren() {
            clip.setWidth(getWidth());
            clip.setHeight(getHeight());
        }

        public void scale(double pivotX, double pivotY, double scale) {
            Affine t = transform.clone();
            t.append(Transform.scale(scale, scale, pivotX, pivotY));
        }

        public void translate(double x, double y) {
            transform.append(Transform.translate(x, y));
        }
    }
}

编辑:

我想我有办法了。我把它贴在下面了。

EN

回答 1

Stack Overflow用户

发布于 2022-01-31 16:38:08

我想这就是答案。

编辑:不,不是。仍然有问题。也许我每次都会破坏和重建这个窗格

@kleopatra在“`PanZoomPane”中提到了给super.layoutChildren的一个电话。虽然这对测试有效,但对我的代码不起作用。

@珠宝海向我指出了这个帖子,它有一个重要的假设:

在第三方面,我们期望人们在初始化时设置场景图,并对节点进行就地修改,而不是进行场景图操作。

这显然不是我要做的。我不只是在做场景图手术,可以说是彻头彻尾的屠杀。

我以前见过@James_D提到如何在layoutChildren中更好地完成一些工作,而不是事件处理程序。所以也许layoutChildren是我们应该去找的地方。

当调用PanZoomPane.layoutChildren时,它将不可避免地调用我的自定义窗格的layoutChildren

当调整PanZoomPane大小时,将调用layoutChildren。但是当它被平移或放大时,它就不会了。为了强制这个问题,在scaletranslate方法中,我挠痒窗格的高度:

代码语言:javascript
运行
复制
double height = getHeight();
setHeight(height + 1);
setHeight(height);

这足以触发layoutChildren调用。显然,layoutChildren是现场图形手术是可以接受的地方。所以,我把我所有的场景图形代码都移到了layoutChildren

可以说,我应该将挠痒移到我的内容窗格中,而不是强迫它从PanZoomPane中退出。

但是它的核心是,我想场景图手术在事件处理程序中是不受欢迎的。我们应该使用现有场景图的属性。

我不知道是否有更好的解决办法,但就目前而言,这似乎是可行的,似乎有点道理。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70920125

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档