You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I started creating subclasses for various builders and found it limiting that all builder methods, e.g. disable() or visible(), are final. Sometimes there's a point of overriding them for a specific type of builder. So it would be nice if those methods were not final
I'm a bit hesitant here to change this. I find it very important that users can know what to expect, and changing the meaning by allowing overrides can easily break that.
Also I'm usually rather strict about inheritance and public / protected members; they should either be final, abstract or empty. This is to avoid entangling the super and sub class implementations (ie. if the super class relies on things in an overridable method, then a subclass can break its invariants). So, I'd be willing to add "hook" methods (ie. empty / abstract) but I'm unsure what you are trying to achieve.
The "leaf" builder types are all final (as you can't extend these properly due to lack of generics) so making those non-final would not really help. What I can do here perhaps to help you a bit is to make another type in between the leaf builder that still has the correct generics (and includes all the methods). So the hierarchy would be like ButtonBuilder (empty mostly, no generic parameters) -> AbstractExtendibleButtonBuilder (contains all the button specific stuff that wasn't in button base, generic) -> AbstractButtonBaseBuilder (the base button stuff according to JavaFX ButtonBase type)
So if you can explain what you are trying to do then perhaps I can think along.
I think there maybe some overlap with what I'm working on currently, allowing your to customize (some) builder methods with a strategy. For example, it would allow you to change the meaning of all "text" providing methods (like text, promptText, accessibleText) by having them delegate to a user provided TextStrategy. For example, to do localization you could then do this:
FXDefaultsdefaults = FXDefaults.DEFAULTS.textStrategy((text, node, setter) -> {
// Example: uppercase text for German locale, but you can do resource look-upsif (LOCALE.equals(Locale.GERMAN)) {
setter.accept(text.toUpperCase());
}
else {
setter.accept(text);
}
});
// Apply defaults to a panePanes.vbox("root").defaults(defaults).nodes(
FX.label().text("hello"),
FX.textField().promptText("enter.name")
);
So at any level, you can have a different set of defaults (if desired) which will inherit defaults from ancestor nodes (or the scene) or use a global default if not overridden.
The FXDefaults class will contain several strategies, is immutable (but you can derive new ones easily) and the strategies are applied at the moment the Node is attached to the Scene. Strategies inherit, so for a specific piece of UI you could override them. Or you could never override them and use global defaults (just set these once at the start of your application before building any UI's). The override is there more for cases where you do want to do something different for a different scene, but I suspect a lot of apps will just set global strategies once.
This can even be fully dynamic (having your app instantly change all labels to suit a different locale) by registering a listener on something as part of the strategy you provide.
The strategies look interesting, I am looking forward to trying that out. You can also do snapshot releases without having to get the full javadocs etc ready to gather some feedback on how it handles.
In my specific case, I was trying to make various common builders method like visible() to be thread-safe, meaning that I could set the property from any thread without it causing issues
The strategies look interesting, I am looking forward to trying that out. You can also do snapshot releases without having to get the full javadocs etc ready to gather some feedback on how it handles.
I'll look into this, but none of this is even committed (I suppose I could make a branch but it is still very much in flux)... hoping to get something committed this weekend as I think I may have found a nice solution that ticks all the boxes I wanted.
I've been experimenting with these a lot... have tried like 5 different solutions so far, none of which I really liked (some required scene listeners to resolve strategies just in time, which I think is too heavy handed).
However, I think I'm close to landing on something now (different from the previous version I showed). This new variant doesn't require any last-minute execution of strategies, but is still flexible enough to override strategies at any point in your Node tree. Here are two sample strategies (names subject to change):
ImmutableContext context = ImmutableContext.DEFAULT
.textStrategy((node, inputString) -> {
Broadcasts.addHandler(node, LocaleChangedEvent.LOCALE_CHANGED, e -> {
if(e.locale().equals(Locale.GERMAN)) {
node.setText(inputString.toUpperCase());
}
else {
node.setText(inputString);
}
});
})
.childrenStrategy((ctx, node, children) -> {
node.getChildren().setAll(children.stream()
.filter(Objects::nonNull)
.map(n -> switch(n) {
case String s when s.equals("Hi") -> FX.label().text("Bye").build(ctx);
case String s -> FX.label().text(s.toUpperCase()).build(ctx);
default -> NodeBuilder.toNode(ctx, n);
})
.toList()
);
});
The first one will listen for LocaleChangedEvents and modify all texts to a different locale (you can also do this with a static locale, without the Broadcast listener stuff I'm experimenting with if you don't mind restarting/reopening windows).
The second one allows you to see everything that you added to say Panes.hbox().nodes( .... ) -- so if you added nodes(1, "A", "B", 3) then that's what you get. This enables you to decide for yourself if a plain String should be converted to a label or something else (it was hard coded before).
The way it works is that you can provide this context object on build methods. Normally you'd probably only want that at Scene level so something like:
FX.scene().root( ...build UI here ...).build(context);
But at any point you can also provide a context to any node you want to override something in the middle somewhere by calling its build method. I'm still seeing if this has to be done through the build method (as that finalizes everything) or if this could be delayed and just have a simpler context() method (that would require a field though, currently no extra field is needed).
Also, if you think you only need a single context for your entire application, there will be a way to override the "default" context once at the start, and then you won't have to provide a context anywhere.
In my specific case, I was trying to make various common builders method like visible() to be thread-safe, meaning that I could set the property from any thread without it causing issues
The builder is however gone after you construct the node, and has no knowledge of the node that it last constructed. Calling visible on it again won't do anything.
If I wanted to build something like this, I'd probably make a small utility to provide thread-safe variants of properties, something like this:
public final ObservableValue<Boolean> textVisible = ThreadSafeProperties.bool();
// then later:
FX.label().visible(textVisible);
Now you can manipulate the label's visibility from any thread.
Yeah the utility approach for the thread-safe properties is also possible, but I always had the issue of sometimes forgetting to do that. And in some cases, modifications from the wrong thread only cause issues rarely. So it's not immediately detected
I thought it would be easier to just centralize it for my builders so I can't do it wrong
Yeah the utility approach for the thread-safe properties is also possible, but I always had the issue of sometimes forgetting to do that. And in some cases, modifications from the wrong thread only cause issues rarely. So it's not immediately detected
I thought it would be easier to just centralize it for my builders so I can't do it wrong
Perhaps when you call a method that accepts a property, I can also call a strategy (bindingStrategy), and you can put a wrapper around the property about to be bound or something. I think there are many possibilities, so I'm eager to get something worked out for this.
Activity
hjohn commentedon Jan 24, 2026
I'm a bit hesitant here to change this. I find it very important that users can know what to expect, and changing the meaning by allowing overrides can easily break that.
Also I'm usually rather strict about inheritance and
public/protectedmembers; they should either befinal,abstractor empty. This is to avoid entangling the super and sub class implementations (ie. if the super class relies on things in an overridable method, then a subclass can break its invariants). So, I'd be willing to add "hook" methods (ie. empty / abstract) but I'm unsure what you are trying to achieve.The "leaf" builder types are all final (as you can't extend these properly due to lack of generics) so making those non-final would not really help. What I can do here perhaps to help you a bit is to make another type in between the leaf builder that still has the correct generics (and includes all the methods). So the hierarchy would be like
ButtonBuilder(empty mostly, no generic parameters) ->AbstractExtendibleButtonBuilder(contains all the button specific stuff that wasn't in button base, generic) ->AbstractButtonBaseBuilder(the base button stuff according to JavaFX ButtonBase type)So if you can explain what you are trying to do then perhaps I can think along.
I think there maybe some overlap with what I'm working on currently, allowing your to customize (some) builder methods with a strategy. For example, it would allow you to change the meaning of all "text" providing methods (like
text,promptText,accessibleText) by having them delegate to a user providedTextStrategy. For example, to do localization you could then do this:So at any level, you can have a different set of defaults (if desired) which will inherit defaults from ancestor nodes (or the scene) or use a global default if not overridden.
The
FXDefaultsclass will contain several strategies, is immutable (but you can derive new ones easily) and the strategies are applied at the moment the Node is attached to the Scene. Strategies inherit, so for a specific piece of UI you could override them. Or you could never override them and use global defaults (just set these once at the start of your application before building any UI's). The override is there more for cases where you do want to do something different for a different scene, but I suspect a lot of apps will just set global strategies once.This can even be fully dynamic (having your app instantly change all labels to suit a different locale) by registering a listener on something as part of the strategy you provide.
crschnick commentedon Jan 30, 2026
The strategies look interesting, I am looking forward to trying that out. You can also do snapshot releases without having to get the full javadocs etc ready to gather some feedback on how it handles.
In my specific case, I was trying to make various common builders method like visible() to be thread-safe, meaning that I could set the property from any thread without it causing issues
hjohn commentedon Jan 30, 2026
I'll look into this, but none of this is even committed (I suppose I could make a branch but it is still very much in flux)... hoping to get something committed this weekend as I think I may have found a nice solution that ticks all the boxes I wanted.
I've been experimenting with these a lot... have tried like 5 different solutions so far, none of which I really liked (some required scene listeners to resolve strategies just in time, which I think is too heavy handed).
However, I think I'm close to landing on something now (different from the previous version I showed). This new variant doesn't require any last-minute execution of strategies, but is still flexible enough to override strategies at any point in your Node tree. Here are two sample strategies (names subject to change):
The first one will listen for
LocaleChangedEvents and modify all texts to a different locale (you can also do this with a static locale, without theBroadcastlistener stuff I'm experimenting with if you don't mind restarting/reopening windows).The second one allows you to see everything that you added to say
Panes.hbox().nodes( .... )-- so if you addednodes(1, "A", "B", 3)then that's what you get. This enables you to decide for yourself if a plainStringshould be converted to a label or something else (it was hard coded before).The way it works is that you can provide this context object on
buildmethods. Normally you'd probably only want that atScenelevel so something like:But at any point you can also provide a context to any node you want to override something in the middle somewhere by calling its
buildmethod. I'm still seeing if this has to be done through thebuildmethod (as that finalizes everything) or if this could be delayed and just have a simplercontext()method (that would require a field though, currently no extra field is needed).Also, if you think you only need a single context for your entire application, there will be a way to override the "default" context once at the start, and then you won't have to provide a context anywhere.
The builder is however gone after you construct the node, and has no knowledge of the node that it last constructed. Calling
visibleon it again won't do anything.If I wanted to build something like this, I'd probably make a small utility to provide thread-safe variants of properties, something like this:
Now you can manipulate the label's visibility from any thread.
crschnick commentedon Jan 30, 2026
Yeah the utility approach for the thread-safe properties is also possible, but I always had the issue of sometimes forgetting to do that. And in some cases, modifications from the wrong thread only cause issues rarely. So it's not immediately detected
I thought it would be easier to just centralize it for my builders so I can't do it wrong
hjohn commentedon Jan 30, 2026
Perhaps when you call a method that accepts a property, I can also call a strategy (
bindingStrategy), and you can put a wrapper around the property about to be bound or something. I think there are many possibilities, so I'm eager to get something worked out for this.