【问题标题】:How do you make a ComboBox CellFactory properly disable items on the fly?您如何使 ComboBox CellFactory 正确地动态禁用项目?
【发布时间】:2017-04-24 13:56:01
【问题描述】:

我有几个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 的用途?

【问题讨论】:

  • 对不起,这里是视频链接:youtube.com/watch?v=niGoIvVx9Y0
  • 从您的代码中,我意识到如果时间 isAfter(LocalDateTime.now()) 即您可能想在您的 isBefore 检查中添加一个 else 以撤消对单元格的禁用,则您什么也不做。
  • 谢谢,这有助于在上下滚动时随机着色和禁用不正确的框,但它并没有解决当日子进一步改变时项目无法重新启用的问题.
  • 没有看到更多代码,我希望您尝试更改日期,然后选择不同的小时或分钟,看看是否正确更新。我的想法是 CellFactorys 没有被调用,因为它们没有被更新,只有 DatePicker 正在更新。
  • 是的,我几乎可以肯定,因为CellFactorys 不会在日期更改时被调用。您可能必须绑定属性,以便在更改 DatePicker 时,它也会更改其他属性。我发现 MAY 的唯一帮助是dayCellFactoryProperty。我也不确定这是否可行,因为我没有测试程序可以尝试。祝你好运。

标签: java javafx combobox


【解决方案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
));

【讨论】:

  • 我为起跑线上的所有ComboBox 实例实施了这个解决方案,现在一切都按预期工作。还将样式与外部 CSS 表分开。绑定就是解决方案。
猜你喜欢
  • 2020-04-24
  • 2018-08-25
  • 1970-01-01
  • 2012-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-03
相关资源
最近更新 更多