带有layoutChildren()的JavaFX中的AutoScalePane [英] AutoScalePane in JavaFX with layoutChildren()

查看:108
本文介绍了带有layoutChildren()的JavaFX中的AutoScalePane的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建一个自定义窗格,它将其内容缩放到窗格的可用空间.

我创建了一个演示应用程序,该应用程序使用SplitPane拆分舞台.每个拆分包含一个AutoScalePane(请参阅FMXL).我希望AutoScalePane会根据可用空间来缩小/增加其内容(请使用分割条播放)

AutoScalePane的内容被分组在一个组中,随着AutoScalePane边界的变化,该组应进行缩放.

即使我收到正确的边界并且可以计算正确的缩放比例(检查调试日志),Circle节点也未缩放.

我认为我在layoutChildren()方法中犯了一个错误,但是看不到明显的问题.

如果有更多JavaFX经验的人可以帮助我,那就太好了:)

 公共类Main扩展Application {公共静态void main(String [] args){发射(参数);}@Overridepublic void start(Stage primaryStage)引发异常{父根= FXMLLoader.load(getClass().getResource("sample.fxml"));primaryStage.setTitle("AutoScalePane Test");primaryStage.setScene(new Scene(root,700,200));primaryStage.show();}} 

视图控制器:

 公共类控制器{@FXML公共AutoScalePane scalePaneLeft;@FXMLpublic AutoScalePane scalePaneRight;@FXML公共无效initialize(){fillLeftContent();fillRightContent();}私人void fillLeftContent(){Circle circle1 =新的Circle(100,300,10);Circle circle2 =新的Circle(150,300,10);Circle circle3 =新的Circle(200,300,10);Circle circle4 =新的Circle(250,300,10);scalePaneLeft.addChildren(new Node [] {circle1,circle2,circle3,circle4});}私人void fillRightContent(){Circle circle1 =新的Circle(100,200,20);Circle circle2 =新的Circle(150,200,20);Circle circle3 =新的Circle(200,200,20);Circle circle4 =新的Circle(250,200,20);scalePaneRight.addChildren(new Node [] {circle1,circle2,circle3,circle4});}} 

FXML视图:

 <?import javafx.scene.control.SplitPane?><?import javafx.scene.layout.AnchorPane吗?<?import sample.AutoScalePane?>< AnchorPane fx:controller ="sample.Controller"xmlns:fx ="http://javafx.com/fxml">< SplitPane splitrPositions ="0.3" orientation ="HORIZONTAL" AnchorPane.topAnchor ="0" AnchorPane.bottomAnchor ="0"AnchorPane.leftAnchor ="0" AnchorPane.rightAnchor ="0" style =-fx-background-color:#2c5069;">< AutoScalePane fx:id ="scalePaneLeft"style =-fx-background-color:#943736;"/>< AutoScalePane fx:id ="scalePaneRight"style =-fx-background-color:#d27452;"/></SplitPane></AnchorPane> 

