I like the DataFX idea and I am in the process of moving my half-developed project from pure JavaFx 8 to using DataFX.
I am not using a Flow like all the tutorials show, but rather have a main pane, with buttons on the left and an AnchorPane on the right. The buttons will change the pane content on the right and invoke the proper controller.
In my main application I am doing:
@Override
public void start(Stage stage) throws Exception {
instance = this;
new Flow(FXMLDocumentController.class).startInStage(stage);
}
Inside my
FXMLDocumentController (which is the main controller that contains the buttons) I have:
@ViewController("/fxml/FXMLDocument.fxml")
public class FXMLDocumentController implements Initializable {
private Logger log = Logger.getLogger(FXMLDocumentController.class.getCanonicalName());
@ActionTrigger("view-contracts")
private Button contractsButton;
@ViewNode
@ActionTrigger("view-reports")
private Button reportsButton;
...
@FXML
private AnchorPane domainView;
// DataFX Controllers/Views
private ViewContext<ReportsController> reportsContext;
private ViewContext<ListContractsController> listContractsContext;
...
@Override
public void initialize(URL url, ResourceBundle rb) {
}
@PostConstruct
public void init() {
contractsButton.setDisable(true);
reportsButton.setDisable(true);
...
try {
ViewFactory factory = ViewFactory.getInstance();
// Create the Controllers
reportsContext = factory.createByController(ReportsController.class);
listContractsContext = factory.createByController(ListContractsController.class);
...
} catch (FxmlLoadException ex) {
log.log(Level.SEVERE, null, ex);
}
}
@ActionMethod("view-reports")
private void clickReportsButton() {
setView(reportsContext);
reportsContext.getController().reset();
}
@ActionMethod("view-contracts")
public void clickContractButton() {
setView(listContractsContext);
listContractsContext.getController().reset();
}
...
/**
* Set the view to the side pane.
* @param context the ViewContext that contains the desired view
*/
private void setView(ViewContext context) {
domainView.getChildren().add(0, context.getRootNode());
if (domainView.getChildren().size() > 1) {
domainView.getChildren().remove(1, domainView.getChildren().size());
}
}
}
Everything worked fine when only the main controller had the
@ActionTrigger and
@ActionMethod while the sub-controllers only to have
@ViewController and the elements with
@ViewNode (with the exception of the
TreeTableView and the
AnchorPanes which still needed
@FXML) but no actions, relying on the
@FXML and definition in the fxml
The problem arose when I added the
@ActionTrigger and
@ActionMethod to the
ReportsController and
ListContractsController. I get a NullPointerException when loading the controller
Caused by: java.lang.NullPointerException
at io.datafx.controller.flow.context.FXMLActionResourceConsumer.consumeResource(FXMLActionResourceConsumer.java:39)
at io.datafx.controller.flow.context.FXMLActionResourceConsumer.consumeResource(FXMLActionResourceConsumer.java:35)
at io.datafx.controller.context.ContextResolver.injectResources(ContextResolver.java:70)
at io.datafx.controller.ViewFactory.createByController(ViewFactory.java:185)
... 26 more
This references the following method where the context has no registered objects so the
getRegisteredObject(ViewFlowContext.class) is null.
public class FXMLActionResourceConsumer implements ControllerResourceConsumer<ActionTrigger, Object> {
@Override
public void consumeResource(ActionTrigger annotation, Object resource, ViewContext<?> context) {
FlowActionHandler actionHandler = context.getRegisteredObject(ViewFlowContext.class).getRegisteredObject(FlowActionHandler.class);
if (resource != null) {
if(resource instanceof MenuItem) {
actionHandler.attachEventHandler((MenuItem) resource, annotation.value());
} else if(resource instanceof Node){
actionHandler.attachEventHandler((Node) resource, annotation.value());
}
}
}
...
}
What am I doing incorrectly?