问题
I can listen for scroll events:
tableView.addEventFilter(javafx.scene.input.ScrollEvent.SCROLL,
new EventHandler<javafx.scene.input.ScrollEvent>() {
@Override
public void handle(final javafx.scene.input.ScrollEvent scrollEvent) {
System.out.println("Scrolled.");
}
});
But How can I be notified if the bottom/ top of the table is reached?
回答1:
A simple way of doing this is to retrieve the ScrollBar
using a lookup
:
ScrollBar tvScrollBar = (ScrollBar) tableView.lookup(".scroll-bar:vertical");
You can then add a listener to check if it's reached the bottom (the valueProperty
of the ScrollBar
is represented by the percentage it's been scrolled, so 0.0
is the top and 1.0
is the bottom):
tvScrollBar.valueProperty().addListener((observable, oldValue, newValue) -> {
if ((Double) newValue == 1.0) {
System.out.println("Bottom!");
}
});
Below is a simple MCVE that demonstrates how to put it all together:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ScrollBarNotify extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// Simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
// Simple TableView to demonstrate
TableView<String> tableView = new TableView<>();
TableColumn<String, String> column = new TableColumn<>("Text");
column.setCellValueFactory(f -> new SimpleStringProperty(f.getValue()));
tableView.getColumns().add(column);
// Add some sample items to our TableView
for (int i = 0; i < 100; i++) {
tableView.getItems().add("Item #" + i);
}
// Now, let's add a listener to the TableView's scrollbar. We can only access the ScrollBar after the Scene is
// rendered, so we need to do schedule this to run later.
Platform.runLater(() -> {
ScrollBar tvScrollBar = (ScrollBar) tableView.lookup(".scroll-bar:vertical");
tvScrollBar.valueProperty().addListener((observable, oldValue, newValue) -> {
if ((Double) newValue == 1.0) {
System.out.println("Bottom!");
}
});
});
// Finally, add the TableViewto our layout
root.getChildren().add(tableView);
// Show the Stage
primaryStage.setWidth(300);
primaryStage.setHeight(300);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
回答2:
If you're using Java 10+ you can subclass TableViewSkin
and get access to the VirtualFlow. The latter class has the position
property which you can use to know if the top or bottom has been reached.
Here's an example using custom events:
MyEvent.java
import javafx.event.Event;
import javafx.event.EventType;
public class MyEvent extends Event {
public static final EventType<MyEvent> ANY = new EventType<>(Event.ANY, "MY_EVENT");
public static final EventType<MyEvent> TOP_REACHED = new EventType<>(ANY, "TOP_REACHED");
public static final EventType<MyEvent> BOTTOM_REACHED = new EventType<>(ANY, "BOTTOM_REACHED");
public MyEvent(EventType<? extends MyEvent> eventType) {
super(eventType);
}
}
MyTableViewSkin.java
import javafx.scene.control.TableView;
import javafx.scene.control.skin.TableViewSkin;
public class MyTableViewSkin<T> extends TableViewSkin<T> {
public MyTableViewSkin(TableView<T> control) {
super(control);
getVirtualFlow().positionProperty().addListener((obs, oldVal, newVal) -> {
if (newVal.doubleValue() == 0.0) {
control.fireEvent(new MyEvent(MyEvent.TOP_REACHED));
} else if (newVal.doubleValue() == 1.0) {
control.fireEvent(new MyEvent(MyEvent.BOTTOM_REACHED));
}
});
}
}
App.java
import javafx.application.Application;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage primaryStage) {
var table = new TableView<Integer>();
for (int i = 0; i < 250; i++) {
table.getItems().add(i);
}
var column = new TableColumn<Integer, Number>("Value");
column.setCellValueFactory(features -> new SimpleIntegerProperty(features.getValue()));
table.getColumns().add(column);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setSkin(new MyTableViewSkin<>(table));
table.addEventHandler(MyEvent.ANY, event -> System.out.printf("%s%n", event.getEventType()));
primaryStage.setScene(new Scene(table, 500, 300));
primaryStage.setTitle("Example");
primaryStage.show();
}
}
In this example I manually call table.setSkin
. Another option is to subclass TableView
and override createDefaultSkin
which returns the skin you want to use.
回答3:
tableView.addEventFilter(javafx.scene.input.ScrollEvent.SCROLL,
new EventHandler<javafx.scene.input.ScrollEvent>() {
@Override
public void handle(final javafx.scene.input.ScrollEvent scrollEvent) {
Object virtualFlow = ((javafx.scene.control.SkinBase<?>) tableView.getSkin()).getChildren().get(1);
double position = -1;
try {
position = (double) virtualFlow.getClass().getMethod("getPosition").invoke(virtualFlow);
} catch (Exception ignored) { }
if(position == 0.0) {
System.out.println("scrolled to top!");
}
else if(position == 1.0) {
System.out.println("scrolled to bottom!");
}
}
});
来源:https://stackoverflow.com/questions/53991221/tableview-get-notified-when-scroll-reaches-bottom-top-of-table