java - 如何创建一个方法来接受并返回任何类型的 ObjectProperty<?>?

标签 java generics javafx enums

我的应用程序有一个包含多个 ObjectProperty<Enum> 的对象字段。我正在尝试编写一个执行以下操作的辅助方法:

  • 接受任何类型的 ObjectProperty<Enum>作为参数
  • 显示ChoiceDialog弹出窗口,允许用户为 enum 选择不同的文本值
  • 更新 enum 的值在过去ObjectProperty本身

我对泛型不太熟悉,但我相信以下方法可能有效(没有编译器错误):

public static <E extends Enum<E>> boolean editEnumProperty(String prompt,
                                                           ObjectProperty<Class<E>> property,
                                                           Window owner) {

    // Create the dialog to ask for a new enum value
    ChoiceDialog<E> dialog = new ChoiceDialog<>();
    dialog.setTitle("Edit Value");
    dialog.setHeaderText(null);
    dialog.setContentText(prompt);
    dialog.initOwner(owner);

    // Currently, this displays the enum NAME in the ComboBox, rather than it's text value        
    dialog.getItems().setAll(FXCollections.observableArrayList(property.get().getEnumConstants()));

    Stage dialogStage = (Stage) dialog.getDialogPane().getScene().getWindow();
    dialogStage.getIcons().add(ImageHelper.appIcon());

    Optional<E> result = dialog.showAndWait();
    if (result.isPresent()) {
        Utility.printTempMsg(result.toString());
        Utility.printTempMsg(Enum.valueOf(property.get(), result.toString()).toString());
    }

    return true;
}

但是,我不知道如何传递ObjectProperty到这个方法。当尝试按如下方式传递我的属性时,我收到编译器错误:

linkEditRequestSource.setOnAction(event ->
            editEnumProperty(
                    "Select new Request Source:",
                    simpleObject.requestSourceProperty(),       // <-- no instance(s) of type variable(s) E exist so that RequestSource conforms to Class<E>
                    lblRequestSource.getScene().getWindow()
            )
    );

我也不知道如何设置 ObjectProperty 的值用户选择后。

下面是一个不起作用的 MCVE:

import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceDialog;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.Window;

import java.util.Optional;

enum RequestSource {
    ACQUIRED("Acquisition"),
    MANUAL("Manually Entered"),
    MANAGER("Manager Reported");

    private final String text;

