当前位置: 首页 > 知识库问答 >
问题:

JavaFx:根据另一个组合框,使用不同的枚举填充组合框

祁飞翰
2023-03-14

我有两个组合框:

    final ComboBox<MainCategory> comboBoxMainCategory = new ComboBox<>();
    final ComboBox<SubCategory> comboBoxSubCategory = new ComboBox<>();

根据comboBoxMainCategory中选择的值,comboBoxSubCategory应填充相应的枚举。

    public enum MainCategory { // extra enum class
      EUROPE("Europe"),
      USA("USA");
    }

    public enum SubCategoryEurope { // extra enum class
      GERMANY("Germany"),
      FRANCE("France");
    }

    public enum SubCategoryUSA {
      COLORADO("Colorado"),
      CALIFORNIA("California");
    }

如果comboBoxMainCategory选择“Europe”,则comboBoxSubCategory应填充SubCategory Europe。如果为“USA”,则带有子类别USA。

你是如何做到这一点的?

这是我的代码:

    final ComboBox<MainCategory> comboBoxMainCategory = new ComboBox<();
    final ComboBox<SubCategory> comboBoxSubCategory = new ComboBox<>(); 

    comboBoxMainCategory.valueProperty().addListener((obs, oldValue, 
    newValue) ->
          {
            if (newValue == null) { // newValue: Europe || USA
              comboBoxSubCategory.getItems().clear();
              comboBoxSubCategory.setDisable(true);
            } else if (newValue.equals(MainCategory.EUROPE)) {

 comboBoxSubCategory.setItems(FXCollections.observableArrayList(SubCategoryEurope.values())); 
            comboBoxSubCategory.setDisable(false);
        } else {
             comboBoxSubCategory.setItems(FXCollections.observableArrayList(SubCategoryUSA.values())); 
            comboBoxSubCategory.setDisable(false);}
        });

问题是,因为comboBoxSubCategory是“SubCategory”,如果它填充了“SubCategoryEurope”或“SubCategoryUSA”,则会出现类型错误。

解决这个问题的最佳方法是什么?抱歉,如果这是一个愚蠢的问题,我是JavaFx新手。

非常感谢!

共有3个答案

益稳
2023-03-14

创建通用占位符界面

public interface EnumPlaceHolder<E extends Enum<?>> {

  public abstract String getDisplayValue();

  public abstract E getEnum();

}

为您的所有枚举创建一个实现。例如

public class EuropePlaceholder implements EnumPlaceHolder<Europe> {

  private final Europe value;

  public EuropePlaceholder(Europe pValue){
     value = pValue;
  }

  @Override
  public String getDisplayValue() {
    // here you create a user-friendly version of your enum for display
    return value.toString();
  }

  @Override
  public Europe getEnum() {
    return value;
  }

}

然后更改组合框的类型

EnumPlaceholder<?> selectedItem = ...;
if(selectedItem instanceof EuropePlaceholder){
   Europe selectedEuropeEnum = (Europe) selectedItem.getEnum();
} else if(....){
  // check with else if for your other enums
}

要在组合框中显示枚举,可以调用枚举占位符的getDisplayValue(),并在单元格中显示返回的字符串:)

总的来说,我同意费边的回答。对于这样的构造,不应该使用枚举。而是使用地图

屈昊天
2023-03-14

只是为了好玩(也为了充实我的评论):比其他答案中的方法更通用的方法是将兴趣的焦点从支持数据的具体性质转移到手头用例的更通用的解决方案上。让UI实现特殊情况的缺点总是一样的——你必须为每个特殊UI和每个特殊数据类型一遍又一遍地做。出路也总是一样的:实现一个接管一般方面的模型,并在具体UI /data环境中重复使用它。

一般情况如下:

  • 有一个项目列表,每个项目都有一个从属对象列表(相同或不同类型)
  • 这个(我们称之为根目录)项目列表显示在控件中
  • 从根列表中,可以选择单个项目(又名:selected)
  • 另一个控件应显示根的从属项

一般方法是建立一个模型

  • 管理项目列表

这种模式的优点是

  • 可以进行正式和严格的测试,因此使用代码可以依赖其正常功能
  • 它可用于任何数据上下文
  • 它可用于许多控件
  • 通过绑定,使用非常简单

