1 2 3 Previous Next 31 Replies Latest reply: Mar 2, 2012 4:58 PM by bvm Go to original post RSS
      • 15. Re: Proposal for FXML defined custom controls
        bvm
        Greg Brown wrote:
        I ran into the same issue this morning. I believe it was fixed last week, but I'm not sure if the fix has made it into a public build yet. Try updating to the latest beta to see if that helps.
        It works in b14. But... I took the next obvious step of putting the application (the 3 custom* files) into a different package from the toolkit (the two content* files), and it breaks with the highly informative "Exception in Application start method", which turns out to be a NullPointerException in this method:
        @Override
        protected String getUserAgentStylesheet() {
            return getClass().getResource("content_control.css").toString();
        }
        If I move content_control.css into the application package, it works again, but surely I don't want to have to put content_control.css into every consumer's path. I don't understand enough about how css is hooked up to figure out the right thing here. Should it be ContentControl.class.getResource(...)?
        • 16. Re: Proposal for FXML defined custom controls
          bvm
          Greg Brown wrote:
          As for why we want to make the control and its controller the same class, it's just simpler. It's a familiar programming model if you come from the WPF world.
          I can understand that. But that model makes more sense in WPF, where XAML + CS is compiled into a single class. Since FXML isn't compiled, it doesn't make quite as much sense here. So it just seems like you're jumping through hoops for the sake of "familiarity", but not really gaining that much.
          It feels to me like you're putting the cart before the horse. Maybe the xaml implementation is the way it is in order to facilitate a familiar programming model. Without xaml, how does the programmer make a control? With a big hairy block of tree-building code in the constructor. Xaml replaces that big hairy block with something easier to read and edit (and maybe also lets them farm out some or all of its beautiful details to a graphic designer). You can still write a control without xaml (and sometimes you have to, because of inheritance restrictions), but I don't expect many programmers doing so would then think, "Oh, I'd better create a second class to give me access to the control's subcomponents and to handle the events", which is what fxml seems to be forcing you into.

          At any rate, to usefully use FXML, I have to have some sensible way to connect the control's Java code and its display tree. The FXML documentation doesn't really provide guidance in this area. The OP and I have both opted for an approach that makes it easy, if not entirely pretty behind the scenes. It would be nice if lots of other people didn't also have to reinvent this wheel, but rather have JavaFX offer it as one style of programming with FXML.
          • 17. Re: Proposal for FXML defined custom controls
            914420
            Greg Brown wrote:
            Just one more thought - one other thing you may want to consider when designing a custom control is encapsulation. By extending an existing class such as VBox or GridPane, you are allowing a caller to modify the contents of your control using the existing API of those classes. If your intent is to create a specialized VBox or GridPane, then that is probably exactly what you want. However, if you want to keep the implementation details of your control opaque, you probably don't want to do this.

            A better option would be to extend some abstract base class that provides a place to host your custom control's content, but does not impose any additional API. This could be done, for example, by defining a class similar to ContentControl but whose "content" property is protected rather than public. Maybe we should consider providing such a class with the platform, to make it easier for developers to create custom controls using FXML.
            You are right about encapsulation. I am aware of that, e.g. if I extend from HBox the control accepts "<children>" when it is used in the scene graph unless I make the specific control reject that. So far it seemed to be the lesser evil. FWIW, my previous approach was similar to BVM's: I was also extending StackPane in order to put the FXML load result into the children of the StackPane (already merging the controller and control instances). However, I did not like the extra StackPane in the scene graph. I might have done what you are suggesting but did not have the API know-how to extend Control to avoid the extra baggage. And then some better way presented itself (and you have seen the result in the posted code).

            Currently I do not worry too much about encapsulation in this regard because the FXML controls will be used in-house only. If those were meant for an external library things would be different.
            • 18. Re: Proposal for FXML defined custom controls
              914420
              Greg Brown wrote:
              I also defined a CustomControl class that extends ContentControl and defines its content in markup. It is a simple example, but demonstrates the concept. Note that the CustomControl class delegates the implementation of its "text" property to the controller:
              This is how the control could be implemented using that util method. The launching app can be reused (the import package has to be adjusted).

              CustomControl.java
              package test.control;
              
              import javafx.beans.property.StringProperty;
              import javafx.fxml.FXML;
              import javafx.scene.control.TextField;
              import javafx.scene.layout.VBox;
              import mint.util.MintFXUtils;
              
              public class CustomControl extends VBox {
                @FXML private TextField textField;  
                
                public CustomControl() {
                  MintFXUtils.load(this);
                }
                
                public String getText() {
                  return textProperty().get();
                }
                
                public void setText(String value) {
                  textProperty().set(value);
                }
                
                public StringProperty textProperty() {
                  return textField.textProperty();                
                }
                
                @FXML
                protected void doSomething() {
                  System.out.println("The button was clicked!");
                }
              }
              CustomControl.fxml
              <?xml version="1.0" encoding="UTF-8"?>
               
              <?import javafx.scene.*?>
              <?import javafx.scene.control.*?>
              <?import javafx.scene.layout.*?>
              <?import test.control.*?>
              
              <CustomControl fx:controller="test.control.CustomControl" xmlns:fx="http://javafx.com/fxml">
                  <TextField fx:id="textField"/>
                  <Button text="Click Me" onAction="#doSomething"/>
              </CustomControl>
              • 19. Re: Proposal for FXML defined custom controls
                885691
                Should it be ContentControl.class.getResource(...)?
                Yes, probably. Good catch.
                • 20. Re: Proposal for FXML defined custom controls
                  885691
                  It feels to me like you're putting the cart before the horse.
                  What I mean is that, because XAML is compiled, it is easier to achieve the "developer experience" you describe. Since, by design, FXML is not compiled, it isn't possible to provide the same experience. So it's not so much a question of putting the cart before the horse, but understanding the differences between the two approaches (compiled vs. uncompiled).
                  • 21. Re: Proposal for FXML defined custom controls
                    885691
                    I've been thinking about this some more and I think I have a solution that would allow you to work with FXML as you would like without the need for a custom builder and controller factory.

                    I think there are two issues with the current design that are preventing you from doing what you want:

                    1) FXMLLoader is responsible for instantiating the root element, whereas you want to instantiate the root element yourself via your custom class's constructor. You are currently using a custom builder factory to get around this.

                    2) FXMLLoader assumes that the root and the controller will be two distinct types. You are currently using a custom controller factory to get around this.

                    Here's my suggestion:

                    1) Add a new FXML element called <fx:root>. This element would only be valid as the root of an FXML document, and would resolve to the current value of the loader's "root" property.

                    2) Add a setRoot() method to FXMLLoader to allow a caller to pre-populate the "root" property. Calling this method would be a prerequisite to using the <fx:root> element.

                    3) Add a setController() method to FXMLLoader to allow a caller to pre-populate the "controller" property. The "fx:controller" attribute could be used to override this value, or to instantiate the controller if the caller has not previously called setController() on the loader (for example, when specifying a controller in an include).

                    With these three (very minor) changes, a custom control could be implemented as easily as follows:
                    public class CustomControl extends Control {
                        public CustomControl() {
                            URL location = CustomControl.class.getResource("custom_control.fxml");
                            ResourceBundle resources = ResourceBundle.getBundle(CustomControl.class.getName()); 
                            
                            FXMLLoader fxmlLoader = new FXMLLoader(location, resources);
                            fxmlLoader.setRoot(this);
                            fxmlLoader.setController(this);
                            
                            try {
                                fxmlLoader.load();
                            } catch (IOException exception) {
                                throw new RuntimeException(exception);
                            }
                        }
                    
                        ...
                    }
                    I think it would work really well. What do you think?

                    Greg
                    • 22. Re: Proposal for FXML defined custom controls
                      914420
                      Greg Brown wrote:
                      I think it would work really well. What do you think?
                      Sounds great! I'd be very happy with that :-)

                      Rgds
                      Werner
                      • 23. Re: Proposal for FXML defined custom controls
                        885691
                        As would I. Hopefully this is something we can do for 2.2.
                        • 24. Re: Proposal for FXML defined custom controls
                          bvm
                          I think your proposal is reasonable, and would greatly simplify our messy design pattern.
                          3) Add a setController() method to FXMLLoader to allow a caller to pre-populate the "controller" property. The "fx:controller" attribute could be used to override this value, or to instantiate the controller if the caller has not previously called setController() on the loader (for example, when specifying a controller in an include).
                          Yes on the latter, no on the former. Surely the code explicitly loading an FXML file should have the final say on who the controller is. It can always refrain from calling setController if it wants the file's fx:controller attribute to be used.
                          • 25. Re: Proposal for FXML defined custom controls
                            885691
                            Surely the code explicitly loading an FXML file should have the final say on who the controller is. It can always refrain from calling setController if it wants the file's fx:controller attribute to be used.
                            I think we have two options for handling any "root" and "controller" values that have been set by the caller:

                            1) Caller-specified values win. If a caller sets the root value and then specifies a root element other than <fx:root>, it is an error. Similarly, if the caller sets the controller value and then specifies fx:controller, it is also an error.

                            2) Document-specified values win. If the caller sets the root value and then specifies a root element other than <fx:root>, the document-specified root is used. Similarly, if the caller sets the controller value and then specifies fx:controller, the document-specified controller is used.

                            My original thinking was that #2 would be preferable. Why do you think #1 would be better?

                            Greg
                            • 26. Re: Proposal for FXML defined custom controls
                              Richard Bair-Oracle
                              What about

                              3) caller-specified values win. If a caller sets the root value and then specifies a root element of a different type, the supplied root wins. If the caller sets the controller value and then specifies fx:controller, the supplied value wins.

                              The benefit here is that if there is no special root/controller specified then the me in the document is used, otherwise the specified instance is used. Might be useful for some scenarios such as testing.
                              • 27. Re: Proposal for FXML defined custom controls
                                885691
                                I'd be fine going with #1 with the option of adding support for #3 if the need arises.
                                • 28. Re: Proposal for FXML defined custom controls
                                  914420
                                  Sounds good to me.
                                  • 29. Re: Proposal for FXML defined custom controls
                                    bvm
                                    Greg Brown wrote:
                                    1) Caller-specified values win. If a caller sets the root value and then specifies a root element other than <fx:root>, it is an error. Similarly, if the caller sets the controller value and then specifies fx:controller, it is also an error.

                                    2) Document-specified values win. If the caller sets the root value and then specifies a root element other than <fx:root>, the document-specified root is used. Similarly, if the caller sets the controller value and then specifies fx:controller, the document-specified controller is used.

                                    My original thinking was that #2 would be preferable. Why do you think #1 would be better?
                                    I don't see how #2 would ever make sense. If the code loading an FXML file can't overrule the root or the controller, then we're just back to where we were before your proposal -- no setController, no setRoot. It's not like anybody would ever write code that calls setController and/or setRoot without knowing what's in the FXML file.

                                    But I think Richard's #3 is more what I had in mind...
                                    3) caller-specified values win. If a caller sets the root value and then specifies a root element of a different type, the supplied root wins. If the caller sets the controller value and then specifies fx:controller, the supplied value wins.
                                    In other words, the FXML can supply defaults for the things not dictated by the code that loads it. The first clause (setRoot wins) means that you could write FXML code that could be both loaded as the content of a user control and also be used in a fx:include directive (since you could never include a file rooted with fx:root). The second means you could write some chunk of FXML code that would be reusable by various clients who specialize its behavior by supplying their own controller, yet still have it do something using its default controller when no more specialized one was supplied.

                                    But we may be splitting hairs here. I'm having a hard time imagining real-life scenarios in which a single FXML file could usefully be loaded by different classes who want to supply their own controllers, yet still have a sensible default controller. After all, I think we've established that a controller class that nobody has a handle on can't do very much that's useful, since it can only access its own fxml nodes and global entities. Richard suggested there might be testing scenarios. Can anyone else more imaginative than I think of real cases that could make a good case for any of the suggested alternatives?