简体   繁体   中英

Keep or set JavaFX Scrollpane position when content resized

I want to set Scrollpane H and V values but after inner content was resized.

Some context - I got following structure:

在此处输入图片说明

I attached .setOnScroll(...) (mouse) event to StackPane and when it happens, the inner content is resized (image) to some values (aka zoom). I want to add that StackPane min size is binded to scrollpane viewport size and Pane size binded to image size to keep everything working properly.

Up to this point everything works fine, when content changes size, scrollbars are updated and everything adjust as it supossed to be but I want to keep or rather set scrollbars positions to some values to keep zooming to previous middle point (something like zoom in photoshop). In this case I added this line in my callback to test it:

scrollPane.setHvalue(0.5);

The function itself works but it's overridden later (probably when layout recalculate) and this way it's always wrong. I tested values via event

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

and the output is kind of

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

That's why my conclusion is that I succeed setting 0.5 but later layout probably set different value for resized content becuase i changed size restrictions to Pane and StackPane .

My question then how should I do this?

Is there any onResize event to corrent or force my own value? or other way to schedule scroll event or my updates to be done when layout is recalculated?

// EDIT

I cleaned the code and here is example:

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 is empty

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

Sample.fxml (replace image url)

<?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>

and 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);
        });

    }
}

While I am unfamiliar with JavaFX, one would think that you could "force your own value" by resizing the scrollbar bounds whenever it changes. So, execute scrollPane.setHvalue(0.5) within the ChangeListener callback instead:

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

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

});

This was asked about a year and a half ago, but for anyone still looking for an answer to this -- the layout overrides the hvalue and vvalue properties exactly once. That means, instead of setting the values directly, you can attach a listener that disposes of itself after veto'ing an attempt to change the value.

That is, given the wrapping class that automagically unregisters a listener..

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's scenario could be fixed with something like the following.

        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);
            }));
        });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM