Issue
I have a ComboBox I've populated with a long ObservableList so that it has a scrollbar. If I make a selection, say at the bottom of the list in the ComboBox, and then click the CLEAR button, I want the ComboBox to have no selection and the scrollbar to reset to the top. I can't seem to get the scrollbar back to the top. Can someone tell me what I'm doing wrong?
ComboBoxView.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="70.0" prefWidth="154.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="comboboxscroll.ComboBoxViewController">
<children>
<ComboBox fx:id="myCB" prefWidth="150.0" />
<Button layoutX="51.0" layoutY="35.0" mnemonicParsing="false" onAction="#clearCB" text="CLEAR" />
</children>
</AnchorPane>
ComboBoxViewController.java:
package comboboxscroll;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
public class ComboBoxViewController implements Initializable
{
@FXML
private ComboBox<String> myCB;
@Override
public void initialize(URL url, ResourceBundle rb)
{
ObservableList<String> myList = FXCollections.observableArrayList("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P");
myCB.getItems().clear();
myCB.setItems(myList);
}
@FXML
private void clearCB(ActionEvent event)
{
myCB.getSelectionModel().clearSelection();
// I thought this would position the scrollbar back to the top but it doesn't
myCB.setValue(null);
}
}
ComboBoxScroll.java:
package comboboxscroll;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class ComboBoxScroll extends Application {
@Override
public void start(Stage stage) throws Exception
{
try
{
FXMLLoader loader = new FXMLLoader(getClass().getResource("ComboBoxView.fxml"));
Scene scene = new Scene((Parent) loader.load());
stage.setScene(scene);
stage.setTitle("ComboBox Scroll Test");
stage.show();
}
catch (IOException ignored)
{
}
}
public static void main(String[] args)
{
launch(args);
}
}
Solution
The ComboBox itself has not api to scroll its list, but the ListView that's showing in the popup does (though by default, it does nothing to scroll the selected item into the visible region): basically you have to use it like
listView.scrollTo(comboBox.getSelectionModel().getSelectedIndex())
A good location to do so is a onShowing handler in the comboBox - at that time, the listView is guaranteed to be instantiated (which is done in the comboBox' skin) and accessible (via the comboBoxSkin).
A small example to play with:
public class ComboBoxScroll extends Application {
int count;
private Parent createContent() {
List<String> data = Stream.generate(() -> "item " + count++).limit(30).collect(toList());
ComboBox<String> combo = new ComboBox<>(observableArrayList(data));
// scroll just before the comboBox is showing
combo.setOnShowing(e -> {
// Beware: type of skin is an implementation detail!
ListView list = (ListView) ((ComboBoxListViewSkin) combo.getSkin()).getPopupContent();
list.scrollTo(Math.max(0, combo.getSelectionModel().getSelectedIndex()));
});
// to see the handler working initially
combo.getSelectionModel().select(25);
// a button to clear see the handler working on clearing selection
Button clear = new Button("clear selection");
clear.setOnAction(e -> combo.getSelectionModel().clearSelection());
BorderPane content = new BorderPane(combo);
content.setBottom(new HBox(10, clear));
return content;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
//stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Answered By - kleopatra
Answer Checked By - David Goodson (JavaFixing Volunteer)