在下面的例子中,模型被命名为关系模型,它需要关系提供者类型的根项目(它允许访问依赖列表,这是一种选择,也可以使用f. i. a函数来构建依赖)。它一次用于简单的字符串映射/列表,一次用于大陆/国家枚举,每个都非常易于实现。请注意,生成的UI非常幸运地不知道数据的性质,仅针对模型实现。

自然,不是生产级,特别是,没有经过正式测试,模型只有最基本的功能:)

public class CombosWithCategories extends Application {

    public interface RelationProvider<T> {
        default ObservableList<T> getRelations() {
            return emptyObservableList();
        };
    }

    /**
     * A model that manages a list of RelationProviders and has the notion
     * of a current relationProvider with relations (it's a kind-of selectionModel).
     * 
     * <T> the type of elements in the list of relations 
     */
    public static class RelationModel<T> {

        /**
         * all relationProviders managed by this model
         */
        private ListProperty<RelationProvider<T>> relationProviders;
        /**
         * The owner of the relations. Must be contained in the providers managed
         * by this model.
         */
        private ObjectProperty<RelationProvider<T>> relationProvider;
        private ListProperty<T> relations;

        public RelationModel() {
            initProperties();
        }

        /**
         * The RelationProviders managed by the model.
         */
        public ListProperty<RelationProvider<T>> relationProvidersProperty() {
            return relationProviders;
        }

        /**
         * The RelationProvider that manages the current relations.
         */
        public ObjectProperty<RelationProvider<T>> relationProviderProperty() {
            return relationProvider;
        }

        public RelationProvider<T> getRelationProvider() {
            return relationProviderProperty().get();
        }

        public ListProperty<T> relations() {
            return relations;
        }

        /**
         * Callback from invalidation of current relationProvider.
         * Implemented to update relations.
         */
        protected void relationProviderInvalidated() {
            RelationProvider<T> value = getRelationProvider();
            relations().set(value != null ? value.getRelations() : emptyObservableList());
        }

        /**
         * Creates and wires all properties.
         */
        private void initProperties() {
            relationProviders = new SimpleListProperty<>(this, "relationProviders", observableArrayList());
            relationProvider = new SimpleObjectProperty<>(this, "relationProvider") {

                @Override
                protected void invalidated() {
                    // todo: don't accept providers that are not in the list
                    relationProviderInvalidated();
                }

            };
            relations = new SimpleListProperty<>(this, "relations");
            relationProviderInvalidated();

        }

    }

    /**
     * Implement the ui against a RelationModel. Here we create
     * the same UI with a model backed by enums or a Map, respectively
     */
    private Parent createContent() {
        TabPane tabPane = new TabPane(
                new Tab("Enums", createRelationUI(createEnumRelationModel())),
                new Tab("Manual map", createRelationUI(createMapRelationModel()))
                );

        return new BorderPane(tabPane);
    }

    /**
     * Common factory for UI: creates and returns a Parent that
     * contains two combo's configured to use the model.
     */
    protected <T> Parent createRelationUI(RelationModel<T> model) {
        ComboBox<RelationProvider<T>> providers = new ComboBox<>();
        providers.itemsProperty().bind(model.relationProvidersProperty());
        providers.valueProperty().bindBidirectional(model.relationProviderProperty());

        ComboBox<T> relations = new ComboBox<>();
        relations.itemsProperty().bind(model.relations());
        relations.valueProperty().addListener((src, ov, nv) -> {
            LOG.info("relation changed: " + nv); 
        });

        return new VBox(10, providers, relations);
    }


    // ------------- manual with maps

    /**
     * On-the-fly creation of a RelationModel using a backing map.
     */
    protected RelationModel<String> createMapRelationModel() {
        RelationModel<String> model = new RelationModel<>();
        Map<String, ObservableList<String>> data = new HashMap<>();
        data.put("EUROPE", observableArrayList("GERMANY", "FRANCE"));
        data.put("AMERICA", observableArrayList("MEXICO", "USA"));
        for (String key: data.keySet()) {
            model.relationProvidersProperty().add(new RelationProvider<String>() {

                @Override
                public ObservableList<String> getRelations() {
                    return data.get(key);
                }

                @Override
                public String toString() {
                    return key;
                }


            });
        }
        return model;
    }
    //-------------------- enum
    /**
     * RelationModel using Enums.
     */
    protected RelationModel<Object> createEnumRelationModel() {
        RelationModel<Object> model = new RelationModel<Object>();
        model.relationProvidersProperty().setAll(Continent.values());
        return model;
    }

