如何让ComboBox CellFactory动态地正确禁用项目?

时间:2017-04-24 13:56:02

标签: java javafx combobox

我有几个ComboBox用于为约会调度程序应用程序选择日期和时间,但是当我自定义CellFactory时,它只会在您第一次单击时正确禁用正确的项目扩大。基本上它应该禁用允许您选择LocalDateTime.now()之前的日期和时间的选择。以下是显示不稳定行为的视频: https://www.youtube.com/watch?v=niGoIvVx9Y0

以下是我的手机工厂的代码:

startHour.setCellFactory((final ListView<String> param) -> {
        return new ComboBoxListCell<String>() {
            @Override
            public void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);
                if(empty || item == null) {
                    setText(null);
                    setGraphic(null);
                } else {
                    LocalDateTime selected = createDateTime(startDate.getValue(), item,
                            startMinute.getValue(), startMeridian.getValue());
                    if(selected.isBefore(LocalDateTime.now())) {
                        System.out.print(item + ", ");
                        setDisable(true);
                        setStyle(COMBOCELL_DISABLED_COLOR);
                    }
                }
            }
        };
    });

    startMinute.setCellFactory((final ListView<String> listView) -> {
        return new ComboBoxListCell<String>() {
            @Override
            public void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);
                if(empty || item == null) {
                    setText(null);
                    setGraphic(null);
                } else {
                    LocalDateTime selected = createDateTime(startDate.getValue(), startHour.getValue(),
                            item, startMeridian.getValue());
                    if(selected.isBefore(LocalDateTime.now())) {
                        setDisable(true);
                        setStyle(COMBOCELL_DISABLED_COLOR);
                    }
                }
            }
        };
    });

    startMeridian.setCellFactory((final ListView<String> listView) -> {
        return new ComboBoxListCell<String>() {
            @Override public void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);
                if(empty || item == null) {
                    setText(null);
                    setGraphic(null);
                } else {
                    LocalDateTime selected = createDateTime(startDate.getValue(), startHour.getValue(),
                            startMinute.getValue(), item);
                    if(selected.isBefore(LocalDateTime.now())) {
                        setDisable(true);
                        setStyle(COMBOCELL_DISABLED_COLOR);
                    }
                }
            }
        };
    });

使用此方法计算选定的LocalDateTime

private LocalDateTime createDateTime(LocalDate date, String hour, String minute, String meridian) {
    String dateTimeStr = date + " " + hour + " "  + minute + " " + meridian;
    return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd h m a"));
}

startHour#setCellFactory中我插入了println语句,显示所选项目在LocalDateTime之前发生转换为LocalDateTime.now(),这是我得到的输出:

  

1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,12,1,1,2,3,4 ,5,6,7,8,9,12,12,12,1,9,12,1,9,12,2,1,7,9,

当前时间是上午9:48,所选时间是上午10:00,因此似乎符合预期条件。但是,正如视频所示,第一次扩展ComboBox后,行为非常不稳定。我有DatePicker节点的类似单元工厂,它们工作得很好。 我误解了ComboBox#setCellFactory打算如何使用?

1 个答案:

答案 0 :(得分:1)

对于startHour,当单元格的当前项目,所选开始日期,所选开始分钟或所选子午线的任何更改时,您需要单元格的禁用状态(可能)更新。你可以用绑定来做到这一点。

(请注意,您使用的是错误的单元格类。ComboBoxListCell用于在列表中显示可编辑单元格,该列表使用组合框作为编辑器。您只需要在此处使用普通ListCell。 )

startHour.setCellFactory((final ListView<String> param) -> {
    ListCell<String> cell = new ListCell<String>() {
        @Override
        public void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
            setText(item); // works in empty case as item will be null, as desired
        }
    };

    cell.disableProperty().bind(Bindings.createBooleanBinding(
        () -> {
            if (cell.getItem() == null) return false ;
            LocalDateTime selected = createDateTime(startDate.getValue(), cell.getItem(),
                        startMinute.getValue(), startMeridian.getValue());
            return selected.isBefore(LocalDateTime.now());
        }, 
        cell.itemProperty(), 
        startMinute.valueProperty(), 
        startMeridian.valueProperty(), 
        startDate.valueProperty()
    ));

    return cell ;
});

其他细胞工厂将是类似的。

对于样式,我强烈建议您使用外部样式表,其规则如下:

.combo-box .list-cell:disabled {
    /* style for disabled cell here */
}

这使代码更简单,并且意味着只有一个地方可以为所有相关的禁用组合框单元格定义样式。

请注意,一旦显示的单元格表示当前时间之后的时间(因此已启用),如果稍后显示的单元格不再在当前时间之后,则不会自动禁用。 (例如,如果2017年4月24日下午1点的单元格首次显示在当天下午12:55,它将被启用。如果10分钟后再次显示,则没有其他UI更改导致updateItem()要被调用,它应该被禁用,但不会被禁用。)一个完整的解决方案会让绑定也随着它的变化观察“当前时间”,你可以这样做:

ObjectProperty<LocalDateTime> clock = new SimpleObjectProperty<>(LocalDateTime.now());
Timeline clockwork = new Timeline(new KeyFrame(Duration.seconds(1), 
        e -> clock.set(LocalDateTime.now())));
clockwork.setCycleCount(Animation.INDEFINITE);
clockwork.play();

然后将clock添加到绑定中的相关值列表中:

cell.disableProperty().bind(Bindings.createBooleanBinding(
    () -> {
        if (cell.getItem() == null) return false ;
        LocalDateTime selected = createDateTime(startDate.getValue(), cell.getItem(),
                    startMinute.getValue(), startMeridian.getValue());
        return selected.isBefore(clock.get());
    }, 
    cell.itemProperty(), 
    startMinute.valueProperty(), 
    startMeridian.valueProperty(), 
    startDate.valueProperty(),
    clock
));