英文:
How to create a method to accept and return any type of ObjectProperty<?>?
问题
以下是您要翻译的部分:
My application has an object containing multiple `ObjectProperty<Enum>` fields. I am trying to write a helper method that does the following:
- Accept any type of `ObjectProperty<Enum>` 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 <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;
}
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 ->
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()
)
);
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 <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;
}
}
希望这有助于您理解问题并解决它。如果您需要进一步的帮助,请随时提问。
英文:
My application has an object containing multiple ObjectProperty<Enum> fields. I am trying to write a helper method that does the following:
- Accept any type of
ObjectProperty<Enum>as a parameter - Display a
ChoiceDialogpopup, allowing the user to select a different text value for theenum - Update the value of the
enumin the passedObjectPropertyitself
I am not very familiar with generics at all, but I believe the following might work (no compiler errors):
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;
}
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 ->
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()
)
);
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 <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;
}
}
<hr>
So really there are a few related questions here:
- How can I pass a generic
ObjectProperty<Enum>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
Propertyafter the user has selected a new one?
答案1
得分: 5
请注意,以下是您要翻译的内容:
"The property you need to pass to the generic method should be an ObjectProperty<E>, not an ObjectProperty<Class<E>>. Then you can do:
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 ;
}
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 <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 ;
}
In this case, you just need this slight modification:
linkEditRequestSource.setOnAction(event ->
editEnumProperty(
"Select new Request Source:",
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<E, String> parameter via overloading:
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...?
}
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 <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);
}
or you could try an ugly hack such as this, which stores the string values and corresponding eval values in a 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);
}
and now of course
linkEditRequestSource.setOnAction(event -> editEnumProperty(
"Select new Request Source:",
simpleObject.requestSourceProperty(),
RequestSource::getText,
lblRequestSource.getScene().getWindow()));
英文:
The property you need to pass to the generic method should be an ObjectProperty<E>, not an ObjectProperty<Class<E>>. Then you can do:
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 ;
}
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 <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 ;
}
In this case, you just need this slight modification:
linkEditRequestSource.setOnAction(event ->
editEnumProperty(
"Select new Request Source:",
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<E, String> parameter via overloading:
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...?
}
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 <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);
}
or you could try an ugly hack such as this, which stores the string values and corresponding eval values in a 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);
}
and now of course
linkEditRequestSource.setOnAction(event -> editEnumProperty(
"Select new Request Source:",
simpleObject.requestSourceProperty(),
RequestSource::getText,
lblRequestSource.getScene().getWindow()));
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论