问题
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