Ideally, a desktop app should have some of the better navigational aspects of an HTML app, like the ability to go backward to an arbitrary point one visited in the past. I had the idea of making a stack of nodes visited and allowing those nodes to be pushed and popped, but we don’t have any clear vision in the app to allow for that.
That is, we’re primarily assuming we’ll be switching from node-to-node but we have the ScreenController — for replacing the entire screen — right there! I mean, even as this is an experimental app where I work out my confusions (hopefully to your edification) as I go along, at some point, it would be nice if it played some games, and that probably means doing a little full screen work.
It’s okay, though: Thinking about it lets us view the problem in a richer context than we might have if we just glibly thought in terms of “yeah, you can go visit screen X from screen Q, even though there’s no connection between the two.” Because probably what most users want is to, say, flip from this screen with Bob Jones’ info on it, to another screen where maybe Bob Jones is part of a list, or Mrs. Jones is filing a complaint or what-have-you.
In other words, it brings in a kind of REST-ful idea where we look at locations and their state. I don’t just want to go back to the tic-tac-toe game, I want to go back to the tic-tac-toe game where I lost and figure out how drunk I was. (OK, it makes more sense with chess, but you get the idea.)
The other thing that makes it okay is that we’ve barely started working with navigation issues at all. Let’s just work on getting back and forth between the tile pane and the tic-tac-toe board. Should be easy, right?
With an optimism in defiance of experience, we head out to SceneBuilder, open up our frame.fxml, and click on the Back button. We don’t need an ID since we don’t really have any call to address the button, specifically. We just want an event to fire when it gets clicked on:
Now, if we try to run this without putting code into the Controller.java — you did remember to set the controller in SceneBuilder, right?
Well, at this point, Java will refuse to run because we don’t have a navigateBackwards method in Controller. I like to start simple:
public void navigateBackwards(ActionEvent e) {
System.out.println("Back it up!");
}
I’ll run this and make sure I get the “Back it up!” message in the console when I click. Now what?
Well, let’s just make the “Back” button always go back to the tiles-panel for now. If we’re on the tiles-panel, do nothing:
if (nodeController.getActive() != nodeController.node("tiles-panel"))
Otherwise, we’ll switch. I’m just going to steal the code from Main where it sets this up. This is truly awful, since we’ll need to address Main directly and utilize the Devil’s Clipboard:
public void navigateBackwards(ActionEvent e) {
if (nodeController.getActive() != nodeController.node("tiles-panel"))
NodeController.me.activate("tiles-panel", Main.me.bp, event -> {
Main.me.bp.getChildren().remove(NodeController.me.node("tiles-panel"));
((Pane) Main.me.bp.getCenter()).getChildren().clear();
Main.me.bp.setCenter(NodeController.me.node("tiles-panel"));
});
}
But I’m basically happy, because it works: The tic-tac-toe board slides in (when you click the appropriate tile), then the tiles panel slides in (when you click “Back”).
This seems weird, though:
((Pane) Main.me.bp.getCenter()).getChildren().clear();
If I take it out…everything works still!
I can’t take out this line:
Main.me.bp.getChildren().remove(NodeController.me.node("tiles-panel"));
Because the subsequent SetCenter will get an error for duplicate children. I understand that, and we can work out a cleaner solution in a bit.
Going back to Main, to try to recall what this line does, again:
((Pane) Main.me.bp.getCenter()).getChildren().clear();
I discover I don’t need it in Main either! The start method now looks like this:
@Override
public void start(Stage primaryStage) throws Exception{
me = this;
primaryStage.setTitle("Fun 'n' Games with JavaFX");
Scene scene = new Scene(new Group(), 800, 600);
scene.getStylesheets().add(getClass().getResource("styles.css").toExternalForm());
primaryStage.setScene(scene);
screenController = new ScreenController(scene); //not using, currently
screenController.addScreen("frame", FXMLLoader.load(getClass().getResource( "frame.fxml" )));
switchScreen("frame");
bp = ((BorderPane) scene.getRoot());
NodeController.me.activate("tiles-panel", bp, event -> {
bp.getChildren().remove(NodeController.me.node("tiles-panel"));
bp.setCenter(NodeController.me.node("tiles-panel"));
});
primaryStage.show();
}
This is the sort of thing that can drive a person nuts, since the line removed solved a very specific problem which has somehow gone away. I remember, at the time, thinking it was some issue with leftover state, and it’s important to recall that we’re dealing with an external file manipulator (SceneBuilder) which doesn’t autosave.
Now, I don’t think that’s the problem here. But I do know that, when one is learning, one comes across all kinds of problems that never emerge once the subject has been understood.
When those problems recur repeatedly to beginners, they’re good to point out. But in most tech books, nobody talks about this: You’re shown a narrow path that works, and when you go astray from this path — which you necessarily must to get any real work done at all — you have no idea where you went off the rails.
And rather sinisterly, you’ll forget you hit those issues when you started on your journey, and have no patience for the novice who comes to you years later with the same problem.
I mean, I’d never do that. But if I were to, this blog will remind me of my humble origins.