Как создать метод для приема и возврата любого типа ObjectProperty ? - PullRequest
2 голосов
/ 06 августа 2020

В моем приложении есть объект, содержащий несколько полей 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;
    }
}

Так что на самом деле здесь есть несколько связанных вопросов:

  • Как я могу пройти generi c ObjectProperty<Enum> методу?
  • Можно ли отображать текстовые значения перечисления вместо имени перечисления?
  • Как мне установить значение Property после пользователь выбрал новый?

1 Ответ

5 голосов
/ 06 августа 2020

Свойство, которое необходимо передать в метод generi c, должно быть 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 ;
}

Теперь ваш примерный код работает как есть.

Здесь есть одно предостережение: если property.get() возвращает null, тогда это приведет к нулю исключение указателя в

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

Если вы хотите поддерживать "неустановленные" свойства, значение которых равно null, я думаю, вам понадобится дополнительный параметр в методе для представления типа:

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()
            )
    );

Ваш единственный оставшийся вопрос:

Можно ли отображать текстовые значения перечисления вместо имени перечисления?

Поскольку не все перечисления имеют метод getText(), нет особенно простого способа подключиться к определенному вами методу 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()));
...