javafx - 为什么 JavaFX 中出现奇怪的绘画行为

标签 javafx

我有一个带有简单组件的简单 FX 示例。

package fxtest;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class App extends Application {

    @Override
    public void start(Stage stage) {
        var bp = new BorderPane();
        var r = new Rectangle(0, 0, 200, 200);
        r.setFill(Color.GREEN);
        var sp = new StackPane(r);
        bp.setCenter(sp);
        bp.setTop(new XPane());
        bp.setBottom(new XPane());
        bp.setLeft(new XPane());
        bp.setRight(new XPane());
        var scene = new Scene(bp, 640, 480);
        stage.setScene(scene);
        stage.show();
    }

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

package fxtest;

import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;

public class XPane extends Region {

    public XPane() {
        setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
        setMinSize(100, 100);
        setPrefSize(100, 100);

        widthProperty().addListener((o) -> {
            populate();
        });
        heightProperty().addListener((o) -> {
            populate();
        });
        populate();
    }

    private void populate() {
        ObservableList<Node> children = getChildren();
        Rectangle r = new Rectangle(getWidth(), getHeight());
        r.setFill(Color.WHITE);
        r.setStroke(Color.BLACK);
        children.add(r);
        Line line = new Line(0, 0, getWidth(), getHeight());
        children.add(line);
        line = new Line(0, getHeight(), getWidth(), 0);
        children.add(line);
    }
}

运行时,它会按照我的预期进行:

enter image description here

当我增大窗口时,X 也会增大。

但是当我缩小窗口时,我得到了侧面板的伪影。

enter image description here

我原以为删除背景可以解决这个问题,但我猜是顺序问题。但即便如此,当您拖动角时,所有 XPanes 的大小都会发生变化,并且它们都会重新绘制,但瑕疵仍然存在。

我尝试将 XPanes 包装到 StackPane 中,但这没有做任何事情(我认为它不会,但无论如何都试过了)。

我该如何补救?这是 macOS Big Sur 上 JDK 16 上的 JavaFX 13。

最佳答案

为什么会出现伪影

我认为应该使用不同的方法而不是修复现有方法,但如果需要,您可以修复它。

您正在向监听器中的 XPane 添加新的矩形和线条。每次高度或宽度发生变化时,您都会添加一组新节点,但旧高度和宽度的旧节点集仍然存在。最终,如果您调整的大小足够大,性能将会下降,或者您将耗尽内存或资源,从而使程序无法使用。

BorderPane 按照添加它们的顺序绘制其子项(中心和 XPanes)而不进行裁剪,因此这些旧行将保留并且渲染器将在您调整大小时将它们绘制在某些 Pane 上。同样,一些 Pane 会覆盖一些线条,因为您可能会在 Pane 中构建大量填充矩形,并且它们与创建的大量线条部分重叠。

要解决此问题,请在添加任何新节点之前在 populate() 方法中清除()子节点列表。

private void populate() {
    ObservableList<Node> children = getChildren();
    children.clear();

    // now you can add new nodes... 
}

替代解决方案

更改宽度和高度的监听器并不是真正向自定义区域添加内容的地方,IMO。

我认为最好利用场景图,让它在更改这些节点的属性后处理现有节点的重绘和更新,而不是一直创建新节点。

这是一个子类化区域并在发生调整大小时绘制良好的示例。

import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;

public class XPane extends Region {

    public XPane() {
        super();

        Rectangle border = new Rectangle();
        Line topLeftToBottomRight = new Line();
        Line bottomLeftToTopRight = new Line();

        getChildren().addAll(
                border,
                topLeftToBottomRight,
                bottomLeftToTopRight
        );

        border.setStroke(Color.BLACK);
        border.setFill(Color.WHITE);
        border.widthProperty().bind(
                widthProperty()
        );
        border.heightProperty().bind(
                heightProperty()
        );

        topLeftToBottomRight.endXProperty().bind(
                widthProperty()
        );
        topLeftToBottomRight.endYProperty().bind(
                heightProperty()
        );

        bottomLeftToTopRight.startYProperty().bind(
                heightProperty()
        );
        bottomLeftToTopRight.endXProperty().bind(
                widthProperty()
        );

        setMinSize(100, 100);
        setPrefSize(100, 100);
    }
}

关于区域与 Pane

我不确定您是否应该对 Pane 或 Region 进行子类化,两者之间的主要区别在于 Pane 具有用于可修改子列表的公共(public)访问器,而 Region 则没有。所以这取决于你想做什么。如果像例子一样只是画X,那么Region是合适的。

关于 layoutChildren() 与绑定(bind)

Region文档状态:

By default a Region inherits the layout behavior of its superclass, Parent, which means that it will resize any resizable child nodes to their preferred size, but will not reposition them. If an application needs more specific layout behavior, then it should use one of the Region subclasses: StackPane, HBox, VBox, TilePane, FlowPane, BorderPane, GridPane, or AnchorPane.

To implement a more custom layout, a Region subclass must override computePrefWidth, computePrefHeight, and layoutChildren. Note that layoutChildren is called automatically by the scene graph while executing a top-down layout pass and it should not be invoked directly by the region subclass.

Region subclasses which layout their children will position nodes by setting layoutX/layoutY and do not alter translateX/translateY, which are reserved for adjustments and animation.

我实际上并没有在这里这样做,而是在构造函数中进行绑定(bind),而不是覆盖 layoutChildren()。您可以实现替代解决方案,该解决方案按照文档讨论的方式运行,覆盖 layoutChildren() 而不是使用绑定(bind),但它更复杂,而且关于如何做到这一点的文档也较少。

继承 Region 并覆盖 layoutChildren() 并不常见。相反,通常会使用标准布局 Pane 的组合,并在 Pane 和节点上设置约束以获得所需的布局。这让布局引擎可以做很多工作,例如对齐像素、计算边距和插图、遵守约束、重新定位内容等,其中很多工作需要手动完成 layoutChildren() 实现。

关于javafx - 为什么 JavaFX 中出现奇怪的绘画行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70311488/

相关文章:

java - HBox child 不收缩

ListView 中的 JavaFx 按钮启用程序

java - PlPlot 和 Javafx

java - 用于设置 SeparatorMenuItem 颜色的 CSS?

java - 从 Controller 访问 FXML 中的重复控件,无需为每个控件提供 ID

java - 将 Shape 从 awt 转换为 javafx 中的 Shape

java - 如何正确设置 javafx datepicker 值?

JavaFX 表格 View : Do I HAVE to wrap my fields in Simple<Object/Integer/String>Property?

JavaFX ComboBox 选择错误

Mac OS 中的 Java fx 错误