Let a reused Component edit one nested Model after the container injects it.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
package de.neitzel.fx.view;
|
||||
|
||||
import de.neitzel.injection.ApplicationContext;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.util.Builder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Loads a Component from {@code ClassName.fxml} in the Component's package.
|
||||
* <p>
|
||||
* The container creates the instance. The document uses {@code fx:root} with that instance as
|
||||
* the root and as the Controller. Bindings declared in the document wait until the model
|
||||
* property holds a non-null Model. A Component that implements {@link HasViewModel} is handed
|
||||
* that ViewModel after the property is set and before those bindings run.
|
||||
*/
|
||||
final class Components {
|
||||
|
||||
private Components() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a builder for a type the container knows.
|
||||
* <p>
|
||||
* An unknown type, such as a JavaFX control, makes {@link ApplicationContext#getComponent}
|
||||
* throw {@link IllegalArgumentException}. This method returns null so FXML constructs that
|
||||
* type itself. A failure while creating a known Component is not caught.
|
||||
*
|
||||
* @param type the type FXML is about to construct
|
||||
* @param context the container that creates Components
|
||||
* @return a builder for a Component, or {@code null} when the type is not a Component
|
||||
*/
|
||||
static Builder<?> builder(Class<?> type, ApplicationContext context) {
|
||||
Object component;
|
||||
try {
|
||||
component = context.getComponent(type);
|
||||
} catch (IllegalArgumentException notAComponent) {
|
||||
return null;
|
||||
}
|
||||
return () -> load(component, context);
|
||||
}
|
||||
|
||||
private static Object load(Object component, ApplicationContext context) {
|
||||
Class<?> type = component.getClass();
|
||||
String document = type.getSimpleName() + ".fxml";
|
||||
URL fxml = type.getResource(document);
|
||||
if (fxml == null) {
|
||||
throw new IllegalArgumentException("No component document " + document + " for " + type.getName());
|
||||
}
|
||||
LoadSession nested = LoadSession.nest();
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(fxml);
|
||||
loader.setRoot(component);
|
||||
loader.setController(component);
|
||||
loader.setBuilderFactory(nestedType -> builder(nestedType, context));
|
||||
loader.load();
|
||||
watchModel(component, List.copyOf(nested.bindingLists()), loader.getNamespace(), context);
|
||||
return component;
|
||||
} catch (IOException failure) {
|
||||
throw new IllegalStateException("Could not load component " + fxml, failure);
|
||||
} finally {
|
||||
LoadSession.unnest();
|
||||
}
|
||||
}
|
||||
|
||||
private static void watchModel(Object component, List<BindingList> lists, Map<String, Object> namespace,
|
||||
ApplicationContext context) {
|
||||
Property<Object> model = modelProperty(component);
|
||||
boolean[] applied = {false};
|
||||
model.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == null || applied[0]) {
|
||||
return;
|
||||
}
|
||||
applied[0] = true;
|
||||
ViewModel viewModel = ViewModels.of(newValue);
|
||||
if (component instanceof HasViewModel hasViewModel) {
|
||||
hasViewModel.setViewModel(viewModel);
|
||||
}
|
||||
Parent root = (Parent) component;
|
||||
for (BindingList list : lists) {
|
||||
list.apply(namespace, viewModel, component, root, context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Property<Object> modelProperty(Object component) {
|
||||
try {
|
||||
Method method = component.getClass().getMethod("modelProperty");
|
||||
Object value = method.invoke(component);
|
||||
if (!(value instanceof Property<?> property)) {
|
||||
throw new IllegalArgumentException("modelProperty did not return a property");
|
||||
}
|
||||
return (Property<Object>) property;
|
||||
} catch (NoSuchMethodException failure) {
|
||||
throw new IllegalArgumentException("No model property on " + component.getClass().getName(), failure);
|
||||
} catch (IllegalAccessException | InvocationTargetException failure) {
|
||||
throw new IllegalStateException("Could not read model property", failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.neitzel.fx.view;
|
||||
|
||||
/**
|
||||
* Receives the ViewModel selected for a Controller or a Component.
|
||||
* <p>
|
||||
* A Component is called after its model property is set to a non-null Model and before that
|
||||
* Component's bindings run.
|
||||
*/
|
||||
public interface HasViewModel {
|
||||
|
||||
/**
|
||||
* Receives the ViewModel for the Model this object is bound to.
|
||||
*
|
||||
* @param viewModel the selected ViewModel
|
||||
*/
|
||||
void setViewModel(ViewModel viewModel);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package de.neitzel.fx.view;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -8,11 +10,12 @@ import java.util.List;
|
||||
* <p>
|
||||
* FXML objects declared in {@code fx:define} are not scene-graph children and have no
|
||||
* {@code fx:id} in the accepted document shape, so each {@link BindingList} registers
|
||||
* itself here from its constructor.
|
||||
* itself here from its constructor. A Component document opens a nested session, so its
|
||||
* lists stay off the parent session.
|
||||
*/
|
||||
final class LoadSession {
|
||||
|
||||
private static final ThreadLocal<LoadSession> CURRENT = new ThreadLocal<>();
|
||||
private static final ThreadLocal<Deque<LoadSession>> STACK = new ThreadLocal<>();
|
||||
|
||||
private final List<BindingList> bindingLists = new ArrayList<>();
|
||||
|
||||
@@ -25,16 +28,38 @@ final class LoadSession {
|
||||
* @return the open session
|
||||
*/
|
||||
static LoadSession open() {
|
||||
Deque<LoadSession> stack = new ArrayDeque<>();
|
||||
LoadSession session = new LoadSession();
|
||||
CURRENT.set(session);
|
||||
stack.push(session);
|
||||
STACK.set(stack);
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a nested session for a Component document.
|
||||
* <p>
|
||||
* Binding lists created until {@link #unnest()} register here, not on the parent session.
|
||||
*
|
||||
* @return the nested session
|
||||
*/
|
||||
static LoadSession nest() {
|
||||
LoadSession session = new LoadSession();
|
||||
STACK.get().push(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the session that was current before {@link #nest()}.
|
||||
*/
|
||||
static void unnest() {
|
||||
STACK.get().pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the session for this thread.
|
||||
*/
|
||||
static void close() {
|
||||
CURRENT.remove();
|
||||
STACK.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,10 +68,11 @@ final class LoadSession {
|
||||
* @param bindingList the list FXML just constructed
|
||||
*/
|
||||
static void register(BindingList bindingList) {
|
||||
LoadSession session = CURRENT.get();
|
||||
if (session != null) {
|
||||
session.bindingLists.add(bindingList);
|
||||
Deque<LoadSession> stack = STACK.get();
|
||||
if (stack == null || stack.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
stack.peek().bindingLists.add(bindingList);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,12 @@ public final class ViewLoader {
|
||||
* A path that is not a read-write Attribute fails the load. The context creates a Controller,
|
||||
* and a Converter when a Direct binding names one. A converter that cannot produce a value
|
||||
* keeps the previous value and records one error Message. It does not fail the load.
|
||||
* A parent document names a Component class as an element. The context creates that Component
|
||||
* and injects its services. The Component document {@code ClassName.fxml}, in the same package,
|
||||
* is loaded with the Component as {@code fx:root} and as the Controller. That document's
|
||||
* bindings are not applied during this load. They run when the Component's model property
|
||||
* becomes a non-null Model. A Component that implements {@link HasViewModel} receives that
|
||||
* ViewModel after the property is set and before those bindings run.
|
||||
*
|
||||
* @param fxml the FXML document
|
||||
* @param model the Model instance
|
||||
@@ -47,6 +53,7 @@ public final class ViewLoader {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(fxml);
|
||||
loader.setControllerFactory(type -> controller(context, type));
|
||||
loader.setBuilderFactory(type -> Components.builder(type, context));
|
||||
Object loaded = loader.load();
|
||||
if (!(loaded instanceof Parent root)) {
|
||||
throw new IllegalArgumentException("View root must be a parent");
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package de.neitzel.fx.view;
|
||||
|
||||
import de.neitzel.fx.view.component.AddressForm;
|
||||
import de.neitzel.fx.view.component.Envelope;
|
||||
import de.neitzel.fx.view.component.Letter;
|
||||
import de.neitzel.fx.view.fixture.Address;
|
||||
import de.neitzel.fx.view.fixture.Person;
|
||||
import de.neitzel.injection.ApplicationContext;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
class EditAddressInAComponentTest {
|
||||
|
||||
@BeforeAll
|
||||
static void startJavaFx() throws InterruptedException {
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
try {
|
||||
Platform.startup(started::countDown);
|
||||
} catch (IllegalStateException alreadyStarted) {
|
||||
started.countDown();
|
||||
}
|
||||
if (!started.await(10, TimeUnit.SECONDS)) {
|
||||
fail("JavaFX toolkit did not start");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void editingTheStreetUpdatesThatAddress() throws Exception {
|
||||
onFx(() -> {
|
||||
Address address = new Address("Main");
|
||||
Person person = new Person("Ada");
|
||||
person.setAddress(address);
|
||||
URL fxml = EditAddressInAComponentTest.class.getResource("address-component.fxml");
|
||||
assertNotNull(fxml);
|
||||
ApplicationContext context = new ApplicationContext("de.neitzel.fx.view.component");
|
||||
LoadedView loaded = ViewLoader.load(fxml, person, context);
|
||||
AddressForm form = (AddressForm) loaded.getRoot().lookup("#addressForm");
|
||||
assertNotNull(form);
|
||||
assertSame(address, form.getModel());
|
||||
TextField streetField = (TextField) form.lookup("#streetField");
|
||||
assertEquals("Main", streetField.getText());
|
||||
streetField.setText("Oak");
|
||||
assertEquals("Oak", address.getStreet());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullAddressLeavesTheModelUnsetAndKeepsTheStreetText() throws Exception {
|
||||
onFx(() -> {
|
||||
Person person = new Person("Ada");
|
||||
URL fxml = EditAddressInAComponentTest.class.getResource("address-component.fxml");
|
||||
assertNotNull(fxml);
|
||||
ApplicationContext context = new ApplicationContext("de.neitzel.fx.view.component");
|
||||
LoadedView loaded = ViewLoader.load(fxml, person, context);
|
||||
AddressForm form = (AddressForm) loaded.getRoot().lookup("#addressForm");
|
||||
assertNotNull(form);
|
||||
assertNull(form.getModel());
|
||||
TextField streetField = (TextField) form.lookup("#streetField");
|
||||
assertEquals("kept", streetField.getText());
|
||||
assertNull(person.getAddress());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void streetLabelShowsTheInjectedPrefix() throws Exception {
|
||||
onFx(() -> {
|
||||
Person person = new Person("Ada");
|
||||
person.setAddress(new Address("Main"));
|
||||
URL fxml = EditAddressInAComponentTest.class.getResource("address-component.fxml");
|
||||
assertNotNull(fxml);
|
||||
ApplicationContext context = new ApplicationContext("de.neitzel.fx.view.component");
|
||||
LoadedView loaded = ViewLoader.load(fxml, person, context);
|
||||
AddressForm form = (AddressForm) loaded.getRoot().lookup("#addressForm");
|
||||
Label streetLabel = (Label) form.lookup("#streetLabel");
|
||||
assertEquals("Lane", streetLabel.getText());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void componentReceivesTheAddressViewModelBeforeItsBindingsRun() throws Exception {
|
||||
onFx(() -> {
|
||||
Address address = new Address("Main");
|
||||
Person person = new Person("Ada");
|
||||
person.setAddress(address);
|
||||
URL fxml = EditAddressInAComponentTest.class.getResource("address-component.fxml");
|
||||
assertNotNull(fxml);
|
||||
ApplicationContext context = new ApplicationContext("de.neitzel.fx.view.component");
|
||||
LoadedView loaded = ViewLoader.load(fxml, person, context);
|
||||
AddressForm form = (AddressForm) loaded.getRoot().lookup("#addressForm");
|
||||
assertSame(loaded.property("address").getValue(), form.receivedViewModel());
|
||||
assertEquals("kept", form.streetTextAtHandOff());
|
||||
TextField streetField = (TextField) form.lookup("#streetField");
|
||||
assertEquals("Main", streetField.getText());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedComponentShowsTheInnerStreet() throws Exception {
|
||||
onFx(() -> {
|
||||
Address address = new Address("Main");
|
||||
Letter letter = new Letter(new Envelope(address));
|
||||
URL fxml = EditAddressInAComponentTest.class.getResource("letter-envelope.fxml");
|
||||
assertNotNull(fxml);
|
||||
ApplicationContext context = new ApplicationContext("de.neitzel.fx.view.component");
|
||||
LoadedView loaded = ViewLoader.load(fxml, letter, context);
|
||||
TextField streetField = (TextField) loaded.getRoot().lookup("#streetField");
|
||||
assertEquals("Main", streetField.getText());
|
||||
streetField.setText("Oak");
|
||||
assertEquals("Oak", address.getStreet());
|
||||
});
|
||||
}
|
||||
|
||||
private static void onFx(FxAction action) throws Exception {
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
action.run();
|
||||
return;
|
||||
}
|
||||
AtomicReference<Throwable> thrown = new AtomicReference<>();
|
||||
CountDownLatch done = new CountDownLatch(1);
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
action.run();
|
||||
} catch (Throwable failure) {
|
||||
thrown.set(failure);
|
||||
} finally {
|
||||
done.countDown();
|
||||
}
|
||||
});
|
||||
if (!done.await(10, TimeUnit.SECONDS)) {
|
||||
fail("JavaFX thread did not finish");
|
||||
}
|
||||
Throwable failure = thrown.get();
|
||||
if (failure instanceof Exception exception) {
|
||||
throw exception;
|
||||
}
|
||||
if (failure instanceof Error error) {
|
||||
throw error;
|
||||
}
|
||||
if (failure != null) {
|
||||
throw new AssertionError(failure);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface FxAction {
|
||||
void run() throws Exception;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package de.neitzel.fx.view.component;
|
||||
|
||||
import de.neitzel.fx.view.HasViewModel;
|
||||
import de.neitzel.fx.view.ViewModel;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
/**
|
||||
* Component that shows one address.
|
||||
* <p>
|
||||
* The container injects the caption service. The street label shows that service's prefix.
|
||||
*/
|
||||
@Singleton
|
||||
public class AddressForm extends VBox implements HasViewModel {
|
||||
|
||||
private final StreetCaption captions;
|
||||
private final ObjectProperty<Object> model = new SimpleObjectProperty<>(this, "model");
|
||||
|
||||
@FXML
|
||||
private Label streetLabel;
|
||||
|
||||
@FXML
|
||||
private TextField streetField;
|
||||
|
||||
private ViewModel receivedViewModel;
|
||||
private String streetTextAtHandOff;
|
||||
|
||||
/**
|
||||
* Creates a form that reads its street label from the injected caption.
|
||||
*
|
||||
* @param captions the service that supplies the street prefix
|
||||
*/
|
||||
@Inject
|
||||
public AddressForm(StreetCaption captions) {
|
||||
this.captions = captions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the injected prefix onto the street label.
|
||||
*/
|
||||
@FXML
|
||||
public void initialize() {
|
||||
streetLabel.setText(captions.prefix());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the model property a parent View sets with a Direct binding.
|
||||
*
|
||||
* @return the model property
|
||||
*/
|
||||
public ObjectProperty<Object> modelProperty() {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address this component shows.
|
||||
*
|
||||
* @return the address, or {@code null} when none is set
|
||||
*/
|
||||
public Object getModel() {
|
||||
return model.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the address this component shows.
|
||||
*
|
||||
* @param model the address
|
||||
*/
|
||||
public void setModel(Object model) {
|
||||
this.model.set(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the ViewModel handed to this component, and the street text at that moment.
|
||||
*
|
||||
* @param viewModel the ViewModel selected for the address
|
||||
*/
|
||||
@Override
|
||||
public void setViewModel(ViewModel viewModel) {
|
||||
this.receivedViewModel = viewModel;
|
||||
this.streetTextAtHandOff = streetField.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ViewModel this component was handed.
|
||||
*
|
||||
* @return the ViewModel, or {@code null} when none was handed over
|
||||
*/
|
||||
public ViewModel receivedViewModel() {
|
||||
return receivedViewModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the street text observed when the ViewModel was handed over.
|
||||
*
|
||||
* @return the street text at hand-off
|
||||
*/
|
||||
public String streetTextAtHandOff() {
|
||||
return streetTextAtHandOff;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.neitzel.fx.view.component;
|
||||
|
||||
import de.neitzel.fx.view.fixture.Address;
|
||||
|
||||
/**
|
||||
* Ordinary model that holds one address, used to nest an address component.
|
||||
*/
|
||||
public class Envelope {
|
||||
|
||||
private Address address;
|
||||
|
||||
/**
|
||||
* Creates an envelope for the given address.
|
||||
*
|
||||
* @param address the address inside the envelope
|
||||
*/
|
||||
public Envelope(Address address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address.
|
||||
*
|
||||
* @return the address
|
||||
*/
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the address.
|
||||
*
|
||||
* @param address the address
|
||||
*/
|
||||
public void setAddress(Address address) {
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package de.neitzel.fx.view.component;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
/**
|
||||
* Component that shows one envelope and the address component inside it.
|
||||
*/
|
||||
@Singleton
|
||||
public class EnvelopeForm extends VBox {
|
||||
|
||||
private final ObjectProperty<Object> model = new SimpleObjectProperty<>(this, "model");
|
||||
|
||||
/**
|
||||
* Returns the model property a parent View sets with a Direct binding.
|
||||
*
|
||||
* @return the model property
|
||||
*/
|
||||
public ObjectProperty<Object> modelProperty() {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the envelope this component shows.
|
||||
*
|
||||
* @return the envelope, or {@code null} when none is set
|
||||
*/
|
||||
public Object getModel() {
|
||||
return model.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the envelope this component shows.
|
||||
*
|
||||
* @param model the envelope
|
||||
*/
|
||||
public void setModel(Object model) {
|
||||
this.model.set(model);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package de.neitzel.fx.view.component;
|
||||
|
||||
/**
|
||||
* Ordinary model that holds one envelope.
|
||||
*/
|
||||
public class Letter {
|
||||
|
||||
private Envelope envelope;
|
||||
|
||||
/**
|
||||
* Creates a letter for the given envelope.
|
||||
*
|
||||
* @param envelope the envelope
|
||||
*/
|
||||
public Letter(Envelope envelope) {
|
||||
this.envelope = envelope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the envelope.
|
||||
*
|
||||
* @return the envelope
|
||||
*/
|
||||
public Envelope getEnvelope() {
|
||||
return envelope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the envelope.
|
||||
*
|
||||
* @param envelope the envelope
|
||||
*/
|
||||
public void setEnvelope(Envelope envelope) {
|
||||
this.envelope = envelope;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package de.neitzel.fx.view.component;
|
||||
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Service whose text an address component must show, so a test can see that injection ran.
|
||||
*/
|
||||
@Singleton
|
||||
public class StreetCaption {
|
||||
|
||||
/**
|
||||
* Returns the prefix written on the street label.
|
||||
*
|
||||
* @return the prefix
|
||||
*/
|
||||
public String prefix() {
|
||||
return "Lane";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import de.neitzel.fx.view.BindingList?>
|
||||
<?import de.neitzel.fx.view.DirectBinding?>
|
||||
<?import de.neitzel.fx.view.component.AddressForm?>
|
||||
|
||||
<StackPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml">
|
||||
<fx:define>
|
||||
<BindingList>
|
||||
<DirectBinding control="addressForm" property="model" path="address" direction="both"/>
|
||||
</BindingList>
|
||||
</fx:define>
|
||||
<AddressForm fx:id="addressForm"/>
|
||||
</StackPane>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import de.neitzel.fx.view.BindingList?>
|
||||
<?import de.neitzel.fx.view.DirectBinding?>
|
||||
|
||||
<fx:root type="javafx.scene.layout.VBox" xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
<fx:define>
|
||||
<BindingList>
|
||||
<DirectBinding control="streetField" property="text" path="street" direction="both"/>
|
||||
</BindingList>
|
||||
</fx:define>
|
||||
<Label fx:id="streetLabel" id="streetLabel"/>
|
||||
<TextField fx:id="streetField" id="streetField" text="kept"/>
|
||||
</fx:root>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import de.neitzel.fx.view.BindingList?>
|
||||
<?import de.neitzel.fx.view.DirectBinding?>
|
||||
<?import de.neitzel.fx.view.component.AddressForm?>
|
||||
|
||||
<fx:root type="javafx.scene.layout.VBox" xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
<fx:define>
|
||||
<BindingList>
|
||||
<DirectBinding control="addressForm" property="model" path="address" direction="both"/>
|
||||
</BindingList>
|
||||
</fx:define>
|
||||
<AddressForm fx:id="addressForm"/>
|
||||
</fx:root>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import de.neitzel.fx.view.BindingList?>
|
||||
<?import de.neitzel.fx.view.DirectBinding?>
|
||||
<?import de.neitzel.fx.view.component.EnvelopeForm?>
|
||||
|
||||
<StackPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml">
|
||||
<fx:define>
|
||||
<BindingList>
|
||||
<DirectBinding control="envelopeForm" property="model" path="envelope" direction="both"/>
|
||||
</BindingList>
|
||||
</fx:define>
|
||||
<EnvelopeForm fx:id="envelopeForm"/>
|
||||
</StackPane>
|
||||
Reference in New Issue
Block a user