当窗口/阶段缩小时,为什么位置和比例确定容器/组件没有正确设置?

时间:2017-04-28 15:51:08

标签: javafx javafx-8

我正在尝试学习如何使用javafx,并且我遇到了一些javafx代码的行为,这些行为对我来说并不合适。我希望你能解释为什么javafx代码产生这些行为以及如何解决它们。

为了便于说明,我写了一个小例子程序:

import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.SplitPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class Example extends Application {
    @Override
    public void start(Stage primaryStage) {
        // Top section with image
        ImageView imageView = new ImageView(new Image("http://wfarm2.dataknet.com/static/resources/icons/set114/b4f29385.png"));
            imageView.setPreserveRatio(true);
        StackPane stackPane = new StackPane(imageView);
            imageView.fitWidthProperty().bind(stackPane.widthProperty());
            imageView.fitHeightProperty().bind(stackPane.heightProperty());
            stackPane.getStyleClass().add("stack-pane");

        // Bottom Section with CSS toggle
        ToggleButton toggle = new ToggleButton("Show CSS");
        HBox hbox = new HBox(toggle);
            hbox.setAlignment(Pos.CENTER);
            hbox.getStyleClass().add("h-box");

        // Combines the two sections
        SplitPane root = new SplitPane(stackPane, hbox);
            root.setOrientation(Orientation.VERTICAL);
            root.setResizableWithParent(hbox, false);
        Scene scene = new Scene(root);
            toggle.setOnMouseClicked(e -> {
                if (toggle.isSelected()) {
                    scene.getStylesheets().add("StyleSheet.css");
                    toggle.setText("Disable CSS");
                } else {
                    scene.getStylesheets().remove(0);
                    toggle.setText("Show CSS");
                }
            });
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

StyleSheet.css文件仅包含以下三行:

.stack-pane {  -fx-background-color: aqua;  }
.split-pane {  -fx-background-color: crimson;  }
.h-box {  -fx-background-color: chartreuse;  }

最初我认为这个程序会在顶部显示一个图像 - 就像堆栈窗格一样 - 在窗口/阶段调整大小时总会调整大小。不幸的是,这并不是发生的事情:当窗口/阶段收缩时,堆栈窗格和图像没有正确调整大小。事实上,即使堆叠窗格的位置似乎也是偏移的,并且与分割窗格没有正确对齐。

以下图片旨在作为视觉参考: 它看起来像:

  1. 当窗口/舞台收缩时。

    black

  2. 当窗口/舞台缩小并着色时。

    colored

  3. 正如您所看到的,现实并不符合我原先的期望。

    1. 你知道为什么会这样吗?
    2. 如何修复示例程序?
    3. 窗口/舞台是不是意味着在启动后缩小?
    4. 甚至可以用javafx解决这个问题吗?
    5. 我注意到的其他一些事情是,当窗户/舞台收缩时,图像不会缩小。你知道背后的原因吗?有修复吗?

1 个答案:

答案 0 :(得分:1)

  

当窗口/舞台缩小时,图像不会缩小

是的,这是设计的。图像和ImageView具有固定的大小。如果您想动态调整ImageView的大小,那么有几个选项可以在这个答案中概述:

  

如何修复示例程序?

使用上面可调整大小的图片中概述的技术之一。

  

启动后窗口/阶段是不是意味着缩小?

取决于您编写代码的方式。您可以这样编写,使其不会缩小,或者您可以编写它,以便在舞台大小更改时适当调整布局和内容。如何实现这一点取决于您作为开发人员。 JavaFX为可调整大小的布局定义了许多方法,其中一些在下面讨论:

实现可调整大小布局的标准方法是使用适当的layout panes

  

即使堆栈窗格的位置似乎偏移并且未与拆分窗格

正确对齐

我没有详细分析您的代码中的这个问题。我认为这可能是由几件事造成的:

  1. 您将ImageView的fitWidth / Height绑定到包含ImageView的StackPane的宽度/高度。这可能会导致默认布局代码有点混淆,因为StackPane的宽度/高度的部分决定因素是子项的首选大小,但是通过改变fitWidth / Height,您将更改ImageView的首选大小,所以这使得很难推断StackPane的宽度和高度应该是什么。
  2. 当没有足够的空间显示StackPane的内容时,StackPane可能会大于可用区域(这在StackPane javadoc中有所解释)。因此,当你使场景更大时,fitWidth / Height会随着StackPane的可用区域而变得更大,但是当调整大小以使其变小时,StackPane永远不会变小,因为它需要至少与放大后的大小一样大之前计算过的图像视图。
  3. 无论如何,故事的道德是:不要将孩子的宽度/高度绑定到父容器的宽度/高度。有时您可以使用这种方法,有时它会产生奇怪的副作用,例如您在示例代码中观察到的。

    相反,您可以覆盖父节点中的布局传递逻辑,并在那里执行适当的布局计算。下面的示例中提供了此方法的示例。不幸的是,Oracle(或几乎网上的任何其他地方)没有关于覆盖父节点的布局传递逻辑的技术的零官方文档。大多数情况下,您不需要编写自定义布局逻辑,而是可以使用标准布局窗格和控件来获取所需的布局(尽管不幸的是,在这种情况下无法使用CSS后台设置或自定义布局代码。

    使用-fx-shape

    进行平滑缩放

    缩放位图图像可能会导致某些像素化并且图像不是很平滑。在您的情况下,输入位图图像在512x512处非常大,因此平滑缩放对于大图像来说并不是真正的问题,因为如果您只有例如较小的32x32图像并尝试将其扩展到512x512

    由于您有一个简单的黑白图像,您可以将图像转换为svg,从svg中提取路径并使用region属性-fx-shape。这是默认JavaFX样式表定义形状的方式,例如检查复选框,以便它们可以平滑地跨大小进行缩放。请参阅JRE实施中modena.css内的文件jfxrt.jar,了解-fx-shape的定义和用法示例。

    这样的东西会为你的图像做到这一点(可以将CSS样式提取到CSS文件以便于维护):

    Pane tile = new Pane();
    tile.setStyle("" +
            "-fx-background-color: forestgreen;" +
            "-fx-shape: \"M2470 4886 c-83 -22 -144 -60 -220 -136 -41 -41 -91 -102 -111 -135\n" +
            "-121 -200 -2027 -3510 -2046 -3554 -55 -125 -68 -272 -33 -377 56 -162 205\n" +
            "-260 431 -284 127 -13 4011 -13 4138 0 226 24 375 122 431 284 35 105 22 252\n" +
            "-33 377 -32 72 -1994 3472 -2064 3577 -135 202 -320 295 -493 248z m223 -1182\n" +
            "c122 -43 210 -155 237 -301 12 -69 3 -156 -45 -428 -34 -196 -101 -636 -120\n" +
            "-785 -8 -69 -18 -144 -21 -168 l-5 -42 -177 2 -177 3 -8 75 c-20 203 -66 500\n" +
            "-158 1030 -38 218 -40 240 -29 305 15 94 47 161 107 221 58 58 126 94 197 104\n" +
            "61 9 147 2 199 -16z m30 -1989 c64 -32 144 -111 175 -172 49 -96 49 -231 0\n" +
            "-332 -29 -59 -102 -133 -165 -164 -275 -140 -595 91 -543 391 14 80 39 130 93\n" +
            "189 86 94 160 124 293 120 76 -2 99 -7 147 -32z\";"
    );
    tile.setScaleY(-1);
    tile.setPrefSize(64, 64);
    

    如果调整窗格大小,我不知道如何使窗格仅显示比例大小。所以我怀疑这对你有多大帮助。

    如果JavaFX直接支持SVG图像but it does not

    ,这会更简单

    一些布局创建和调试设备

    1. 要创建布局,请尝试使用Gluon SceneBuilder。即使您不使用FXML,也可以通过动态播放SceneBuilder中的UI元素和属性,并观察生成的FXML文件中所产生的变化等,来轻松学习。
    2. 要在运行时动态调试现有应用程序的布局,请使用FXExperience ScenicView
    3. 示例解决方案

      不同阶段尺寸的样本输出:

      withoutcss

      withcss

      这使用在关于调整JavaFX中图像大小的链接问题的答案中定义的ImageViewPane。

      ResizableImageSample.java

      import javafx.application.Application;
      import javafx.geometry.*;
      import javafx.scene.Scene;
      import javafx.scene.control.*;
      import javafx.scene.image.*;
      import javafx.scene.layout.HBox;
      import javafx.stage.Stage;
      
      public class ResizableImageSample extends Application {
          @Override
          public void start(Stage stage) {
              // Top section with image
              ImageView imageView = new ImageView(new Image(
                      "http://wfarm2.dataknet.com/static/resources/icons/set114/b4f29385.png"
              ));
              imageView.setPreserveRatio(true);
              ImageViewPane imagePane = new ImageViewPane(imageView);
              imagePane.getStyleClass().add("stack-pane");
      
              // Bottom Section with CSS toggle
              ToggleButton toggle = new ToggleButton("Show CSS");
              HBox hbox = new HBox(toggle);
              hbox.setAlignment(Pos.CENTER);
              hbox.getStyleClass().add("h-box");
      
              // Combines the two sections
              SplitPane root = new SplitPane(imagePane, hbox);
              root.setDividerPositions(0.8);
              root.setOrientation(Orientation.VERTICAL);
              SplitPane.setResizableWithParent(hbox, false);
              Scene scene = new Scene(root);
              toggle.setOnMouseClicked(e -> {
                  if (toggle.isSelected()) {
                      scene.getStylesheets().add(getClass().getResource("StyleSheet.css").toExternalForm());
                      toggle.setText("Disable CSS");
                  } else {
                      scene.getStylesheets().remove(0);
                      toggle.setText("Show CSS");
                  }
              });
      
              stage.setScene(scene);
              stage.show();
          }
      
          public static void main(String[] args) {
              launch(args);
          }
      }
      

      ImageViewPane.java

      /*
       * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
       * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       */
      import javafx.beans.property.ObjectProperty;
      import javafx.beans.property.SimpleObjectProperty;
      import javafx.geometry.HPos;
      import javafx.geometry.VPos;
      import javafx.scene.image.ImageView;
      import javafx.scene.layout.Region;
      
      /**
       *
       * @author akouznet
       */
      class ImageViewPane extends Region {
      
          private ObjectProperty<ImageView> imageViewProperty = new SimpleObjectProperty<>();
      
          public ObjectProperty<ImageView> imageViewProperty() {
              return imageViewProperty;
          }
      
          public ImageView getImageView() {
              return imageViewProperty.get();
          }
      
          public void setImageView(ImageView imageView) {
              this.imageViewProperty.set(imageView);
          }
      
          public ImageViewPane() {
              this(new ImageView());
          }
      
          @Override
          protected void layoutChildren() {
              ImageView imageView = imageViewProperty.get();
              if (imageView != null) {
                  if (imageView.isPreserveRatio()) {
                      if (getHeight() > getWidth()) {
                          imageView.setFitWidth(getWidth());
                          imageView.setFitHeight(0);
                      } else {
                          imageView.setFitWidth(0);
                          imageView.setFitHeight(getHeight());
                      }
                  } else {
                      imageView.setFitWidth(getWidth());
                      imageView.setFitHeight(getHeight());
                  }
      
                  layoutInArea(imageView, 0, 0, getWidth(), getHeight(), 0, HPos.CENTER, VPos.CENTER);
              }
              super.layoutChildren();
          }
      
          public ImageViewPane(ImageView imageView) {
              imageViewProperty.addListener((observable, oldIV, newIV) -> {
                  if (oldIV != null) {
                      getChildren().remove(oldIV);
                  }
                  if (newIV != null) {
                      getChildren().add(newIV);
                  }
              });
              this.imageViewProperty.set(imageView);
          }
      }