自动缩放窗格:

 /***根据窗格的可用空间自动缩放其内容.*内容始终居中**/公共类AutoScalePane扩展了窗格{私人群组内容= new Group();private Scale zoom = new Scale(1,1);公共AutoScalePane(){layoutBoundsProperty().addListener((o)-> {autoScale();});content.scaleXProperty().bind(zoom.xProperty());content.scaleYProperty().bind(zoom.yProperty());getChildren().add(content);}/***将节点添加到AutoScalePane** @param子节点*/public void addChildren(Node ... children){content.getChildren().addAll(children);requestLayout();}私人void autoScale(){如果(getHeight()> 0&&getWidth()>0&&content.getBoundsInParent().getWidth()>0&&content.getBoundsInParent().getHeight()>0){//规模double scaleX = getWidth()/content.getBoundsInParent().getWidth();double scaleY = getHeight()/content.getBoundsInParent().getHeight();System.out.println("*************** DEBUG ****************");System.out.println(窗格宽度:" + getWidth());System.out.println("Content Bounds Width:" + content.getBoundsInParent().getWidth());System.out.println(窗格高度:" + getHeight());System.out.println("Content Bounds Height:" + content.getBoundsInParent().getHeight());System.out.println("ScaleX:" + scaleX);System.out.println("ScaleY:" + scaleY);double zoomFactor = Math.min(scaleX,scaleY);zoom.setX(zoomFactor);zoom.setY(zoomFactor);requestLayout();}}@Override受保护的void layoutChildren(){最后的double paneWidth = getWidth();最后的double paneHeight = getHeight();最后的double insetTop = getInsets().getTop();最后的double insetRight = getInsets().getRight();最后的double insetLeft = getInsets().getLeft();最后的double insertBottom = getInsets().getBottom();最后的double contentWidth =(paneWidth-insetLeft-insetRight)*zoom.getX();最后的double contentHeight =(paneHeight-insetTop-insertBottom)*zoom.getY();layoutInArea(content,0,0,contentWidth,contentHeight,getBaselineOffset(),HPos.CENTER,VPos.CENTER);}} 

更改节点大小时,将调用

解决方案

layoutChildren .如果您通过 layoutChildren 方法调整比例,则无需注册侦听器.

至于缩放:您从未真正修改过 scale 属性.您无需在此代码段中的任何地方更新 Scale :

  double zoomFactor = Math.min(zoom.getX(),zoom.getY());zoom.setX(zoomFactor);zoom.setY(zoomFactor); 

因此 zoom.getX() zoom.getY()始终返回等于初始比例因子的 1 .

请注意,您可以将 Scale 矩阵直接应用于内容节点的 transforms ,但这不会将中心用作缩放的枢轴点.

BTW:通过扩展 Region (而不是 Pane ),您可以将对 children 列表的访问限制为受 protected 阻止用户对其进行修改.

 公共类AutoScalePane扩展了Region {私人最终小组内容= new Group();公共AutoScalePane(){content.setManaged(false);//避免按内容限制大小getChildren().add(content);}/***将节点添加到AutoScalePane** @param子节点*/public void addChildren(Node ... children){content.getChildren().addAll(children);requestLayout();}@Override受保护的void layoutChildren(){final Bounds groupBounds = content.getBoundsInLocal();最后的double paneWidth = getWidth();最后的double paneHeight = getHeight();最后的double insetTop = getInsets().getTop();最后的double insetRight = getInsets().getRight();最后的double insetLeft = getInsets().getLeft();最后的double insertBottom = getInsets().getBottom();最后的double contentWidth =(paneWidth-insetLeft-insetRight);最后的double contentHeight =(paneHeight-insetTop-insertBottom);//飞涨double factorX = contentWidth/groupBounds.getWidth();double factorY = contentHeight/groupBounds.getHeight();双因素= Math.min(factorX,factorY);content.setScaleX(factor);content.setScaleY(factor);layoutInArea(content,insetLeft,insetTop,contentWidth,contentHeight,getBaselineOffset(),HPos.CENTER,VPos.CENTER);}} 

I am trying to created a custom pane, which scales it's content to the available space of the pane.

I created a demo application, which splits the Stage with a SplitPane. Each split contains one AutoScalePane (see FMXL). I would expect the AutoScalePane to shrink/grow its content according to the available space (please play with the split bar)

The content of the AutoScalePane is grouped in a Group, which should be scaled, as the AutoScalePane boundaries change.

Even though, I receive the correct bounds and can compute the right zoom ratio (check debug log), the Circle nodes are not scaled..

I assume that I made a mistake in the layoutChildren() method, but I can't see an obvious issue.

It would be great if somebody with more JavaFX experience could help me :)

public class Main extends Application {

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("AutoScalePane Test");
        primaryStage.setScene(new Scene(root, 700, 200));
        primaryStage.show();
    }
}

View Controller:

public class Controller {
    @FXML
    public AutoScalePane scalePaneLeft;

    @FXML
    public AutoScalePane scalePaneRight;

    @FXML
    public void initialize() {
        fillLeftContent();
        fillRightContent();
    }

    private void fillLeftContent() {
        Circle circle1 = new Circle(100, 300, 10);
        Circle circle2 = new Circle(150, 300, 10);
        Circle circle3 = new Circle(200, 300, 10);
        Circle circle4 = new Circle(250, 300, 10);

        scalePaneLeft.addChildren(new Node[] {circle1, circle2, circle3,
                circle4});
    }

    private void fillRightContent() {
        Circle circle1 = new Circle(100, 200, 20);
        Circle circle2 = new Circle(150, 200, 20);
        Circle circle3 = new Circle(200, 200, 20);
        Circle circle4 = new Circle(250, 200, 20);

        scalePaneRight.addChildren(new Node[] {circle1, circle2, circle3,
                circle4});
    }
}

FXML View:

<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import sample.AutoScalePane?>
<AnchorPane fx:controller="sample.Controller"
            xmlns:fx="http://javafx.com/fxml">


    <SplitPane dividerPositions="0.3" orientation="HORIZONTAL" AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0"
               AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" style="-fx-background-color: #2c5069;">

        <AutoScalePane fx:id="scalePaneLeft"
                       style="-fx-background-color: #943736;"/>

        <AutoScalePane fx:id="scalePaneRight"
                       style="-fx-background-color: #d27452;"/>

    </SplitPane>

</AnchorPane>

Auto-scale Pane:

   /**
     * Auto-scales its content according to the available space of the Pane.
     * The content is always centered
     *
     */
    public class AutoScalePane extends Pane {

        private Group content = new Group();
        private Scale zoom = new Scale(1, 1);

        public AutoScalePane() {
            layoutBoundsProperty().addListener((o) -> {
                autoScale();
            });

            content.scaleXProperty().bind(zoom.xProperty());
            content.scaleYProperty().bind(zoom.yProperty());

            getChildren().add(content);
        }

        /**
         * Adds nodes to the AutoScalePane
         *
         * @param children nodes
         */
        public void addChildren(Node... children) {
            content.getChildren().addAll(children);
            requestLayout();
        }

        private void autoScale() {
            if (getHeight() > 0
                    && getWidth() > 0
                    && content.getBoundsInParent().getWidth() > 0
                    && content.getBoundsInParent().getHeight() > 0) {

                // scale
                double scaleX = getWidth() / content.getBoundsInParent().getWidth();
                double scaleY = getHeight() / content.getBoundsInParent()
                        .getHeight();

                System.out.println("*************** DEBUG ****************");
                System.out.println("Pane Width: " + getWidth());
                System.out.println("Content Bounds Width: " + content
                        .getBoundsInParent()
                        .getWidth());
                System.out.println("Pane Height: " + getHeight());
                System.out.println("Content Bounds Height: " + content
                        .getBoundsInParent()
                        .getHeight());
                System.out.println("ScaleX: " + scaleX);
                System.out.println("ScaleY: " + scaleY);

                double zoomFactor = Math.min(scaleX, scaleY);
                zoom.setX(zoomFactor);
                zoom.setY(zoomFactor);

                requestLayout();
            }
        }

        @Override
        protected void layoutChildren() {
            final double paneWidth = getWidth();
            final double paneHeight = getHeight();
            final double insetTop = getInsets().getTop();
            final double insetRight = getInsets().getRight();
            final double insetLeft = getInsets().getLeft();
            final double insertBottom = getInsets().getBottom();

            final double contentWidth = (paneWidth - insetLeft - insetRight) *
                    zoom.getX();
            final double contentHeight = (paneHeight - insetTop - insertBottom) *
                    zoom.getY();

            layoutInArea(content, 0, 0, contentWidth, contentHeight,
                    getBaselineOffset(), HPos.CENTER, VPos.CENTER);
        }
    }

解决方案

layoutChildren is invoked on a change of the size of the node. You don't need to register a listener if you adjust the scale from the layoutChildren method.

As for the zoom: You never really modify the scale properties. You don't update the Scale anywhere but in this snippet:

double zoomFactor = Math.min(zoom.getX(), zoom.getY());
zoom.setX(zoomFactor);
zoom.setY(zoomFactor);

so zoom.getX() and zoom.getY() always return 1 which is equal to the initial scale factor.

Note that you can apply the Scale matrix to the transforms of the content node directly, but this wouldn't use the center as a pivot point of the zoom.

BTW: By extending Region instead of Pane you restrict the access to the children list to protected which prevents users from modifying it.

public class AutoScalePane extends Region {

    private final Group content = new Group();

    public AutoScalePane() {
        content.setManaged(false); // avoid constraining the size by content
        getChildren().add(content);
    }

    /**
     * Adds nodes to the AutoScalePane
     *
     * @param children nodes
     */
    public void addChildren(Node... children) {
        content.getChildren().addAll(children);
        requestLayout();
    }

    @Override
    protected void layoutChildren() {
        final Bounds groupBounds = content.getBoundsInLocal();

        final double paneWidth = getWidth();
        final double paneHeight = getHeight();
        final double insetTop = getInsets().getTop();
        final double insetRight = getInsets().getRight();
        final double insetLeft = getInsets().getLeft();
        final double insertBottom = getInsets().getBottom();

        final double contentWidth = (paneWidth - insetLeft - insetRight);
        final double contentHeight = (paneHeight - insetTop - insertBottom);

        // zoom
        double factorX = contentWidth / groupBounds.getWidth();
        double factorY = contentHeight / groupBounds.getHeight();
        double factor = Math.min(factorX, factorY);
        content.setScaleX(factor);
        content.setScaleY(factor);

        layoutInArea(content, insetLeft, insetTop, contentWidth, contentHeight,
                getBaselineOffset(), HPos.CENTER, VPos.CENTER);
    }

}

这篇关于带有layoutChildren()的JavaFX中的AutoScalePane的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