[REVIEW] Make controller instantiation customizable

Greg Brown greg.x.brown at oracle.com
Thu Dec 15 08:06:02 PST 2011


> You have designed everything around the FXMLLoader creating classes, I specifically am trying to get around this. I'm looking for a compromise where your stuff all still works and the back door is left open for me to do my stuff. 

But that's exactly what ControllerFactory and BuilderFactory are designed for. They delegate object creation to an external entity. FXMLLoader doesn't get involved at all when factories are used.

> FXML is not 'compiled' but it is most definitely 'typed'.

Fair enough.

> But as we have already discussed, you can pass values to the loader via the namespace, even before you call the load() method.
> 
> Yes but the namespace is untyped. There is nothing in the FXML that tells the tooling what type each of the namespace variable are, the tool cannot validate this for example: 
> 
> <Label text=${myNameSpaceVariable.someTextValue}/>
...
> So if we start using Namespaces for this sort of stuff we lose tooling support

Not really. We can validate that Label is a valid type, and that "text" is a valid property of that type. What we can't determine at compile time is whether the contents of the text attribute evaluate to a value that is appropriate for the text property. But that's not strictly a limitation of FXML - it applies to any dynamically evaluated content (e.g. script code).

> <Button onAction=${controller.soSomething}/>
> 
> But I don't believe I can access the event if I want it, using this approach?

Yes, you can:

<Button onAction="controller.doSomething(event)"/>

As in HTML, the namespace of an event handler contains an implicit "event" variable containing the event object that was fired.

> Bi-directional binding (assuming it works on namespace variables?) will reduce the need to access the fields directly

It does work on namespace variables.

> What I'd need is something like this: 
> 
> <Button fx:assignTo=${myNameSpaceVariable.myButton}/>
> 
> Which would assign the Button to the bean property called myButton on a variable defined in the namespace with the name 'myNameSpaceButton'. 

FYI, you can do this with script:

<Button fx:id="myButton"/>

<fx:script>
myNameSpaceVariable.myButton = myButton;
</fx:script>

> Again, FXML isn't typed, so it can't really "implement" anything. Also note that the current set of tooling doesn't actually get involved in any code generation, only markup. That's one of the benefits of the FXML approach - it avoids the messiness typically associated with GUI builder-generated code.
> 
> I admit, I'm surprised by this. So the current tool doesn't create a Controller class and automatically put @FXML fields on it for things I define in fx:id in my FXML? What about auto generating methods that are defined in onAction="#doSomething". 

My understanding is that it does not, but I could be wrong about that.

> Does the RAD tool use the fx:controller attribute for anything?   

Not sure.

> I'm really struggling to understand the use case.
> 
> Have you read this: http://www.zenjava.com/2011/12/11/javafx-and-mvp-a-smorgasbord-of-design-patterns/
> 
> I want to achieve a pure MVP pattern without the compromises outlined in option #4 (the FXML comprompise). 

> Check out option #2: interfaces for the purists. In a perfect world this pattern could be implemented with FXML being a hot-swap for the view impl (e.g. 'ContactViewImpl').

Heh. I definitely I fall into the "unnecessary bloat" camp here. I have never been a fan of the "everything should be an interface" approach. To me, it just creates a bunch of noise. But I digress.

For better or worse, the view and controller in FXML are (intentionally) fairly tightly coupled. As I have said before, the controller's primary purpose is to provide the backing logic for the view, just as the "code behind" class does in XAML. I understand that this may not seem ideal from a "purist" perspective, but FXML isn't an attempt to define a "pure" MVC framework. It is meant to provide a convenient means for associating objects defined in markup with the logic that defines the behavior of those objects. And in some cases, allowing the view to talk to the controller directly is desirable.

> Couldn't you do something like this, for example? 
> 
> public interface MyController {
>    ...
> }
> 
> public class MyControllerImpl implements MyController {
>    ...
> }
> 
> <HBox fx:controller="com.foo.MyController">
>> </HBox>
> 
> Your controller factory's implementation of getController() could return an instance of MyControllerImpl or whatever implementation makes sense.
> 
> On a technical front this won't work at the moment because I don't think I can define an @FXML field on an interface that an fx:id would map to. I don't believe @FXML will map back to a method for this and interfaces can't define fields?

As I mentioned in another post, FXMLLoader will perform reflection on the implementation type, not the interface type. So you can still use @FXML even with interfaces.

> Also, note that this is going to break your FXMLLoader if a factory is not defined, since it can't now instantiate the controller (since it is an interface).

That is correct. However, it doesn't "break" the loader - if the caller doesn't provide a factory in this case, it is just developer error.

> So from your point of view I would have thought this is just as bad as the 'default' controller defined above. It won't run without a factory, and if the RAD tool is trying to instantiate controllers to preview the GUI (not sure if this is what it does or not) then it will fail too. 

See the "staticLoad" flag I mentioned yesterday. When this is set, FXMLLoader won't attempt to instantiate the loader, allowing you to load the FXML content for layout and preview, but no logic gets executed.





More information about the openjfx-dev mailing list