【问题标题】:Keep or set JavaFX Scrollpane position when content resized调整内容大小时保留或设置 JavaFX Scrollpane 位置
【发布时间】:2018-11-20 04:16:23
【问题描述】:

我想设置 Scrollpane H 和 V 值,但在调整内部内容大小后。

一些上下文 - 我得到以下结构:

我将.setOnScroll(...)(鼠标)事件附加到 StackPane,当它发生时,内部内容的大小(图像)被调整为某些值(也称为缩放)。我想补充一点,StackPane min size 绑定到滚动窗格视口大小,Pane size 绑定到图像大小,以保持一切正常工作。

到目前为止,一切正常,当内容更改大小时,滚动条会更新,并且一切都会按预期进行调整,但我想保留或将滚动条位置设置为某些值以保持缩放到上一个中间点(类似于放大 Photoshop)。在这种情况下,我在回调中添加了这一行来测试它:

scrollPane.setHvalue(0.5);

该函数本身可以工作,但稍后会被覆盖(可能在重新计算布局时),这样总是错误的。我通过事件测试了值

scrollPane.hvalueProperty().addListener((observable, oldvalue, newvalue) -> {
     System.out.println(newvalue);
});

输出有点像

0.5
0.45172283226050736
0.5
0.45196805476326296
0.5
0.4522703818369453

// or this when scaled down 

0.5
0.5591296121097445
0.5
0.5608324439701174

这就是为什么我的结论是我成功设置了0.5,但后来的布局可能为调整大小的内容设置了不同的值,因为我将大小限制更改为PaneStackPane

我的问题然后我应该怎么做?

是否有任何 onResize 事件来纠正或强制我自己的值?或其他方式来安排滚动事件或在重新计算布局时完成我的更新?

// 编辑

我清理了代码,这里是示例:

Main.java

package application;

import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.fxml.FXMLLoader;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            BorderPane root = (BorderPane)FXMLLoader.load(getClass().getResource("Sample.fxml"));
            Scene scene = new Scene(root,800,600);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

application.css为空

/* JavaFX CSS - Leave this comment until you have at least create one rule which uses -fx-Property */

Sample.fxml(替换图片网址)

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.StackPane?>

<BorderPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.SampleController">
   <center>
      <ScrollPane fx:id="scrollPane" prefHeight="600.0" prefWidth="800.0" BorderPane.alignment="CENTER">
         <content>
            <StackPane fx:id="stackPane">
               <children>
                  <Pane fx:id="clipContainer">
                     <children>
                        <ImageView fx:id="img" pickOnBounds="true" preserveRatio="true">
                           <image>
                              <Image url="@../../../../Desktop/sample.jpg" />
                           </image>
                        </ImageView>
                     </children>
                  </Pane>
               </children>
            </StackPane>
         </content>
      </ScrollPane>
   </center>
</BorderPane>

SampleController.java

package application;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;


public class SampleController implements Initializable {


    @FXML
    private Pane clipContainer;

    @FXML
    private StackPane stackPane;

    @FXML
    private ImageView img;

    @FXML
    private ScrollPane scrollPane;

    final double SCALE_DELTA = 1.1;

    private double imgW;
    private double imgH;
    private double scale = 1;


    @Override
    public void initialize(URL location, ResourceBundle resources) {


        clipContainer.setStyle("-fx-background-color: #999999");
        stackPane.setStyle("-fx-background-color: #CCCCCC");

        imgW = img.getImage().getWidth();
        imgH = img.getImage().getHeight();

        // bind max and min to adjust to new image bounds

        stackPane.minWidthProperty().bind(Bindings.createDoubleBinding(() -> 
            scrollPane.getViewportBounds().getWidth(), scrollPane.viewportBoundsProperty()
        ));
        stackPane.minHeightProperty().bind(Bindings.createDoubleBinding(() -> 
            scrollPane.getViewportBounds().getHeight(), scrollPane.viewportBoundsProperty()
        ));

        clipContainer.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> 
            img.getFitWidth(), img.fitWidthProperty()
        ));
        clipContainer.maxHeightProperty().bind(Bindings.createDoubleBinding(() -> 
            img.getFitHeight(), img.fitHeightProperty()
        ));

        // initial scale
        img.setFitWidth(imgW * 1);
        img.setFitHeight(imgH * 1);

        // on mouse scroll
        stackPane.setOnScroll(event -> {
            event.consume();

            if (event.getDeltaY() == 0) {
              return;
            }

            double scaleFactor =
              (event.getDeltaY() > 0)
                ? SCALE_DELTA
                : 1/SCALE_DELTA;


            scale *= scaleFactor;

            img.setFitWidth(imgW * scale);
            img.setFitHeight(imgH * scale);

            // HERE i want to do something to keep my image where it was
            scrollPane.setHvalue(0.5);

        });

        scrollPane.hvalueProperty().addListener((observable, oldvalue, newvalue) -> {
            System.out.println(newvalue);
        });

    }
}

