ScrollPane переходит наверх при удалении узлов
У меня есть ScrollPane
содержащий 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);
}
}
2 ответа
ScrollPane
удостоверяется, что сфокусировано Node
отображается внутри области просмотра. Button
это щелкнуло имеет фокус. Это означает, что сфокусированный узел удален, а JavaFX пытается переместить фокус на другой узел в ScrollPane
контент, который в данном случае является первым Button
, ScrollPane
прокручивает этот узел в поле зрения, что объясняет наблюдаемое поведение.
Решением будет установка focusTraversable
собственность каждого Button
в false
,
Другой вариант, который все еще позволит вам изменить фокус с помощью Tab, будет переопределять onTraverse
метод ScrollPaneSkin
, поскольку этот метод отвечает за это поведение:
scroller.setSkin(new ScrollPaneSkin(scroller) {
@Override
public void onTraverse(Node n, Bounds b) {
}
});
Кажется, проблема вызвана возвращением фокуса к кнопке в первом элементе панели листов. Один из обходных путей:
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);
}
Этот ответ основан на ответе Фабиана. в некоторых чертах JavaFx нет onTraverse
метод на ScrollPaneSkin
(см. javafx.scene.control.skin.ScrollPaneSkin против 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 необходим в случае удаления и добавления одного и того же узла несколько раз, так что будет только один слушатель