【问题标题】:Fixed size JavaFX component固定大小的 JavaFX 组件
【发布时间】:2021-09-03 22:12:36
【问题描述】:

与 Swing 中的“一切都是 JPanel”相比,在 JavaFX 中创建新组件对我来说仍然是一件很麻烦的事情。

我正在尝试制作一个固定大小的组件。我不敢称它为控件,它是一个活动窗格,而不是按钮。

但这是我的问题。

我想要的固定大小小于元素的内容。

网格实际上是 200x200。我将它向上和向左移动 25x25,并且我正在尝试将固定大小设为 150x150。您可以在我的示例中看到我尝试了各种将其强制为 150 的方法,但在我的测试中,大小永远不会粘住。另外,需要明确的是,我希望这些线条会在组件的边界处剪辑。

这大致就是我在人为的情况下拍摄的内容(请注意,这看起来比 150x150 大,因为我的 Mac 上的视网膜显示屏使所有内容都翻倍):

我已将一些放入 FlowPane,它们会直接堆叠,但忽略 150x150 尺寸。

        FlowPane fp = new FlowPane(new TestPane(), new TestPane(), new TestPane());
        var scene = new Scene(fp, 640, 480);
        stage.setScene(scene);

我尝试在 ScrollPane 中粘贴一个,但滚动条永远不会出现,即使在调整窗口大小后也是如此。

        TestPane pane = new TestPane();
        ScrollPane sp = new ScrollPane(pane);
        var scene = new Scene(sp, 640, 480);
        stage.setScene(scene);

我很难辨别在这些情况下我应该扩展区域还是控制。

我缺少一些基本的东西。

package pkg;

import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.shape.Line;
import javafx.scene.transform.Translate;

public class TestPane extends Control {

    public TestPane() {
        setMinHeight(150);
        setMaxHeight(150);
        setMinWidth(150);
        setMaxWidth(150);
        setPrefHeight(150);
        setPrefWidth(150);
        populate();
    }

    @Override
    protected double computePrefHeight(double width) {
        return 150;
    }

    @Override
    protected double computePrefWidth(double height) {
        return 150;
    }

    @Override
    protected double computeMaxHeight(double width) {
        return 150;
    }

    @Override
    protected double computeMaxWidth(double height) {
        return 150;
    }

    @Override
    protected double computeMinHeight(double width) {
        return 150;
    }

    @Override
    protected double computeMinWidth(double height) {
        return 150;
    }

    
    @Override
    public boolean isResizable() {
        return false;
    }

    private void populate() {
        Translate translate = new Translate();
        translate.setX(-25);
        translate.setY(-25);

        getTransforms().clear();
        getTransforms().addAll(translate);

        ObservableList<Node> children = getChildren();

        for (int i = 0; i < 4; i++) {
            Line line = new Line(0, i * 50, 200, i * 50);
            children.add(line);
            line = new Line(i * 50, 0, i * 50, 200);
            children.add(line);
        }
    }
}

补充说明。

我想要一个固定大小的组件。这是一个矩形。我想要它 X x Y 大。

我想在我的盒子里画东西。线条、圆圈、文字。

我希望我绘制的东西被剪辑到组件的边界。

我不想使用 Canvas。

更多附录。

我要查找的内容与 ScrollPane 所做的没有太大区别,只是我不想要任何滚动条,而且我不希望外围窗格的大小增大或缩小。

【问题讨论】:

  • 您是否考虑过使用场景构建器进行设计?
  • 忘记这个想法 - 绝对 固定大小 一直是并且仍然是完全错误的思考方向。而是将您的问题表述为布局,即考虑关系(大小、位置)
  • 我认为我们需要了解更多信息来指导您。在 Swing 中,您将组件添加到布局中;您覆盖 paintComponent() 以更改组件的外观。在javafx 中,您将节点添加到图表中,例如Lines 在您的示例中为 ControlCanvas 是一个 Node,可让您在GraphicsContext 中安排渲染操作。这里有一些examples 比较两者。
  • 您在寻找GridPane吗?将Pane 添加到GridPane 中的每个单元格。使用鼠标在每个Pane 上绘图。 stackoverflow.com/questions/53708509/…
  • @WillHartung 是的,这就是我的观点。布局边界不包括转换。因此,您的自定义组件将布置在父容器中,然后 然后 将应用转换。因此,您的方法不能很好地与放置它的布局窗格一起使用。请参阅openjfx.io/javadoc/16/javafx.graphics/javafx/scene/layout/…中的“视觉边界与布局边界”

标签: javafx


【解决方案1】:

TLDR:

  • 子类Region,
  • 使 isResizable() 返回 true 以尊重首选项、最小和最大尺寸,
  • 明确设置剪辑以避免在本地边界之外进行绘制。

这方面的大部分文档都在package documentation for javafx.scene.layout

首先,请注意可调整大小和不可调整大小节点之间的区别。可调整大小的节点(isResizable() 返回 true)在布局期间由其父级调整大小,并且父级将尽最大努力尊重其首选、最小和最大尺寸。

不可调整大小的节点不会被其父节点调整大小。如果 isResizable() 返回 false,则 resize() 是一个无操作,并且首选、最小和最大大小将被有效忽略。它们的大小在内部计算并通过其视觉边界报告给父级。最终,所有 JavaFX 节点在底层图形系统中都有一个对等节点,AFAIK 无法调整大小的节点确定其大小的唯一方法是直接设置对等节点的大小。 (我很高兴得到纠正。)