【问题讨论】:

  • 你为什么还要使用ScrollPane?使用ScrollPane 调整大小而不是向上/向下/向左/向右滚动对我来说听起来很糟糕。 setOnScroll() 可以在任何节点上使用。
  • @Jai 我不使用 ScrollPane 来调整大小,而是使用鼠标滚动事件来调整内部图像的大小。滚动窗格用于导航缩放的图像 - 这与 Photoshop 中的缩放画布完全相同,您可以通过滚动条进行导航。我遇到的问题是我想在缩放之前将图像保持在适当位置,这就是为什么我尝试将滚动条设置到某个位置但无论我设置什么都会在稍后更改,可能在布局意识到内部内容更改大小和滚动条最大、最小和我相信价值必须更新。
  • 请提供一个minimal reproducible example 来说明问题。
  • @kleopatra 对不起,我以前没有这样做,代码一团糟,我不得不清理很多。我认为这是一个相当简单的问题,并且很容易解决它 - 我的错。我编辑了问题并添加了示例代码,如果可以,请检查一下,谢谢。

标签: java javafx layout scrollbar


【解决方案1】:

虽然我不熟悉 JavaFX,但有人会认为您可以“强制自己的值”,只要滚动条边界发生变化,就可以调整其大小。因此,请改为在 ChangeListener 回调中执行 scrollPane.setHvalue(0.5)

scrollPane.hvalueProperty().addListener((DoubleProperty observable, double oldvalue, double newvalue) -> {

    if (newvalue != 0.5) {
        scrollPane.setHvalue(0.5);
    }

});

【讨论】:

  • 我不确定这是否是个好主意。这不会造成一些问题吗?调用 setHValue 将触发另一个事件 + 回调属性描述为 In general is is considered bad practice to modify the observed value in this method.
  • @Griva 这就是为什么我在强迫你自己的价值中加粗并引用你的话。我同意,当 API 希望您以另一种方式执行任何操作时,强制执行任何操作不被认为是好的做法,但出于您的目的,“强制”是唯一的解决方案。 JavaFX UI 系统试图以一种方式排列 UI,而您正试图破解这种排列并手动创建自己的排列。您唯一的选择是实现您自己的 ScrollPane,直​​至覆盖您的更改的类。
  • @Griva 查看我的编辑以确保setHvalue不会自行循环。
【解决方案2】:

大约一年半前有人问过这个问题,但对于仍在寻找答案的人来说——布局只覆盖了一次 hvalue 和 vvalue 属性。这意味着,而不是设置直接设置值,您可以附加一个侦听器,该侦听器在否决更改值的尝试后自行处理。

也就是说,给定自动注销侦听器的包装类..

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;

public class ChangeOnceListener<T> implements ChangeListener<T> {

    private ChangeListener<T> wrapped;

    public ChangeOnceListener(ChangeListener<T> wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) {
        observable.removeListener(this);
        wrapped.changed(observable, oldValue, newValue);
    }

}

.. OP 的场景可以通过以下方式修复。

        pane.setOnScroll(e -> {
            pane.hvalueProperty().addListener(new ChangeOnceListener<>((obv, old, neww) -> {
                pane.setHvalue(0.5);
            }));

            pane.vvalueProperty().addListener(new ChangeOnceListener<>((obv, old, neww) -> {
                pane.setVvalue(0.5);
            }));
        });

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-19
    • 2012-06-22
    相关资源
    最近更新 更多