Refactorings ...
This commit is contained in:
parent
5503dd192e
commit
3178dc2723
@ -8,13 +8,13 @@ import javafx.beans.property.SimpleIntegerProperty;
|
|||||||
import javafx.beans.property.SimpleLongProperty;
|
import javafx.beans.property.SimpleLongProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AutoViewModel automatically exposes JavaFX properties for all readable/writable fields
|
* AutoViewModel automatically exposes JavaFX properties for all readable/writable fields
|
||||||
@ -23,14 +23,15 @@ import java.util.logging.Logger;
|
|||||||
*
|
*
|
||||||
* @param <T> the type of the underlying model
|
* @param <T> the type of the underlying model
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@Slf4j
|
||||||
public class AutoViewModel<T> {
|
public class AutoViewModel<T> {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(AutoViewModel.class.getName());
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The wrapped model instance.
|
* The wrapped model instance.
|
||||||
|
* -- GETTER --
|
||||||
|
* Retrieves the model associated with this AutoViewModel.
|
||||||
*/
|
*/
|
||||||
|
@Getter
|
||||||
private final T model;
|
private final T model;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,6 +49,22 @@ public class AutoViewModel<T> {
|
|||||||
initProperties();
|
initProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a mapping of field names to JavaFX properties for the associated model object.
|
||||||
|
* <p>
|
||||||
|
* This method utilizes reflection to iterate through the public no-argument getters
|
||||||
|
* of the model's class to create corresponding JavaFX properties. It performs the following:
|
||||||
|
* <p>
|
||||||
|
* 1. Identifies methods that adhere to the getter naming conventions (e.g., `getFieldName` or `isFieldName`).
|
||||||
|
* 2. Maps the field name derived from the getter method to a JavaFX property, creating an appropriate
|
||||||
|
* property type based on the getter's return type.
|
||||||
|
* 3. Adds a listener to each property to bind updates from the property back to the model object
|
||||||
|
* using the corresponding setter method, if available. This ensures bi-directional synchronization
|
||||||
|
* between the ViewModel and the underlying model.
|
||||||
|
* <p>
|
||||||
|
* Exceptions that may occur during reflective operations (e.g., invoking methods) are caught
|
||||||
|
* and logged to avoid runtime failures.
|
||||||
|
*/
|
||||||
private void initProperties() {
|
private void initProperties() {
|
||||||
for (Method getter : model.getClass().getMethods()) {
|
for (Method getter : model.getClass().getMethods()) {
|
||||||
if (isGetter(getter)) {
|
if (isGetter(getter)) {
|
||||||
@ -68,7 +85,7 @@ public class AutoViewModel<T> {
|
|||||||
try {
|
try {
|
||||||
setter.invoke(model, newVal);
|
setter.invoke(model, newVal);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.log(Level.WARNING, "Failed to invoke setter for field " + fieldName, e);
|
log.warn("Failed to invoke setter for field {}", fieldName, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -76,6 +93,14 @@ public class AutoViewModel<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a given method follows the JavaBean getter convention.
|
||||||
|
* The method must be public, take no parameters, have a non-void return type,
|
||||||
|
* and its name should start with either "get" or "is".
|
||||||
|
*
|
||||||
|
* @param method the method to be assessed
|
||||||
|
* @return true if the method adheres to the JavaBean getter convention, false otherwise
|
||||||
|
*/
|
||||||
private boolean isGetter(Method method) {
|
private boolean isGetter(Method method) {
|
||||||
return Modifier.isPublic(method.getModifiers())
|
return Modifier.isPublic(method.getModifiers())
|
||||||
&& method.getParameterCount() == 0
|
&& method.getParameterCount() == 0
|
||||||
@ -83,6 +108,14 @@ public class AutoViewModel<T> {
|
|||||||
&& (method.getName().startsWith("get") || method.getName().startsWith("is"));
|
&& (method.getName().startsWith("get") || method.getName().startsWith("is"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives the field name from a given Java method by following JavaBean naming conventions.
|
||||||
|
* The method's name is analyzed to strip the "get" or "is" prefix (if present),
|
||||||
|
* and the result is converted into a decapitalized field name.
|
||||||
|
*
|
||||||
|
* @param method the method whose name is to be processed to derive the field name
|
||||||
|
* @return the derived field name, or the original method name if no "get" or "is" prefix is found
|
||||||
|
*/
|
||||||
private String getFieldName(Method method) {
|
private String getFieldName(Method method) {
|
||||||
String name = method.getName();
|
String name = method.getName();
|
||||||
if (name.startsWith("get")) {
|
if (name.startsWith("get")) {
|
||||||
@ -95,15 +128,32 @@ public class AutoViewModel<T> {
|
|||||||
|
|
||||||
// ========== Hilfsmethoden ==========
|
// ========== Hilfsmethoden ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the specified method on the model object and returns the result.
|
||||||
|
* If the method invocation fails, logs a warning and returns null.
|
||||||
|
*
|
||||||
|
* @param method the method to be invoked, typically a JavaBean getter
|
||||||
|
* @return the result of invoking the method, or null if an error occurs
|
||||||
|
*/
|
||||||
private Object invokeGetter(Method method) {
|
private Object invokeGetter(Method method) {
|
||||||
try {
|
try {
|
||||||
return method.invoke(model);
|
return method.invoke(model);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.log(Level.WARNING, "Failed to invoke getter: " + method.getName(), e);
|
log.warn("Failed to invoke getter: {}", method.getName(), e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an object into an appropriate JavaFX {@link Property} based on its type.
|
||||||
|
*
|
||||||
|
* @param value the object to be converted into a JavaFX property; can be of type String, Integer,
|
||||||
|
* Boolean, Double, Float, Long, or any other object
|
||||||
|
* @return a JavaFX {@link Property} corresponding to the type of the input object, such as
|
||||||
|
* {@link SimpleStringProperty} for String, {@link SimpleIntegerProperty} for Integer,
|
||||||
|
* {@link SimpleBooleanProperty} for Boolean, and so on. If the type is not explicitly
|
||||||
|
* handled, a {@link SimpleObjectProperty} is returned wrapping the object
|
||||||
|
*/
|
||||||
private Property<?> toProperty(Object value) {
|
private Property<?> toProperty(Object value) {
|
||||||
if (value instanceof String s) return new SimpleStringProperty(s);
|
if (value instanceof String s) return new SimpleStringProperty(s);
|
||||||
if (value instanceof Integer i) return new SimpleIntegerProperty(i);
|
if (value instanceof Integer i) return new SimpleIntegerProperty(i);
|
||||||
@ -114,6 +164,19 @@ public class AutoViewModel<T> {
|
|||||||
return new SimpleObjectProperty<>(value);
|
return new SimpleObjectProperty<>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a setter method in a specified class that corresponds to the given field name and value type.
|
||||||
|
* <p>
|
||||||
|
* The method searches for a public method in the class whose name matches the JavaBean-style setter
|
||||||
|
* naming convention (e.g., "setFieldName"). The method must accept exactly one parameter, and if a
|
||||||
|
* value type is provided, the parameter type must be assignable from it.
|
||||||
|
*
|
||||||
|
* @param clazz the class to search for the method
|
||||||
|
* @param fieldName the name of the field for which the setter method is being sought
|
||||||
|
* @param valueType the expected type of the parameter for the setter method, or null if no specific
|
||||||
|
* type constraint is required
|
||||||
|
* @return the setter method if found, or null if no matching method is identified
|
||||||
|
*/
|
||||||
private Method findSetterFor(Class<?> clazz, String fieldName, Class<?> valueType) {
|
private Method findSetterFor(Class<?> clazz, String fieldName, Class<?> valueType) {
|
||||||
String setterName = "set" + capitalize(fieldName);
|
String setterName = "set" + capitalize(fieldName);
|
||||||
for (Method m : clazz.getMethods()) {
|
for (Method m : clazz.getMethods()) {
|
||||||
@ -126,21 +189,38 @@ public class AutoViewModel<T> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the first character of the given string to lowercase, leaving the rest of the string unchanged.
|
||||||
|
* If the input string is null or empty, it is returned as-is.
|
||||||
|
*
|
||||||
|
* @param str the string to be decapitalized
|
||||||
|
* @return the decapitalized string, or the original string if it is null or empty
|
||||||
|
*/
|
||||||
private String decapitalize(String str) {
|
private String decapitalize(String str) {
|
||||||
if (str == null || str.isEmpty()) return str;
|
if (str == null || str.isEmpty()) return str;
|
||||||
return str.substring(0, 1).toLowerCase() + str.substring(1);
|
return str.substring(0, 1).toLowerCase() + str.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capitalizes the first letter of the given string and leaves the rest of the string unchanged.
|
||||||
|
* If the input string is null or empty, it is returned as is.
|
||||||
|
*
|
||||||
|
* @param str the string to be capitalized
|
||||||
|
* @return the input string with its first character converted to uppercase, or the original string if it is null or empty
|
||||||
|
*/
|
||||||
private String capitalize(String str) {
|
private String capitalize(String str) {
|
||||||
if (str == null || str.isEmpty()) return str;
|
if (str == null || str.isEmpty()) return str;
|
||||||
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the JavaFX property associated with the given field name.
|
||||||
|
*
|
||||||
|
* @param name the name of the field whose associated JavaFX property is to be returned
|
||||||
|
* @return the JavaFX {@link Property} associated with the specified field name,
|
||||||
|
* or null if no property exists for the given name
|
||||||
|
*/
|
||||||
public Property<?> getProperty(String name) {
|
public Property<?> getProperty(String name) {
|
||||||
return properties.get(name);
|
return properties.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public T getModel() {
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package de.neitzel.fx.component;
|
package de.neitzel.fx.component;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic controller used by the ComponentLoader to bind FXML views
|
* Generic controller used by the ComponentLoader to bind FXML views
|
||||||
@ -10,7 +9,6 @@ import lombok.RequiredArgsConstructor;
|
|||||||
* This controller provides access to the {@link AutoViewModel}
|
* This controller provides access to the {@link AutoViewModel}
|
||||||
* which contains JavaFX properties derived from the model.
|
* which contains JavaFX properties derived from the model.
|
||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class ComponentController {
|
public class ComponentController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,4 +18,14 @@ public class ComponentController {
|
|||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
private final AutoViewModel<?> viewModel;
|
private final AutoViewModel<?> viewModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new ComponentController instance with the specified AutoViewModel.
|
||||||
|
*
|
||||||
|
* @param viewModel the AutoViewModel containing JavaFX properties derived from the model,
|
||||||
|
* used for binding the view to the model.
|
||||||
|
*/
|
||||||
|
public ComponentController(AutoViewModel<?> viewModel) {
|
||||||
|
this.viewModel = viewModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import javafx.beans.property.Property;
|
|||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@ -26,7 +25,11 @@ import java.util.Objects;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class ComponentLoader {
|
public class ComponentLoader {
|
||||||
|
|
||||||
@Getter
|
/**
|
||||||
|
* The controller object associated with the ComponentLoader.
|
||||||
|
* It is typically used to manage the behavior and interactions of the UI components
|
||||||
|
* defined in an FXML file.
|
||||||
|
*/
|
||||||
private Object controller;
|
private Object controller;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,6 +39,18 @@ public class ComponentLoader {
|
|||||||
// default constructor only
|
// default constructor only
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an FXML file from the specified URL, sets the provided controller, and processes bindings.
|
||||||
|
* This method parses the FXML file, initializes the specified controller, and evaluates binding controls
|
||||||
|
* defined within the FXML namespace.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the root node, which must extend {@code Parent}
|
||||||
|
* @param fxmlUrl the URL of the FXML file to be loaded
|
||||||
|
* @param controller the controller to associate with the FXML file
|
||||||
|
* @param nothing an unused parameter, included for compatibility
|
||||||
|
* @return the root node loaded from the FXML file
|
||||||
|
* @throws IOException if there is an error reading the FXML file
|
||||||
|
*/
|
||||||
public static <T extends Parent> T load(URL fxmlUrl, Object controller, @SuppressWarnings("unused") String nothing) throws IOException {
|
public static <T extends Parent> T load(URL fxmlUrl, Object controller, @SuppressWarnings("unused") String nothing) throws IOException {
|
||||||
FXMLLoader loader = new FXMLLoader(fxmlUrl);
|
FXMLLoader loader = new FXMLLoader(fxmlUrl);
|
||||||
loader.setController(controller);
|
loader.setController(controller);
|
||||||
@ -56,6 +71,15 @@ public class ComponentLoader {
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely binds two properties bidirectionally while ensuring type compatibility.
|
||||||
|
* If the target property's type is not compatible with the source property's type, the binding will
|
||||||
|
* not be performed, and an error will be logged.
|
||||||
|
*
|
||||||
|
* @param source the source property to bind, must not be null
|
||||||
|
* @param target the target property to bind to; the type will be checked for compatibility at runtime
|
||||||
|
* @param <T> the type of the source property's value
|
||||||
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static <T> void bindBidirectionalSafe(@NotNull Property<T> source, Property<?> target) {
|
private static <T> void bindBidirectionalSafe(@NotNull Property<T> source, Property<?> target) {
|
||||||
try {
|
try {
|
||||||
@ -66,6 +90,15 @@ public class ComponentLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to bind the source property to the target property in a type-safe manner.
|
||||||
|
* If the target property's type is incompatible with the source property's type, the binding operation
|
||||||
|
* will be skipped, and an error message will be logged.
|
||||||
|
*
|
||||||
|
* @param source the source property to bind, must not be null
|
||||||
|
* @param target the target property to which the source will be bound
|
||||||
|
* @param <T> the type of the source property's value
|
||||||
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static <T> void bindSafe(@NotNull Property<T> source, Property<?> target) {
|
private static <T> void bindSafe(@NotNull Property<T> source, Property<?> target) {
|
||||||
try {
|
try {
|
||||||
@ -76,6 +109,14 @@ public class ComponentLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates and establishes bindings between source and target properties based on the provided
|
||||||
|
* bindings list and namespace. Supports unidirectional and bidirectional bindings, ensuring type
|
||||||
|
* compatibility before binding. Logs errors for unsuccessful binding attempts or exceptions.
|
||||||
|
*
|
||||||
|
* @param bindings a list of BindingData objects that define the source, target, and direction for the bindings
|
||||||
|
* @param namespace a map representing the namespace from which expressions in the bindings are resolved
|
||||||
|
*/
|
||||||
private static void evaluateBindings(List<BindingData> bindings, Map<String, Object> namespace) {
|
private static void evaluateBindings(List<BindingData> bindings, Map<String, Object> namespace) {
|
||||||
for (var binding : bindings) {
|
for (var binding : bindings) {
|
||||||
try {
|
try {
|
||||||
@ -114,6 +155,12 @@ public class ComponentLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the type of the value held by the specified property.
|
||||||
|
*
|
||||||
|
* @param prop the property whose value type is to be determined, must not be null
|
||||||
|
* @return the class of the property's value type; returns Object.class if the type cannot be determined
|
||||||
|
*/
|
||||||
private static Class<?> getPropertyType(Property<?> prop) {
|
private static Class<?> getPropertyType(Property<?> prop) {
|
||||||
try {
|
try {
|
||||||
Method getter = prop.getClass().getMethod("get");
|
Method getter = prop.getClass().getMethod("get");
|
||||||
@ -123,6 +170,15 @@ public class ComponentLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a dot-separated expression (e.g., "viewModel.username") by navigating through the provided namespace
|
||||||
|
* map and invoking the corresponding getter methods to access nested properties.
|
||||||
|
*
|
||||||
|
* @param expr the dot-separated expression to resolve, must not be null
|
||||||
|
* @param namespace a map containing the initial objects to resolve the expression from, must not be null
|
||||||
|
* @return the value resolved from the specified expression
|
||||||
|
* @throws Exception if an error occurs while invoking getter methods or resolving the expression
|
||||||
|
*/
|
||||||
private static Object resolveExpression(@NotNull String expr, @NotNull Map<String, Object> namespace) throws Exception {
|
private static Object resolveExpression(@NotNull String expr, @NotNull Map<String, Object> namespace) throws Exception {
|
||||||
// z.B. "viewModel.username"
|
// z.B. "viewModel.username"
|
||||||
String[] parts = expr.split("\\.");
|
String[] parts = expr.split("\\.");
|
||||||
@ -134,6 +190,12 @@ public class ComponentLoader {
|
|||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively collects all nodes in a scene graph starting from the given root node.
|
||||||
|
*
|
||||||
|
* @param root the starting node from which all descendant nodes will be collected
|
||||||
|
* @return a list containing all nodes, including the root node and its descendants
|
||||||
|
*/
|
||||||
private static @NotNull List<Node> collectAllNodes(Node root) {
|
private static @NotNull List<Node> collectAllNodes(Node root) {
|
||||||
List<Node> nodes = new ArrayList<>();
|
List<Node> nodes = new ArrayList<>();
|
||||||
nodes.add(root);
|
nodes.add(root);
|
||||||
@ -145,10 +207,35 @@ public class ComponentLoader {
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the controller associated with the ComponentLoader.
|
||||||
|
*
|
||||||
|
* @return the controller object
|
||||||
|
*/
|
||||||
|
public Object getController() {
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an FXML file and returns the root JavaFX node without binding to a data model.
|
||||||
|
* This method uses the given {@code fxmlPath} to locate and load the FXML file.
|
||||||
|
*
|
||||||
|
* @param fxmlPath the URL of the FXML file to be loaded
|
||||||
|
* @return the root JavaFX node loaded from the specified FXML file
|
||||||
|
*/
|
||||||
public Parent load(URL fxmlPath) {
|
public Parent load(URL fxmlPath) {
|
||||||
return load(null, fxmlPath);
|
return load(null, fxmlPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an FXML file and binds its elements to a generated ViewModel
|
||||||
|
* based on the given POJO model and specified FXML path.
|
||||||
|
*
|
||||||
|
* @param model the data model (POJO) to bind to the UI
|
||||||
|
* @param fxmlPath the URL path to the FXML file
|
||||||
|
* @return the root JavaFX node loaded from FXML
|
||||||
|
* @throws RuntimeException if the FXML could not be loaded
|
||||||
|
*/
|
||||||
public Parent load(Object model, URL fxmlPath) {
|
public Parent load(Object model, URL fxmlPath) {
|
||||||
try {
|
try {
|
||||||
AutoViewModel<?> viewModel = new AutoViewModel<>(model);
|
AutoViewModel<?> viewModel = new AutoViewModel<>(model);
|
||||||
@ -165,6 +252,12 @@ public class ComponentLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an FXML file and returns its root node.
|
||||||
|
*
|
||||||
|
* @param fxmlPath the relative path to the FXML file
|
||||||
|
* @return the root JavaFX node loaded from the FXML file
|
||||||
|
*/
|
||||||
public Parent load(String fxmlPath) {
|
public Parent load(String fxmlPath) {
|
||||||
return load(null, fxmlPath);
|
return load(null, fxmlPath);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,19 +27,10 @@ import java.util.Arrays;
|
|||||||
*/
|
*/
|
||||||
public class FxmlComponent extends StackPane {
|
public class FxmlComponent extends StackPane {
|
||||||
|
|
||||||
/**
|
|
||||||
* The FXML resource path to be loaded by this component. A value of {@code null} or blank disables loading.
|
|
||||||
*/
|
|
||||||
private final StringProperty fxml = new SimpleStringProperty();
|
private final StringProperty fxml = new SimpleStringProperty();
|
||||||
|
|
||||||
/**
|
|
||||||
* Human-readable binding direction hint used by outer frameworks; defaults to "unidirectional".
|
|
||||||
*/
|
|
||||||
private final StringProperty direction = new SimpleStringProperty("unidirectional");
|
private final StringProperty direction = new SimpleStringProperty("unidirectional");
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional data object that is injected into the controller of the loaded FXML when available.
|
|
||||||
*/
|
|
||||||
private final ObjectProperty<Object> data = new SimpleObjectProperty<>();
|
private final ObjectProperty<Object> data = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -6,11 +6,9 @@ import javafx.fxml.FXMLLoader;
|
|||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.control.TextInputControl;
|
import javafx.scene.control.TextInputControl;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
/**
|
/**
|
||||||
* Custom FXMLLoader that binds JavaFX controls to a GenericViewModel using metadata from FXML.
|
* Custom FXMLLoader that binds JavaFX controls to a GenericViewModel using metadata from FXML.
|
||||||
* It supports automatic binding setup based on properties defined in the FXML's node properties.
|
* It supports automatic binding setup based on properties defined in the FXML's node properties.
|
||||||
@ -24,6 +22,17 @@ public class BindingAwareFXMLLoader<T> {
|
|||||||
*/
|
*/
|
||||||
private final T model;
|
private final T model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new instance of the BindingAwareFXMLLoader class.
|
||||||
|
* This loader is designed to load FXML files and automatically bind UI elements
|
||||||
|
* to the properties of the provided model using a binding-aware approach.
|
||||||
|
*
|
||||||
|
* @param model the model object to be used for binding to the UI components
|
||||||
|
*/
|
||||||
|
public BindingAwareFXMLLoader(T model) {
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads an FXML file and performs automatic binding setup using the GenericViewModel.
|
* Loads an FXML file and performs automatic binding setup using the GenericViewModel.
|
||||||
*
|
*
|
||||||
@ -68,23 +77,36 @@ public class BindingAwareFXMLLoader<T> {
|
|||||||
if (userData instanceof String propertyName) {
|
if (userData instanceof String propertyName) {
|
||||||
BindDirection direction = getDirection(node);
|
BindDirection direction = getDirection(node);
|
||||||
|
|
||||||
if (node instanceof TextInputControl control) {
|
switch (node) {
|
||||||
|
case TextInputControl control -> {
|
||||||
Property<String> prop = viewModel.property(StringProperty.class, propertyName);
|
Property<String> prop = viewModel.property(StringProperty.class, propertyName);
|
||||||
bind(control.textProperty(), prop, direction);
|
bind(control.textProperty(), prop, direction);
|
||||||
} else if (node instanceof javafx.scene.control.Label label) {
|
}
|
||||||
|
case javafx.scene.control.Label label -> {
|
||||||
Property<String> prop = viewModel.property(StringProperty.class, propertyName);
|
Property<String> prop = viewModel.property(StringProperty.class, propertyName);
|
||||||
bind(label.textProperty(), prop, direction);
|
bind(label.textProperty(), prop, direction);
|
||||||
} else if (node instanceof javafx.scene.control.CheckBox checkBox) {
|
}
|
||||||
|
case javafx.scene.control.CheckBox checkBox -> {
|
||||||
Property<Boolean> prop = viewModel.property(javafx.beans.property.BooleanProperty.class, propertyName);
|
Property<Boolean> prop = viewModel.property(javafx.beans.property.BooleanProperty.class, propertyName);
|
||||||
bind(checkBox.selectedProperty(), prop, direction);
|
bind(checkBox.selectedProperty(), prop, direction);
|
||||||
} else if (node instanceof javafx.scene.control.Slider slider) {
|
}
|
||||||
|
case javafx.scene.control.Slider slider -> {
|
||||||
Property<Number> prop = viewModel.property(javafx.beans.property.DoubleProperty.class, propertyName);
|
Property<Number> prop = viewModel.property(javafx.beans.property.DoubleProperty.class, propertyName);
|
||||||
bind(slider.valueProperty(), prop, direction);
|
bind(slider.valueProperty(), prop, direction);
|
||||||
} else if (node instanceof javafx.scene.control.DatePicker datePicker) {
|
}
|
||||||
|
case javafx.scene.control.DatePicker datePicker -> {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Property<java.time.LocalDate> prop = (Property<java.time.LocalDate>) viewModel.property(javafx.beans.property.ObjectProperty.class, propertyName);
|
Property<java.time.LocalDate> prop =
|
||||||
|
(Property<java.time.LocalDate>) viewModel.property(
|
||||||
|
javafx.beans.property.ObjectProperty.class,
|
||||||
|
propertyName
|
||||||
|
);
|
||||||
bind(datePicker.valueProperty(), prop, direction);
|
bind(datePicker.valueProperty(), prop, direction);
|
||||||
}
|
}
|
||||||
|
default -> {
|
||||||
|
// ignore unsupported nodes
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user