    RequestSource(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

public class GenericEnumProperty extends Application {

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

    public static <E extends Enum<E>> boolean editEnumProperty(String prompt,
                                                               ObjectProperty<Class<E>> property,
                                                               Window owner) {

        // Create the dialog to ask for a new enum value
        ChoiceDialog<E> dialog = new ChoiceDialog<>();
        dialog.setTitle("Edit Value");
        dialog.setHeaderText(null);
        dialog.setContentText(prompt);
        dialog.initOwner(owner);

        dialog.getItems().setAll(FXCollections.observableArrayList(property.get().getEnumConstants()));

        Optional<E> result = dialog.showAndWait();
        if (result.isPresent()) {
            // This should actually set the value of the underlying object's ObjectProperty<RequestSource> to
            // the new value selected in the ComboBox
            System.out.println(Enum.valueOf(property.get(), result.toString()).toString());

            // Will use this value to track which properties have been modified
            return true;
        }

        return false;

    }

    @Override
    public void start(Stage primaryStage) {

        // Simple Interface
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        // Label to display current value of RequestSource
        Label lblRequestSource = new Label();

        // Create a new object
        SimpleObject simpleObject = new SimpleObject(RequestSource.ACQUIRED);

        // Bind the property value to the label
        lblRequestSource.textProperty().bind(simpleObject.requestSourceProperty().asString());

        // Hyperlink to open value editor
        Hyperlink linkEditRequestSource = new Hyperlink("Request Source:");
        linkEditRequestSource.setOnAction(event ->
                editEnumProperty(
                        "Select new Request Source:",
                        simpleObject.requestSourceProperty(),       // <-- no instance(s) of type variable(s) E exist so that RequestSource conforms to Class<E>
                        lblRequestSource.getScene().getWindow()
                )
        );

        root.getChildren().add(
                new HBox(5, linkEditRequestSource, lblRequestSource)
        );

        // Show the stage
        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("Sample");
        primaryStage.show();
    }
}

class SimpleObject {

    private final ObjectProperty<RequestSource> requestSourceProperty = new SimpleObjectProperty<>();

    public SimpleObject(RequestSource source) {
        this.requestSourceProperty.set(source);
    }

    public RequestSource getRequestSourceProperty() {
        return requestSourceProperty.get();
    }

    public void setRequestSourceProperty(RequestSource requestSourceProperty) {
        this.requestSourceProperty.set(requestSourceProperty);
    }

    public ObjectProperty<RequestSource> requestSourceProperty() {
        return requestSourceProperty;
    }
}

所以这里确实有一些相关的问题:

  • 如何传递通用 ObjectProperty<Enum>到方法?
  • 是否可以显示枚举的文本值而不是枚举名称?
  • 如何设置 Property 的值在用户选择一个新的之后?

最佳答案

您需要传递给泛型方法的属性应该是 ObjectProperty<E> ,不是 ObjectProperty<Class<E>> 。然后你可以这样做:

public static <E extends Enum<E>> boolean editEnumProperty(String prompt,
                                                           ObjectProperty<E> property,
                                                           Window owner) {

    // Create the dialog to ask for a new enum value
    ChoiceDialog<E> dialog = new ChoiceDialog<>();
    dialog.setTitle("Edit Value");
    dialog.setHeaderText(null);
    dialog.setContentText(prompt);
    dialog.initOwner(owner);

    
    
    dialog.getItems().setAll(property.get().getDeclaringClass().getEnumConstants());

    Optional<E> result = dialog.showAndWait();
    boolean changed = result.isPresent();
    result.ifPresent(property::set);
    return changed ;
}

现在您的示例代码可以按原样运行。

这里有一个警告:if property.get()返回null ,那么这将抛出一个空指针异常

dialog.getItems().setAll(property.get().getDeclaringClass().getEnumConstants());

如果你想支持值为空的“unset”属性,我认为你需要在方法中添加一个额外的参数来表示类型:

public static <E extends Enum<E>> boolean editEnumProperty(String prompt,
                                                           Class<E> enumType,
                                                           ObjectProperty<E> property,
                                                           Window owner) {

    // Create the dialog to ask for a new enum value
    ChoiceDialog<E> dialog = new ChoiceDialog<>();
    dialog.setTitle("Edit Value");
    dialog.setHeaderText(null);
    dialog.setContentText(prompt);
    dialog.initOwner(owner);

    
    
    dialog.getItems().setAll(enumType.getEnumConstants());

    Optional<E> result = dialog.showAndWait();
    boolean changed = result.isPresent();
    result.ifPresent(property::set);
    return changed ;
}

在这种情况下,您只需要稍加修改:

    linkEditRequestSource.setOnAction(event ->
            editEnumProperty(
                    "Select new Request Source:",
                    RequestSource.class,
                    simpleObject.requestSourceProperty(),      
                    lblRequestSource.getScene().getWindow()
            )
    );

您剩下的一个问题是

Is it possible to display the enum's text values instead of the enum name?

因为并非所有枚举都有 getText()方法,没有特别简单的方法来 Hook getText()您定义的用于显示枚举值的方法。一般来说,您可以提供可选的 Function<E, String>通过重载参数:

public static <E extends Enum<E>> boolean editEnumProperty(
    String prompt,
    ObjectProperty<E> property,
    Window owner) {

    return editEnumProperty(prompt, property, Object::toString, owner);
}

public static <E extends Enum<E>> boolean editEnumProperty(
    String prompt,
    ObjectProperty<E> property,
    Function<? super E, String> displayText,
    Window owner) {

    // now what...?
}

但因为也无法访问(据我所知)ChoiceDialog 显示的选择框(或组合框?) ,没有明显的方法来配置文本的显示方式。

此时您可以推出您自己的 ChoiceDialog 版本(获得对弹出控件的访问权限);例如:

public static <E extends Enum<E>> boolean editEnumProperty(String prompt, ObjectProperty<E> property,
        Function<? super E, String> displayText, Window owner) {

    Dialog<E> dialog = new Dialog<>();
    ChoiceBox<E> choice = new ChoiceBox<>();
    choice.setConverter(new StringConverter<E>() {

        @Override
        public String toString(E object) {
            return displayText.apply(object);
        }

        @Override
        public E fromString(String string) {
            // Not actually needed...
            return null;
        }
        
    });
    choice.getItems().setAll(property.get().getDeclaringClass().getEnumConstants());
    choice.getSelectionModel().select(property.get());
    dialog.getDialogPane().setContent(new HBox(5, new Label(prompt), choice));
    dialog.getDialogPane().getButtonTypes().setAll(ButtonType.CANCEL, ButtonType.OK);
    dialog.setResultConverter(button -> 
        button == ButtonType.OK ? choice.getSelectionModel().getSelectedItem() : null
    );
    return dialog.showAndWait().map(value -> {
        property.set(value);
        return true ;
    }).orElse(false);
    
}

或者您可以尝试像这样的丑陋黑客,它将字符串值和相应的 eval 值存储在 Map 中。 :

public static <E extends Enum<E>> boolean editEnumProperty(String prompt, ObjectProperty<E> property,
        Function<? super E, String> displayText, Window owner) {

// Create the dialog to ask for a new enum value
    ChoiceDialog<String> dialog = new ChoiceDialog<>();
    dialog.setTitle("Edit Value");
    dialog.setHeaderText(null);
    dialog.setContentText(prompt);
    dialog.initOwner(owner);
    
    Map<String, E> displayValueLookup = Stream.of(property.get().getDeclaringClass().getEnumConstants())
            .collect(Collectors.toMap(displayText, Function.identity()));

    dialog.getItems().setAll(displayValueLookup.keySet());

    return  dialog.showAndWait()
            .map(displayValueLookup::get)
            .map(value -> {
                property.set(value);
                return true ;
            })
            .orElse(false);
}

当然是现在

    linkEditRequestSource.setOnAction(event -> editEnumProperty(
        "Select new Request Source:",
        simpleObject.requestSourceProperty(), 
        RequestSource::getText,
        lblRequestSource.getScene().getWindow()));

关于java - 如何创建一个方法来接受并返回任何类型的 ObjectProperty<?>?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63287728/

相关文章:

具有多个文本行的 JavaFX 按钮

java - java中同步时方法的可访问性

java - 如何从 jSpinner 更改时间格式?

java - 从 Interface<Type> 到 Interface<Subtype> 的适配器

c# - 无法在 C# 中将具体类型转换为其接口(interface)的泛型版本

JavaFX ScrollPane - 如何使用一个滚动条来滚动 2 个 ScrollPanes?

java - 使用javafx向mysql数据库插入数据时出错

java - 为什么调用我的 bean 中的 @Autowired 字段会返回 null?

java - 将 @Transactional 与 hibernate 一起使用

java - 实现 Map 时如何强制泛型参数父类(super class)类型?