ScrollPane 删除节点时滚动到顶部

3
我有一个包含图像的TilePane滚动面板,当我删除其中一张图片时,ScrollPane会跳回到顶部,这在删除多个图像时非常烦人。是否有方法可以控制滚动行为?
我在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);
    }
}

听起来有点奇怪...你能否[编辑]你的问题,包括一个[MCVE]以展示问题? - James_D
我编辑了一个小例子。 - Simoon
3个回答

5

ScrollPane 确保焦点所在的 Node 显示在视口内。被点击的 Button 获得了焦点。这意味着获得焦点的节点被移除,JavaFX 尝试将焦点移动到 ScrollPane 内的另一个节点,此处为第一个 ButtonScrollPane 滚动此节点到视图中,这就解释了观察到的行为。

一种解决方法是将每个 ButtonfocusTraversable 属性设置为 false

另一个选项仍允许您通过 Tab 更改焦点,这是重写 ScrollPaneSkinonTraverse 方法,因为这是负责此行为的方法:

scroller.setSkin(new ScrollPaneSkin(scroller) {

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

});

由于不打算通过选项卡浏览按钮,因此您的解决方案甚至更加简单。谢谢! - Simoon

3
问题似乎是由于焦点返回到平铺面板的第一个元素中的按钮所导致的。一种解决方法是:
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);
}

1
那就是问题所在。谢谢! - Simoon

0

这个答案基于Fabian的回答。在一些JavaFx实现中,ScrollPaneSkin上没有onTraverse方法(参见javafx.scene.control.skin.ScrollPaneSkin vs javafx.scene.control.skin.ScrollPaneSkin),因此它不能被@Override,所以我为一个通用解决方案工作,它不需要程序员更改每个按钮,但它可以获取根并设置FocusTravers= false,无论将哪个Node(在此实现中为Button)添加到视图树中。

这个辅助类:

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

使用方法如下:

  FocusTraversHelper.cancelFocusTravers(root);  

希望能对某些人有所帮助

注意:如果有另一个类似于TextField的控件,则应进行修改以使其正常工作(可能使用Node代替Button)。

注意:在多次删除和添加相同节点的情况下,需要进行remove->add操作,以便只有一个监听器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接