SirWinning
SirWinning

Reputation: 111

JavaFX update a ComboBox item list to disable some items based on a changeable input

I am working on an interface for a booking system. In a window, I need to get the start time of the booking and the end time of the booking desired, in order to check the db if that slot is available. (only hh:mm interested, i get the day somewhere else). The combobox looks like this:

//startTime HBox
    HBox startTime = new HBox();
    startTime.setSpacing(5);
    final ComboBox startHours = new ComboBox(hours);
    final ComboBox startMinutes = new ComboBox(minutes);
    final Label colon = new Label(":");
    startTime.getChildren().addAll(startHours,colon,startMinutes);

It is the same for endTime(an HBox with same components just changed variable names

I want my startTime hour combobox to automatically disable those items that are higher than the current endTime combobox hour, and vice-versa I want endTime hour items that are lower than the startTime hour combobox to be disabled. I have tried to make a cell Factory, however it doesn't work if I opened the combobox before editing the other combobox.

startHours.setCellFactory(
    new Callback<ListView<String>, ListCell<String>>() {
            @Override 
            public ListCell<String> call(ListView<String> param) {
                final ListCell<String> cell = new ListCell<String>() {

                    @Override public void updateItem(String item, 
                        boolean empty) {
                            super.updateItem(item, empty);
                            if (item!=null){
                                setText(item);
                            }
                            if ((endHours.getValue()!=null) && (endHours.getValue().toString().compareTo(item)<0)){
                                setDisable(true);
                                setStyle("-fx-background-color: #ffc0cb");
                            }
                        }
            };
            return cell;
        }
    });

Upvotes: 0

Views: 1435

Answers (2)

Matthew Wright
Matthew Wright

Reputation: 1525

Not as complicated but doesn't disable items in the ComboBox would be to leave the startHours alone and use the chosen value to determine what's shown in endHours. When startHours value is changed, it will populate endHours with all hours from start to 23. If endHours is chosen already and you change startHours to a value higher, it won't show anything and have to be reselected. The values in startHours shouldn't matter to be changed, just the ones in endHours.

ComboBox<Integer> start = new ComboBox<Integer>();
ComboBox<Integer> end = new ComboBox<Integer>();
for(int i : IntStream.range(1, 24).toArray()) {
    start.getItems().add(i);
    end.getItems().add(i);
}
start.setOnAction(ae -> {
    Integer selected = end.getSelectionModel().getSelectedItem();
    end.getItems().clear();
    for(int i : IntStream.range(start.getSelectionModel().getSelectedItem(), 24).toArray()) {
        end.getItems().add(i);
    }
    if(end.getItems().contains(selected)) {
        end.getSelectionModel().select(selected);
    }
});

Upvotes: 0

James_D
James_D

Reputation: 209330

Your cell needs to observe the value in the other combo box, so that it knows to update its disabled state if the value there changes.

Note you also have other bugs in the cell implementation: you must account for all possibilities in your updateItem() method: e.g. you don't properly deal with the item being null (the cell being empty), or setting the disable state back to false when needed. Finally, using strings as the data type here is both inconvenient (you presumably have to convert back to ints at some point to use these values) and messes up the logic (because "10" is less than "2", for example). You should use ComboBox<Integer> here.

Here's an implementation with just two "hours" combo boxes that works:

import java.util.function.BiPredicate;

import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;


public class DependentComboBoxes extends Application {

    private ComboBox<Integer> startHours ;
    private ComboBox<Integer> endHours ;

    @Override
    public void start(Stage primaryStage) {
        startHours = new ComboBox<>();
        endHours = new ComboBox<>();
        startHours.setCellFactory(lv -> new StartHoursCell());
        endHours.setCellFactory(lv -> new EndHoursCell());
        for (int i = 0; i < 24 ; i++) {
            startHours.getItems().add(i);
            endHours.getItems().add(i);
        }

        GridPane root = new GridPane();
        root.setHgap(5);
        root.setVgap(5);
        root.addRow(0, new Label("Start hours:"), startHours);
        root.addRow(1, new Label("End hours:"), endHours);

        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(20));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

    private class StartHoursCell extends ListCell<Integer> {

        StartHoursCell() {
            endHours.valueProperty().addListener((obs, oldEndHours, newEndHours) -> updateDisableState());
        }

        @Override
        protected void updateItem(Integer hours, boolean empty) {
            super.updateItem(hours, empty);
            if (empty) {
                setText(null);
            } else {
                setText(hours.toString());
                updateDisableState();
            }
        }

        private void updateDisableState() {
            boolean disable = getItem() != null && endHours.getValue() != null && 
                    getItem().intValue() > endHours.getValue();
            setDisable(disable) ;
            setOpacity(disable ? 0.5 : 1);
        }
    }

    private class EndHoursCell extends ListCell<Integer> {

        EndHoursCell() {
            startHours.valueProperty().addListener((obs, oldEndHours, newEndHours) -> updateDisableState());
        }

        @Override
        protected void updateItem(Integer hours, boolean empty) {
            super.updateItem(hours, empty);
            if (empty) {
                setText(null);
            } else {
                setText(hours.toString());
                updateDisableState();
            }
        }

        private void updateDisableState() {
            boolean disable = getItem() != null && startHours.getValue() != null && 
                    getItem().intValue() < startHours.getValue();
            setDisable(disable) ;
            setOpacity(disable ? 0.5 : 1);

        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Upvotes: 1

Related Questions