簡體   English   中英

JavaFX ScrollPane 更新 viewportBoundsProperty

[英]JavaFX ScrollPane update of viewportBoundsProperty

我不明白 javaFX 的 ScrollPane 控件如何更新 viewportBounds 屬性:

  • 當拖放以平移屬性時更新
  • 當使用水平和垂直條或箭頭平移時,屬性不會更新。

是否有 javaFX 專家可以向我解釋 ScrollPane 的此屬性如何工作以及如何使用它來重新計算此滾動窗格的內容? (我有一個動態瓦片 map,如果可見,它就會被加載)。

這是說明此行為的最小示例:

public class DemoScrollPane extends javafx.application.Application {
    private Logger logger = LoggerFactory.getLogger(DemoScrollPane.class);


    public static void run(String[] args) {
        javafx.application.Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        // create content
        Pane pane = new Pane();
        Rectangle rectangle = new Rectangle(1000,1000, Color.AQUA);
        Rectangle rectangleInitialVP = new Rectangle(640,480, Color.GRAY);
        pane.getChildren().add(rectangle);
        pane.getChildren().add(rectangleInitialVP);

        // create scrollPane
        ScrollPane scrollPane = new ScrollPane();
        scrollPane.setContent(pane);
        // add click and drag interaction
        scrollPane.setPannable(true);

        // register a watcher on scrollPane viewportBoundsProperty
        InvalidationListener listener =
                    o -> {
                        logger.debug("viewport change {}",scrollPane.getViewportBounds());
                    };
        // This is triggered when moving using click and drag (pan)
        // and does correctly update the scrollPane viewportbounds.
        
        // BUT this is not trigered when moving by clocking on hbar or vbar or by using a touchpad (on mac) 
        // without clicking.
        // (even tough the viewport obviously change)
        scrollPane.viewportBoundsProperty().addListener(listener);

        // display the app
        Scene scene = new Scene(scrollPane, 640, 480);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    @Override
    public void stop() throws Exception {
        logger.debug("stop");
    }
}

這里還有一個視頻,展示了某些平移或交互如何不會生成 viewportBoundsProperty 更新以及單擊和拖動平移如何: video-of-scrollpane-no-update-of-viewportBoundsProperty

謝謝

感謝@James_D 的評論,這是一個解決問題的解決方案(見下文)。

如果有人對 JavaFX 的ScrollPane有更好的了解,請毫不猶豫地解釋兩者之間的區別

  • 更新ScrollPane的視口的單擊和滾動
  • vs 使用不會更新ScrollPane的視口的 H 和 V 條滾動。

以下是取自 James_D 評論的解決方案:

public class DemoScrollPane extends javafx.application.Application {
    private Logger logger = LoggerFactory.getLogger(DemoScrollPane.class);