    public enum EuropeanCountry {
        FRANCE, GERMANY;
    }

    public enum AmericanCountry {
        MEXICO, CANADA, USA;
    }

    public enum Continent implements RelationProvider<Object> {
        AMERICA(AmericanCountry.values()),
        EUROPE(EuropeanCountry.values())
        ;

        ObservableList<Object> subs;
        private Continent(Object[] subs) {
            this.subs = FXCollections.observableArrayList(subs);
        }
        @Override
        public ObservableList<Object> getRelations() {
            return FXCollections.unmodifiableObservableList(subs);
        }
    }

    @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);
    }

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(CombosWithCategories.class.getName());

}
融伯寅
2023-03-14

我根本不会使用枚举,因为这不允许在不重新编译的情况下进行数据操作。但是,如果坚持使用枚举,则需要使用对象或使用两个子类别枚举类型实现的接口作为comboBoxSubCategory的参数类型:

comboBoxMainCategory.valueProperty().addListener((obs, oldValue, newValue) -> {
        if (newValue == null) { // newValue: Europe || USA
            comboBoxSubCategory.getItems().clear();
            comboBoxSubCategory.setDisable(true);
        } else {
            comboBoxSubCategory.setDisable(false);
            List<? extends Object> list;
            switch (newValue) {
                case EUROPE:
                    list = Arrays.asList(SubCategoryEurope.values());
                    break;
                default:
                    list = Arrays.asList(SubCategoryUSA.values());
                    break;
            }
            comboBoxSubCategory.getItems().setAll(list);
        }
    });

更好的方法是使用Map

Map<String, List<String>> data = new HashMap<>();
data.put("EUROPE", Arrays.asList("GERMANY", "FRANCE"));
data.put("USA", Arrays.asList("COLORADO", "CALIFORNIA"));

comboBoxMainCategory.valueProperty().addListener((obs, oldValue, newValue) -> {
        List<String> list = data.get(newValue);
        if (list != null) {
            comboBoxSubCategory.setDisable(false);
            comboBoxSubCategory.getItems().setAll(list);
        } else {
            comboBoxSubCategory.getItems().clear();
            comboBoxSubCategory.setDisable(true);
        }
    });

 类似资料:
  • 我想在一个组合框中填充一些数据。这很好。但是,我想动态地改变组合框中的数据。 目前我有一个表,在组合框中,我在表列中显示数据的唯一值。现在,表数据存储在静态可观察列表变量中。我想根据表中显示的数据更改组合框中的数据。也就是说,如果存储表数据的静态可观察列表发生更改,我希望在没有手动干预的情况下更改组合框数据。 如果没有单独的静态变量用于组合框,这是否可能?

  • 我无法填充从场景生成器创建的JavaFX组合框。虽然我已经搜索过了,但我找不到解决这个错误的方法。 下面的例子都不管用。 帮助将不胜感激。

  • 我正在查询Sql Server并返回一个List-我想使用此List作为我的组合框的源。下面是我正在使用的代码,它运行时没有错误,但我的组合框始终为空并且从不填充。这里有什么不正确的? 主要的Java语言 样品fxml

  • 说到编程或java,我只是一个完全的初学者。所以一开始,我的计划是使用JavaFX(结合场景构建器)创建一个窗口,在那里我有一个按钮,可以引导我进入另一个窗口,在那里我有一个组合框。我谷歌了几个小时,想找到一种方法来填充组合框,但我找到的所有解决方案都不适合我。这就是为什么我认为我在这里犯了一些错误,我希望你能以某种方式帮助我。或者在列表中给我一个提示,我应该学习/阅读什么才能自己找到解决方案。首

  • 我正在为一个游戏开发Javafx应用程序。我有一些数据存储在枚举中,但似乎不知道如何轻松地将数据添加到JavaFX TableView,有人能帮我一下吗。我将使用fxml为TableView设置样式。 我希望每一个枚举vallue都在一个单独的行中,在我希望的列中: 图标为图像。 按整数排序。 级别为整数。 经验值为整数。 枚举:

  • 我正在创建一个影院系统,用户可以通过主页中的组合框选择电影。我在FilmController类中创建了一个数组列表,然后将其转换为observableList,并且正在努力将其内容填充到combobox(HomepageController)中。 这是arraylist的FilmController 我尝试在HomepageController中实现此功能,但它似乎给了我一个错误: 我已经研究过这