ScrollPane jumps to top when deleting nodes

旧城冷巷雨未停 提交于 2019-12-12 19:04:01

问题


I have an ScrollPane containing a TilePane which shows images. Whenever I delete one of those images the ScrollPane jumps back to the top which is quite annoying when trying to remove multiple images. Is there a way to control the scrolling behaviour?

I'm running this code on Windows 7:

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;

public class ScrollbarProblem extends Application{
    private TilePane imagePane;
    private ScrollPane scroller;

    @Override
    public void start(Stage primaryStage) {
        imagePane = new TilePane();
        scroller = new ScrollPane();
        scroller.setContent(imagePane);
        BorderPane root = new BorderPane(scroller, null, null, null, null);
        Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();

        for(int i = 0; i < 20; i++){
            Image img = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/SIPI_Jelly_Beans_4.1.07.tiff/lossy-page1-220px-SIPI_Jelly_Beans_4.1.07.tiff.jpg");
            final ImageView imageView = new ImageView(img);

            final Button button = new Button("Delete");
            final StackPane stackPane = new StackPane();

            button.setOnAction(new EventHandler<ActionEvent>() {

                @Override
                public void handle(ActionEvent event) {
                    imagePane.getChildren().remove(stackPane);
                }
            });

            stackPane.getChildren().addAll(imageView, button);
            imagePane.getChildren().add(stackPane);
        }
    }

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

回答1:


ScrollPane makes sure the focused Node is shown inside the viewport. A Button that is clicked has the focus. This means the focused node is removed and JavaFX tries to move the focus to another node in the ScrollPane content, which in this case is the first Button. ScrollPane scrolls this node into view, which explains the observed behavior.

A solution would be setting the focusTraversable property of every Button to false.

Another option which would still allow you to change the focus via Tab would be overriding the onTraverse method of the ScrollPaneSkin, since this is the method responsible for this behavior:

scroller.setSkin(new ScrollPaneSkin(scroller) {

    @Override
    public void onTraverse(Node n, Bounds b) {
    }

});



回答2:


The problem seems to be caused by the focus reverting to the button in the first element of the tile pane. One workaround is:

Image img = new Image(
        "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/SIPI_Jelly_Beans_4.1.07.tiff/lossy-page1-220px-SIPI_Jelly_Beans_4.1.07.tiff.jpg");

for (int i = 0; i < 20; i++) {
    final ImageView imageView = new ImageView(img);

    final Button button = new Button("Delete");
    final StackPane stackPane = new StackPane();


    button.setOnAction(new EventHandler<ActionEvent>() {

        @Override
        public void handle(ActionEvent event) {

            int index = imagePane.getChildren().indexOf(stackPane);

            imagePane.getChildren().remove(index);
            int numPanes = imagePane.getChildren().size();
            if (numPanes > 0) {
                imagePane.getChildren().get(Math.min(index, numPanes-1)).requestFocus();
            }
        }
    });

    stackPane.getChildren().addAll(imageView, button);
    imagePane.getChildren().add(stackPane);
}



回答3:


this answer is base on Fabian answer. in some JavaFx imp there is no onTraverse method on ScrollPaneSkin(see javafx.scene.control.skin.ScrollPaneSkin vs javafx.scene.control.skin.ScrollPaneSkin) so it can't be @Override, so I work for a general solution that will not include the need for every button change by the programmer, but it can get the root and set the FocusTravers= false whatever Node(Button in this imp) is added to the view tree.

This helper class:

public class FocusTraversHelper {

    static private ListChangeListener listChangeListener;
    static {
        listChangeListener=new ListChangeListener<Node>() {
            @Override
            public void onChanged(Change<? extends Node> c) {
                if(c.next()&&c.wasAdded()){
                    Node b =c.getAddedSubList().get(0);
                    if(b!=null&&(b instanceof Parent || b instanceof Button) ){
                        cancelFocusTravers(b);
                    }
                }

            }};
    }
    static private void registerAddEvent(Parent parent){
        parent.getChildrenUnmodifiable().removeListener(listChangeListener);
        parent.getChildrenUnmodifiable().addListener(listChangeListener);
    }
    static public void cancelFocusTravers(Node node){
        if(node instanceof Parent){
            registerAddEvent((Parent)node);
            for (Node n: ((Parent) node).getChildrenUnmodifiable()) {
                cancelFocusTravers(n);
            }
        }
        if(node instanceof Button){
            node.setFocusTraversable(false);
        }
    }
}

and the use is like:

  FocusTraversHelper.cancelFocusTravers(root);  

Hope it will help someone

Note: if there is another control like TextField it should be modified in order to work(maybe use Node instead of Button).

Note: the remove->add needed in case of remove and add the same node multiple times, so there will be only one listener



来源:https://stackoverflow.com/questions/40983147/scrollpane-jumps-to-top-when-deleting-nodes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!