【问题标题】:How to create totally custom JavaFX control? Or: how to create Pane with dynamically drawn background?如何创建完全自定义的 JavaFX 控件?或者:如何创建具有动态绘制背景的窗格?
【发布时间】:2017-05-05 15:24:53
【问题描述】:

我想创建完全自定义的 JavaFX 控件。我不想“更喜欢作曲”,因为我没有东西要作曲。

例如,假设我需要一个窗格,里面有坐标网格。窗格应该像普通窗格一样工作,即应该可以在那里添加控件或几何图形,但在窗格的背景应该绘制一个网格。此外,数值应绘制在窗格的边缘。

所有这些都应该动态反映窗格的变换和视口。

另一个例子:假设我想将平铺图像作为背景。有数百万个图块,就像在谷歌地图中一样,所以我不能将它们作为子节点加载,因为它们会耗尽内存。我需要在用户滚动窗格时动态加载和卸载它们。

同样,窗格应该像普通窗格一样工作,即可以将子级添加到其中。

如何做到这一点?我发现,类似于paintConponent 的低级方法要么不存在,要么已弃用。那么,该怎么办呢?

更新

我想设计一个具有自定义背景的 CONTAINER。

例如,像这样:

(它应该是无止境的,即一旦控件调整大小显示更多行)

默认情况下容器应该没有孩子,但仍然有背景。背景不应是容器的子级。我,程序员,应该能够将孩子添加到这个容器中,只有在那之后,孩子才会出现在容器中。它们应该出现在背景之上。

像这样:

请注意,我们这里只有 2 个孩子。

更新

ScrollBarstandard 控制下方的代码中显示。如您所见,它包含可移动的旋钮和可按下的箭头按钮。

同时,该控件中的子节点数报告为零。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class ChildrenOfDefaultControls extends Application {
   @Override
   public void start(Stage primaryStage) throws Exception

   {

      ScrollBar scrollBar = new ScrollBar();

      AnchorPane root = new AnchorPane(scrollBar);
      AnchorPane.setLeftAnchor(scrollBar, 0.);
      AnchorPane.setRightAnchor(scrollBar, 0.);
      AnchorPane.setTopAnchor(scrollBar, 100.);

      Scene scene = new Scene(root, 800, 600);

      primaryStage.setTitle(String.valueOf(scrollBar.getChildrenUnmodifiable().size()));
      primaryStage.setScene(scene);
      primaryStage.show();

   }

   public static void main(String[] args) {
      Application.launch(ChildrenOfDefaultControls.class, args);
   }
}

好吧,我同意如果每个人都说不可能像 Swing 那样画画,那么让我们来作曲吧。但是如何像 ScrollBarcontrol 那样对用户隐藏这个组合?

【问题讨论】:

  • layoutChildren() 听起来像是不正确的东西?......就像一些黑客......
  • Skin 也听起来不合适,因为它可以根据定义进行更改。我希望控件永远具有计算背景。
  • 我在 Skin 和 SkinBase 中都没有看到任何绘图方法。绘制代码放在哪里?
  • 线条和瓦片都应该在底层绘制,它们不应该是节点
  • 这意味着我不能有真正的自定义控件,只有可以组合它们的砖块......

标签: java swing javafx


【解决方案1】:

使用Canvas 使用GraphicsContext 提供的命令呈现任意内容;将Canvas 添加到具有合适布局的Pane。在此example 中,“CanvasPaneCanvas 的实例包装在Pane 中并覆盖layoutChildren() 以使画布尺寸与封闭的Pane 匹配。”将rootBorderPane 切换到StackPane 允许在动画背景上放置控件。该示例添加了一个CheckBox,但您可以添加一个包含任何所需控件的Parent。调整舞台大小以查看效果。

    StackPane root = new StackPane();
    root.getChildren().addAll(canvasPane, cb);

附录:在@jewelsea的这个相关的examplecited中,背景是在layoutChildren()的实现中直接渲染的,同时填充了Pane作为封闭的Parent的大小。

你还是个孩子的Canvas

是的,这是一种渲染无尽背景的便捷方式,尽管有一些开销:“每次调用都会将必要的参数推送到缓冲区,稍后它们将在脉搏结束。”

我想像 Oracle 那样从头开始设计控件,而不是结合现有控件。

正如here 所讨论的,“JavaFX UI 控件……是通过使用场景图中的节点构建的”,包括图像、文本和basic geometric shapes。这显着减轻了 Swing paintComponent() 甚至 JavaFX getGraphicsContext2D() 所需的上下文切换开销。当然,正如here 所讨论的,“编写新的 UI 控件并非易事。”您必须决定在您的用例中所做的努力是否合理。

我可以对控件用户隐藏部分子项,以便他看不到该控件包含Canvas吗?

是的,Canvas 很方便,但不是必需的。在下面的示例中,LinePane extends StackPane 并将Rectangle 与您问题中的图像的一部分平铺。请注意,LinePaneLineTest 中仍然是一个StackPane,它在Pos.TOP_LEFT 中添加了一个ButtonRectangle 默认占用Pos.CENTER。覆盖layoutChildren()Rectangle 提供了动态调整大小的机会。

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.ImagePattern;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

/**
 * @see https://stackoverflow.com/a/43814198/230513
 */
public class LineTest extends Application {

    @Override
    public void start(Stage Stage) {
        Stage.setTitle("LineTest");
        LinePane linePane = new LinePane();
        Button button = new Button("Button");
        LinePane.setAlignment(button, Pos.TOP_LEFT);
        LinePane.setMargin(button, new Insets(50));
        linePane.getChildren().add(button);
        Scene scene = new Scene(linePane, 320, 240);
        Stage.setScene(scene);
        Stage.show();
    }

    private static class LinePane extends StackPane {

        private static final String URL = "https://i.stack.imgur.com/bqXKK.png";
        private final Image lines = new Image(URL);
        private final Rectangle rectangle = new Rectangle();

        public LinePane() {
            rectangle.setFill(new ImagePattern(lines, 8, 22, 34, 34, false));
            getChildren().add(rectangle);
        }

        @Override
        protected void layoutChildren() {
            super.layoutChildren();
            final double x = snappedLeftInset();
            final double y = snappedTopInset();
            final double w = snapSize(getWidth()) - x - snappedRightInset();
            final double h = snapSize(getHeight()) - y - snappedBottomInset();
            rectangle.setLayoutX(x);
            rectangle.setLayoutY(y);
            rectangle.setWidth(w);
            rectangle.setHeight(h);
        }
    }

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

【讨论】:

  • 另一个使用类似概念的例子是:Resizable Grid using Canvas
  • 您小时候仍然拥有 Canvas。任何人都可以删除它,背景将消失。我想要成品:具有动态背景的可重用控件。我不想要组合,我想要像 Oracle 那样从头开始设计控件,而不是组合现有控件。
  • 我感同身受; JavaFX 控件设计更复杂,但更具可扩展性,而且速度肯定更快。
  • 好的,我可以对控制用户隐藏部分子项,让他看不到该控件包含Canvas 或其他服务子项,并且无法删除/修改它们吗?
  • IIUC,是的;我上面已经详细说明了。
猜你喜欢
  • 1970-01-01
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
  • 2017-02-11
  • 2014-10-31
  • 1970-01-01
  • 2019-08-06
  • 1970-01-01
相关资源
最近更新 更多