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

huangapple go评论78阅读模式
英文:

How to create a method to accept and return any type of ObjectProperty<?>?

问题

以下是您要翻译的部分:

My application has an object containing multiple `ObjectProperty&lt;Enum&gt;` fields. I am trying to write a helper method that does the following:

 - Accept any type of `ObjectProperty&lt;Enum&gt;` as a parameter
 - Display a `ChoiceDialog` popup, allowing the user to select a different text value for the `enum`
 - Update the value of the `enum` in the passed `ObjectProperty` itself

I am not very familiar with generics at all, but I believe the following might work (no compiler errors):

public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(String prompt,
                                                           ObjectProperty&lt;Class&lt;E&gt;&gt; property,
                                                           Window owner) {

    // Create the dialog to ask for a new enum value
    ChoiceDialog&lt;E&gt; dialog = new ChoiceDialog&lt;&gt;();
    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&lt;E&gt; result = dialog.showAndWait();
    if (result.isPresent()) {
        Utility.printTempMsg(result.toString());
        Utility.printTempMsg(Enum.valueOf(property.get(), result.toString()).toString());
    }

    return true;
}

However, I am not sure how to pass the `ObjectProperty` to this method. When trying to pass my property through as follows, I'm getting a compiler error:

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

I also do not know how to then set the value of the `ObjectProperty` after the user has selected one.

