All of the source code in this post can be found on GitHub.
The App
This screenshot shows an app with two forms: a Canada-specific address subform and an a US-specific address subform. The Canadian subform has a list of provinces while the US subform has a list of states and an extended zip code field.
Screenshot of Both Subforms |
The top ToggleButtons drive the presentation of the subforms. A StackPane contains both of the subforms, and a ToggleButton selection will make one form visible and the other invisible. Apparently, the visibility property also affects the input focus, so I didn't have to shuffle the order using toFront() or toBack().
Classes
An Application subclass, Binding2App uses several FXMLLoader objects to load .fxml files: AccessController2.fxml, AccessController_US.fxml, AccessController_CA.fxml. Each of the .fxml files is backed by a JavaFX controller of the same name. The following UML shows the classes involved in the app.
Class Model |
AddressController2 ac2 = new AddressController2(); FXMLLoader loader = new FXMLLoader( getClass().getResource("/binding2-fxml/Address.fxml") ); loader.setRoot(ac2); Parent pAddr = loader.load(); FXMLLoader usLoader = new FXMLLoader( getClass().getResource("/binding2-fxml/Address_US.fxml") ); usLoader.load(); FXMLLoader caLoader = new FXMLLoader( getClass().getResource("/binding2-fxml/Address_CA.fxml") ); caLoader.load(); // // Setters must be called before postInit() // ((AddressController2)loader.getController()).setUSForm( usLoader.getRoot(), usLoader.getController() ); ((AddressController2)loader.getController()).setCAForm( caLoader.getRoot(), caLoader.getController() ); ((AddressController2)loader.getController()).postInit(); Scene scene = new Scene(pAddr); primaryStage.setScene(scene); primaryStage.setTitle( "Address Form" ); primaryStage.show();
The AddressController2 class contains the two ToggleButtons, some setters, and stubbed-out save() and cancel() methods. The class also has a postInit() method that binds the visibleProperties to the selectedProperties. Finally, I provide some Event Filters to make the ToggleButtons behave as they do on the iOS screen mentioned earlier (basically RadioButtons). If I leave this off, you can produce an empty form by de-selecting the ToggleButton.
Here is the postInit() method.
public void postInit() { usForm.visibleProperty().bind( tbUS.selectedProperty() ); caForm.visibleProperty().bind( tbCanada.selectedProperty() ); // // Makes controls behave like RadioButtons (always one selected) // tbUS.addEventFilter(EventType.ROOT, (evt) -> { if( evt.getEventType() == ActionEvent.ACTION ) { usController.clearForm(); caController.clearForm(); } if( tbUS.isSelected() ) { evt.consume(); } }); tbCanada.addEventFilter(EventType.ROOT, (evt) -> { if( evt.getEventType() == ActionEvent.ACTION ) { usController.clearForm(); caController.clearForm(); } if( tbCanada.isSelected() ) { evt.consume(); } }); }
Both of the subforms are contains of country-specific fields. Their properties -- the JavaFX controls -- are packaged-scoped, so they can be retrieved by the main controller AddressController2.
This is a follow-up post to JavaFX Binding 1 : A Conditional Address Form. This example is more intuitive than the previous code in that there aren't any "holes" left between fields that aren't relevant for the current submission. Be sure to visit GitHub to browse the complete source code. Both examples are in the Examples project
No comments:
Post a Comment