因此,除非您想真正弄脏自定义对等节点(而且我什至不知道 API 是否有这方面的机制),否则我认为创建“固定大小节点”的首选方法是创建一个 resizable 节点,其首选、最小和最大尺寸都设置为相同的值。这很可能是设计使然:正如对您问题的评论中所述,通常不鼓励布局驱动的 UI 工具包中的固定大小节点,除了非常低级的组件(TextShape 等)。

应用于可调整大小节点的转换通常在 布局之后应用(即它们不会影响布局边界)。因此使用翻译来管理子节点的内部定位不是一个好方法;它会对父节点中的自定义节点的布局产生影响,而您可能不希望这样做。

正如您所注意到的,您实际上并没有在这里定义控件;它没有行为或皮肤。因此子类化Control 并不是真正正确的方法。 API 中最合适的钩子是子类Region。覆盖layoutChildren() 方法来定位子节点(对于Shapes 和Text 节点,设置它们的坐标,对于可调整大小的子节点调用resizeRelocate(...))。

最后,为防止节点溢出其预期边界(在您的示例中为 150x150),请确保没有子节点位于这些边界之外,或显式设置剪辑。

这是对您的示例的重构:

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

public class TestPane extends Region {
    
    private Line[] verticalLines ;
    private Line[] horizontalLines ;
    
    private static final int WIDTH = 150 ;
    private static final int HEIGHT = 150 ;
    private static final int LINE_GAP = 50 ;

    public TestPane() {
        populate();
    }

    @Override
    protected double computePrefHeight(double width) {
        return HEIGHT;
    }

    @Override
    protected double computePrefWidth(double height) {
        return HEIGHT;
    }

    @Override
    protected double computeMaxHeight(double width) {
        return HEIGHT;
    }

    @Override
    protected double computeMaxWidth(double height) {
        return WIDTH;
    }

    @Override
    protected double computeMinHeight(double width) {
        return WIDTH;
    }

    @Override
    protected double computeMinWidth(double height) {
        return WIDTH;
    }

    
    @Override
    public boolean isResizable() {
        return true;
    }
    
    @Override
    public void layoutChildren() {
        double w = getWidth();
        double h = getHeight() ;
        
        double actualWidth = verticalLines.length * LINE_GAP ;
        double actualHeight = horizontalLines.length * LINE_GAP ;
        
        double hOffset = (actualWidth - w) / 2 ;
        double vOffset = (actualHeight - h) / 2 ;
                
        for (int i = 0 ; i < verticalLines.length ; i++)  {
            double x = i * LINE_GAP - hOffset;
            verticalLines[i].setStartX(x);
            verticalLines[i].setEndX(x);
            verticalLines[i].setStartY(0);
            verticalLines[i].setEndY(h);
        }
        
        for (int i = 0 ; i < horizontalLines.length ; i++)  {
            double y = i * LINE_GAP - vOffset;
            horizontalLines[i].setStartY(y);
            horizontalLines[i].setEndY(y);
            horizontalLines[i].setStartX(0);
            horizontalLines[i].setEndX(w);
        }
        
        setClip(new Rectangle(0, 0, w, h));
    }

    private void populate() {
        
        verticalLines = new Line[4] ;
        horizontalLines = new Line[4] ;
        
        for (int i = 0 ; i <verticalLines.length ; i++) {
            verticalLines[i] = new Line();
            getChildren().add(verticalLines[i]);
        }
        
        for (int i = 0 ; i <horizontalLines.length ; i++) {
            horizontalLines[i] = new Line();
            getChildren().add(horizontalLines[i]);
        }
        
        
    }
}

一个更复杂的例子可能有,例如,LINE_GAP 作为一个属性。当该属性发生变化时,您将调用 requestLayout() 将组件标记为“脏”,因此将在渲染的下一帧再次调用其 layoutChildren() 方法。

这是一个快速测试用例:

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;



public class App extends Application {

    @Override
    public void start(Stage stage) {
        FlowPane root = new FlowPane();
        root.setAlignment(Pos.TOP_LEFT);
        root.setPadding(new Insets(10));
        root.setHgap(5);
        root.setVgap(5);
        for (int i = 0; i < 6 ; i++) {
            root.getChildren().add(new TestPane());
        }
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();

    }

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

}

结果:

这与布局窗格很好地配合;调整窗口大小给出了

【讨论】:

  • 再次感谢@James_D。在高层次上,populate 方法应该构建场景图,layoutChildren 应该放置节点。我认为 layoutChildren 改变场景图是不合适的?我有一个更大的逻辑模型。目标是为该模型提供一个视口。我曾希望翻译左上角,然后简单地用视口内的元素(在它们的“全局”坐标中)填充图形,并将其裁剪到矩形。那么,在这种情况下我将如何应用翻译还是我必须自己做呢? (这就是我翻译原文的原因。)
  • 我认为在布置孩子的同时改变场景图是可以的。布局通行证是自上而下执行的。当您已经在布局中间时,您不能调用 layout() 方法来请求布局。
  • @WillHartung 由于我在答案中概述的原因,我不会使用翻译。就像我展示的那样重新定位东西。另一种选择是将翻译应用于子节点(此处为行),将它们包装在一个组中,然后将该组添加到该区域。但是直接计算子节点的位置似乎更简单。
  • @WillHartung 至于在layoutChildren 中操作场景图,我觉得没问题,而且我有时也做过。在这里我并没有因为它似乎只构建一次场景图更好,而不是每次执行布局。在您的情况下,如果您的模型是可观察的,只需操纵场景图以响应模型的变化,然后布局当前显示在 layoutChildren() 中的任何内容
猜你喜欢
  • 2012-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-09
  • 1970-01-01
相关资源
最近更新 更多