Below is a non-working 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 &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(String prompt,
                                                               ObjectProperty&lt;Class&lt;E&gt;&gt; property,
                                                               Window owner) {

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

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

        Optional&lt;E&gt; result = dialog.showAndWait();
        if (result.isPresent()) {
            // This should actually set the value of the underlying object's ObjectProperty&lt;RequestSource&gt; 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&lt;E&gt;
                        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&lt;RequestSource&gt; requestSourceProperty = new SimpleObjectProperty&lt;&gt;();

    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&lt;RequestSource&gt; requestSourceProperty() {
        return requestSourceProperty;
    }
}

希望这有助于您理解问题并解决它。如果您需要进一步的帮助,请随时提问。

英文:

My application has an object containing multiple ObjectProperty&lt;Enum&gt; fields. I am trying to write a helper method that does the following:

  • Accept any type of ObjectProperty&lt;Enum&gt; as a parameter
  • Display a ChoiceDialog popup, allowing the user to select a different text value for the enum
  • Update the value of the enum in the passed ObjectProperty itself

I am not very familiar with generics at all, but I believe the following might work (no compiler errors):

public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(String prompt,
ObjectProperty&lt;Class&lt;E&gt;&gt; property,
Window owner) {
// Create the dialog to ask for a new enum value
ChoiceDialog&lt;E&gt; dialog = new ChoiceDialog&lt;&gt;();
dialog.setTitle(&quot;Edit Value&quot;);
dialog.setHeaderText(null);
dialog.setContentText(prompt);
dialog.initOwner(owner);
// Currently, this displays the enum NAME in the ComboBox, rather than it&#39;s text value        
dialog.getItems().setAll(FXCollections.observableArrayList(property.get().getEnumConstants()));
Stage dialogStage = (Stage) dialog.getDialogPane().getScene().getWindow();
dialogStage.getIcons().add(ImageHelper.appIcon());
Optional&lt;E&gt; result = dialog.showAndWait();
if (result.isPresent()) {
Utility.printTempMsg(result.toString());
Utility.printTempMsg(Enum.valueOf(property.get(), result.toString()).toString());
}
return true;
}

However, I am not sure how to pass the ObjectProperty to this method. When trying to pass my property through as follows, I'm getting a compiler error:

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

I also do not know how to then set the value of the ObjectProperty after the user has selected one.

Below is a non-working 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(&quot;Acquisition&quot;),
MANUAL(&quot;Manually Entered&quot;),
MANAGER(&quot;Manager Reported&quot;);
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 &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(String prompt,
ObjectProperty&lt;Class&lt;E&gt;&gt; property,
Window owner) {
// Create the dialog to ask for a new enum value
ChoiceDialog&lt;E&gt; dialog = new ChoiceDialog&lt;&gt;();
dialog.setTitle(&quot;Edit Value&quot;);
dialog.setHeaderText(null);
dialog.setContentText(prompt);
dialog.initOwner(owner);
dialog.getItems().setAll(FXCollections.observableArrayList(property.get().getEnumConstants()));
Optional&lt;E&gt; result = dialog.showAndWait();
if (result.isPresent()) {
// This should actually set the value of the underlying object&#39;s ObjectProperty&lt;RequestSource&gt; 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(&quot;Request Source:&quot;);
linkEditRequestSource.setOnAction(event -&gt;
editEnumProperty(
&quot;Select new Request Source:&quot;,
simpleObject.requestSourceProperty(),       // &lt;-- no instance(s) of type variable(s) E exist so that RequestSource conforms to Class&lt;E&gt;
lblRequestSource.getScene().getWindow()
)
);
root.getChildren().add(
new HBox(5, linkEditRequestSource, lblRequestSource)
);
// Show the stage
primaryStage.setScene(new Scene(root));
primaryStage.setTitle(&quot;Sample&quot;);
primaryStage.show();
}
}
class SimpleObject {
private final ObjectProperty&lt;RequestSource&gt; requestSourceProperty = new SimpleObjectProperty&lt;&gt;();
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&lt;RequestSource&gt; requestSourceProperty() {
return requestSourceProperty;
}
}

<hr>

So really there are a few related questions here:

  • How can I pass a generic ObjectProperty&lt;Enum&gt; to the method?
  • Is it possible to display the enum's text values instead of the enum name?
  • How do I set the value of the Property after the user has selected a new one?

答案1

得分: 5

请注意,以下是您要翻译的内容:

"The property you need to pass to the generic method should be an ObjectProperty&lt;E&gt;, not an ObjectProperty&lt;Class&lt;E&gt;&gt;. Then you can do:

public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(String prompt,
ObjectProperty&lt;E&gt; property,
Window owner) {
// Create the dialog to ask for a new enum value
ChoiceDialog&lt;E&gt; dialog = new ChoiceDialog&lt;&gt;();
dialog.setTitle(&quot;Edit Value&quot;);
dialog.setHeaderText(null);
dialog.setContentText(prompt);
dialog.initOwner(owner);
dialog.getItems().setAll(property.get().getDeclaringClass().getEnumConstants());
Optional&lt;E&gt; result = dialog.showAndWait();
boolean changed = result.isPresent();
result.ifPresent(property::set);
return changed ;
}

Now your example code works as-is.

There is one caveat here: if property.get() returns null, then this will throw a null pointer exception at

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

If you want to support "unset" properties whose value is null, I think you need an extra parameter in the method, to represent the type:

public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(String prompt,
Class&lt;E&gt; enumType,
ObjectProperty&lt;E&gt; property,
Window owner) {
// Create the dialog to ask for a new enum value
ChoiceDialog&lt;E&gt; dialog = new ChoiceDialog&lt;&gt;();
dialog.setTitle(&quot;Edit Value&quot;);
dialog.setHeaderText(null);
dialog.setContentText(prompt);
dialog.initOwner(owner);
dialog.getItems().setAll(enumType.getEnumConstants());
Optional&lt;E&gt; result = dialog.showAndWait();
boolean changed = result.isPresent();
result.ifPresent(property::set);
return changed ;
}

In this case, you just need this slight modification:

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

Your one remaining question is

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

Since not all enums have a getText() method, there's no particularly easy way to hook into the getText() method you've defined to display the enum values. Generally, you could offer an optional Function&lt;E, String&gt; parameter via overloading:

public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(
String prompt,
ObjectProperty&lt;E&gt; property,
Window owner) {
return editEnumProperty(prompt, property, Object::toString, owner);
}
public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(
String prompt,
ObjectProperty&lt;E&gt; property,
Function&lt;? super E, String&gt; displayText,
Window owner) {
// now what...?
}

but since there's also no access (as far as I can tell) to the choice box (or combo box?) displayed by the ChoiceDialog, there's no obvious way to configure how the text is displayed.

You could at this point roll your own version of the ChoiceDialog (gaining access to the popup control); for example:

public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(String prompt, ObjectProperty&lt;E&gt; property,
Function&lt;? super E, String&gt; displayText, Window owner) {
Dialog&lt;E&gt; dialog = new Dialog&lt;&gt;();
ChoiceBox&lt;E&gt; choice = new ChoiceBox&lt;&gt;();
choice.setConverter(new StringConverter&lt;E&gt;() {
@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 -&gt; 
button == ButtonType.OK ? choice.getSelectionModel().getSelectedItem() : null
);
return dialog.showAndWait().map(value -&gt; {
property.set(value);
return true ;
}).orElse(false);
}

or you could try an ugly hack such as this, which stores the string values and corresponding eval values in a Map:

public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(String prompt, ObjectProperty&lt;E&gt; property,
Function&lt;? super E, String&gt; displayText, Window owner) {
// Create the dialog to ask for a new enum value
ChoiceDialog&lt;String&gt; dialog = new ChoiceDialog&lt;&gt;();
dialog.setTitle(&quot;Edit Value&quot;);
dialog.setHeaderText(null);
dialog.setContentText(prompt);
dialog.initOwner(owner);
Map&lt;String, E&gt; 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 -&gt; {
property.set(value);
return true ;
})
.orElse(false);
}

and now of course

	linkEditRequestSource.setOnAction(event -&gt; editEnumProperty(
&quot;Select new Request Source:&quot;,
simpleObject.requestSourceProperty(), 
RequestSource::getText,
lblRequestSource.getScene().getWindow()));
英文:

The property you need to pass to the generic method should be an ObjectProperty&lt;E&gt;, not an ObjectProperty&lt;Class&lt;E&gt;&gt;. Then you can do:

public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(String prompt,
ObjectProperty&lt;E&gt; property,
Window owner) {
// Create the dialog to ask for a new enum value
ChoiceDialog&lt;E&gt; dialog = new ChoiceDialog&lt;&gt;();
dialog.setTitle(&quot;Edit Value&quot;);
dialog.setHeaderText(null);
dialog.setContentText(prompt);
dialog.initOwner(owner);
dialog.getItems().setAll(property.get().getDeclaringClass().getEnumConstants());
Optional&lt;E&gt; result = dialog.showAndWait();
boolean changed = result.isPresent();
result.ifPresent(property::set);
return changed ;
}

Now your example code works as-is.

There is one caveat here: if property.get() returns null, then this will throw a null pointer exception at

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

If you want to support "unset" properties whose value is null, I think you need an extra parameter in the method, to represent the type:

public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(String prompt,
Class&lt;E&gt; enumType,
ObjectProperty&lt;E&gt; property,
Window owner) {
// Create the dialog to ask for a new enum value
ChoiceDialog&lt;E&gt; dialog = new ChoiceDialog&lt;&gt;();
dialog.setTitle(&quot;Edit Value&quot;);
dialog.setHeaderText(null);
dialog.setContentText(prompt);
dialog.initOwner(owner);
dialog.getItems().setAll(enumType.getEnumConstants());
Optional&lt;E&gt; result = dialog.showAndWait();
boolean changed = result.isPresent();
result.ifPresent(property::set);
return changed ;
}

In this case, you just need this slight modification:

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

Your one remaining question is

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

Since not all enums have a getText() method, there's no particularly easy way to hook into the getText() method you've defined to display the enum values. Generally, you could offer an optional Function&lt;E, String&gt; parameter via overloading:

public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(
String prompt,
ObjectProperty&lt;E&gt; property,
Window owner) {
return editEnumProperty(prompt, property, Object::toString, owner);
}
public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(
String prompt,
ObjectProperty&lt;E&gt; property,
Function&lt;? super E, String&gt; displayText,
Window owner) {
// now what...?
}

but since there's also no access (as far as I can tell) to the choice box (or combo box?) displayed by the ChoiceDialog, there's no obvious way to configure how the text is displayed.

You could at this point roll your own version of the ChoiceDialog (gaining access to the popup control); for example:

public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(String prompt, ObjectProperty&lt;E&gt; property,
Function&lt;? super E, String&gt; displayText, Window owner) {
Dialog&lt;E&gt; dialog = new Dialog&lt;&gt;();
ChoiceBox&lt;E&gt; choice = new ChoiceBox&lt;&gt;();
choice.setConverter(new StringConverter&lt;E&gt;() {
@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 -&gt; 
button == ButtonType.OK ? choice.getSelectionModel().getSelectedItem() : null
);
return dialog.showAndWait().map(value -&gt; {
property.set(value);
return true ;
}).orElse(false);
}

or you could try an ugly hack such as this, which stores the string values and corresponding eval values in a Map:

public static &lt;E extends Enum&lt;E&gt;&gt; boolean editEnumProperty(String prompt, ObjectProperty&lt;E&gt; property,
Function&lt;? super E, String&gt; displayText, Window owner) {
// Create the dialog to ask for a new enum value
ChoiceDialog&lt;String&gt; dialog = new ChoiceDialog&lt;&gt;();
dialog.setTitle(&quot;Edit Value&quot;);
dialog.setHeaderText(null);
dialog.setContentText(prompt);
dialog.initOwner(owner);
Map&lt;String, E&gt; 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 -&gt; {
property.set(value);
return true ;
})
.orElse(false);
}

and now of course

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

huangapple
  • 本文由 发表于 2020年8月7日 00:12:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/63287728.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定