    public static void run(String[] args) {
        javafx.application.Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        // create content
        Pane pane = new Pane();
        pane.setMaxSize(1000,1000);
        Rectangle rectangle = new Rectangle(1000,1000, Color.AQUA);
        Rectangle rectangleInitialVP = new Rectangle(640,480, Color.GRAY);
        pane.getChildren().add(rectangle);
        pane.getChildren().add(rectangleInitialVP);


        // create scrollPane
        ScrollPane scrollPane = new ScrollPane();
        scrollPane.setContent(pane);
        scrollPane.setVmin(0);
        scrollPane.setVmax(pane.getMaxHeight());
        scrollPane.setHmin(0);
        scrollPane.setHmax(pane.getMaxWidth());
        
        // add click and drag interaction
        scrollPane.setPannable(true);

        // register a watcher on scrollPane viewportBoundsProperty
        InvalidationListener listener =
                    o -> {
            Bounds movingViewPort = new BoundingBox(scrollPane.getHvalue(),
                    scrollPane.getVvalue(),
                    0.,
                    scrollPane.getViewportBounds().getWidth(),
                    scrollPane.getViewportBounds().getHeight(),
                    0.
                    );
                        logger.debug("viewport {}",movingViewPort);
                    };
        // This is triggered when moving using click and drag (pan)
        // and does correctly update the scrollPane viewportbounds.

        // BUT this is not trigered when moving by clocking on hbar or vbar or by using a touchpad (on mac)
        // without clicking.
        // (even tough the viewport obviously change)
        scrollPane.vvalueProperty().addListener(listener);
        scrollPane.hvalueProperty().addListener(listener);

        // display the app
        Scene scene = new Scene(scrollPane, 640, 480);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    @Override
    public void stop() throws Exception {
        logger.debug("stop");
    }
}

您關於差異的問題的簡單答案是::這就是它的實現方式!

首先,每次 scrollPane 嘗試布局其子項時,都會更新 viewPort 邊界。 ScrollPaneSkin class 中 layoutChildren 方法的潛行如下:

protected void layoutChildren(double x, double y, double w, double h) {
  ScrollPane control = (ScrollPane)this.getSkinnable();
  ... // All the stuff for layouting
  ...
  control.setViewportBounds(new BoundingBox(this.snapPosition(this.viewContent.getLayoutX()), this.snapPosition(this.viewContent.getLayoutY()), this.snapSize(this.contentWidth), this.snapSize(this.contentHeight)));
} 

現在,如果我們檢查 ScrollPane 的“可平移”屬性的相關實現,代碼如下(在 ScrollPaneSkin 類中):

this.viewRect.setOnDragDetected((e) -> {
    if (IS_TOUCH_SUPPORTED) {
        this.startSBReleasedAnimation();
    }

    if (((ScrollPane)this.getSkinnable()).isPannable()) {
        this.dragDetected = true;
        if (this.saveCursor == null) {
            this.saveCursor = ((ScrollPane)this.getSkinnable()).getCursor();
            if (this.saveCursor == null) {
                this.saveCursor = Cursor.DEFAULT;
            }

            ((ScrollPane)this.getSkinnable()).setCursor(Cursor.MOVE);
            ((ScrollPane)this.getSkinnable()).requestLayout(); // !! THIS LINE !!
        }
    }

});
this.viewRect.addEventFilter(MouseEvent.MOUSE_RELEASED, (e) -> {
    this.mouseDown = false;
    if (this.dragDetected) {
        if (this.saveCursor != null) {
            ((ScrollPane)this.getSkinnable()).setCursor(this.saveCursor);
            this.saveCursor = null;
            ((ScrollPane)this.getSkinnable()).requestLayout();// !! THIS LINE !!
        }

        this.dragDetected = false;
    }

    if ((this.posY > ((ScrollPane)this.getSkinnable()).getVmax() || this.posY < ((ScrollPane)this.getSkinnable()).getVmin() || this.posX > ((ScrollPane)this.getSkinnable()).getHmax() || this.posX < ((ScrollPane)this.getSkinnable()).getHmin()) && !this.touchDetected) {
        this.startContentsToViewport();
    }

});

從上面的代碼你可以注意到,當“pannable”屬性為真時,如果檢測到拖動,它會請求一個布局。 在下一個場景脈沖中,它將調用 layoutChildren 並更新 viewportBounds。 當您釋放鼠標時(如果檢測到拖動),也會發生同樣的事情。 這就是您只能在平移開始和結束時而不是在拖動時看到日志的原因。

現在來看hbar/vbar拖動的代碼,滾動條值更新時的代碼如下:(在ScrollPaneSkin類中)

InvalidationListener vsbListener = (valueModel) -> {
    if (!IS_TOUCH_SUPPORTED) {
        this.posY = Utils.clamp(((ScrollPane)this.getSkinnable()).getVmin(), this.vsb.getValue(), ((ScrollPane)this.getSkinnable()).getVmax());
    } else {
        this.posY = this.vsb.getValue();
    }

    this.updatePosY();
};
this.vsb.valueProperty().addListener(vsbListener);
InvalidationListener hsbListener = (valueModel) -> {
    if (!IS_TOUCH_SUPPORTED) {
        this.posX = Utils.clamp(((ScrollPane)this.getSkinnable()).getHmin(), this.hsb.getValue(), ((ScrollPane)this.getSkinnable()).getHmax());
    } else {
        this.posX = this.hsb.getValue();
    }

    this.updatePosX();
};
this.hsb.valueProperty().addListener(hsbListener);  

值監聽器只是更新 viewContent 的 layoutX/Y 屬性,而不是布局請求。 updatePosX 方法如下:

private double updatePosX() {
    ScrollPane sp = (ScrollPane)this.getSkinnable();
    double x = this.isReverseNodeOrientation() ? this.hsb.getMax() - (this.posX - this.hsb.getMin()) : this.posX;
    double minX = Math.min(-x / (this.hsb.getMax() - this.hsb.getMin()) * (this.nodeWidth - this.contentWidth), 0.0D);
    this.viewContent.setLayoutX(this.snapPosition(minX));
    if (!sp.hvalueProperty().isBound()) {
        sp.setHvalue(Utils.clamp(sp.getHmin(), this.posX, sp.getHmax()));
    }

    return this.posX;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM