FX My Life: Part III

Beyond The Black Triangle

Blake
10 min readApr 3, 2021

In the last experiment, we came up with a plain “Hello, World” window, which is trivial except for all the steps needed to get there. (Get used to trivial: It’ll be a while before we leave that neighborhood.)

Although I have a number of “real” applications I need to develop, I also need a “toy” application that serves as a proving ground for things I want to try to integrate into the “real” applications. Let me define my terms so I can stop using scare-quotes: A “real” application is any application (no matter how trivial) that someone besides me might use. The goal of a real application is to provide someone, somewhere with a tool that is operational, that provides some needed or useful functionality, and that can be proffered by me unapologetically.

A “toy” application is any application (no matter how complex) where, whatever functionality it might offer (and however useful that functionality might be), no attempt is made to seriously get it into other peoples’ hands with an expectation that it will work and perform to some specification.

You can get an application to 100% functionality but have it be so bug-ridden, so quirky and so basically unusable that it’s still just a toy. (Sample sentence: “Windows is a toy application”.)

The thing is, the steps needed to go from functional to user-acceptable are often more work than going from literally nothing to functional. It’s good to have such apps around and to grow them like topsy, for a whole bunch of reasons which will be apparent as we roll along.

Our loose vision for the toy application will be a main window with a bunch of tiles on it, where the user clicks on tiles to open games or mini-apps or whatever. We’re not too concerned about this, because we’ll be throwing things in that we can use in real apps, so things won’t always make sense. (But this will provide us a good way to have them make sense!)

Scene Builder

VDEs (visual design environments) peaked in the ’90s with Delphi and Visual Basic (and VisPro ReXX for those who remember it) and basically petered out with the rise of the web-based app. “Modern” offerings, including Gluon’s Scene Builder, are a pretty thin gruel compared to the fat (and phat!) VDEs of the Golden Age of Windows 95, and there are a few elements of Scene Builder that may make you question the wisdom of using JavaFX at all (such as the tendency of a key pressed in an input pulling down a menu, as if you had also been holding down the Alt/Opt key).

Nonetheless, it’s what we have to work with, and there are elements of those old-school VDEs which, while very sexy, encouraged some very bad habits (such as mixing all your concerns into the presentation layer).

So, let’s open up the “sample.fxml” IJ starts us off with as its default FX application, and we’ll see a lot of nothing. Make sure it’s completely empty (delete any objects that show up in the Document pane on the lower left) and add in a BorderPane.

I like starting with a BorderPane because it’s easy to add things to the edges, which can be a useful way to extend functionality without having to redesign the whole UI. (Oh, you want a history panel? Add that on the right! Status bar? Add it to the bottom!)

One of the apps I’m working on rewriting has a button bar across the top. This is done programmatically, though it’s done programmatically in about the worst possible way, with one event handler for all the possible incarnations of the buttons — so, depending on the current context which is sussed out from a series of if-thens on the sender — the handler has code for adding, saving, deleting, doing various calculations, and so on. There’s a lot of duplication because at no point did someone say “Hey, let’s put all this code to save in one place, and just call that code from the handler!”

That, however, is for another day. Right now, let’s work on just getting a button bar going: Add a button bar to the top area of the BorderPane and then drag four buttons on to it with the labels “Back”, “New”, “Save” and “Load”.

Buttons in an HBox

Actually, that’s an HBox, not a ButtonBar, that’s holding those buttons. The first thing I encountered with ButtonBar is that it aligns things to the right — allegedly due to some Windows defaults. Let’s switch back to ButtonBar for a moment.

I did this by adding a ButtonBar to the bottom and then dragging all the exiting buttons on to that, then deleting the HBox and dragging the whole ButtonBar back to the top. It looks like this now:

Buttons in a ButtonBar

Why tell you all this? Well, when I’m designing an interface, I make a lot of mistakes and change my mind a lot and I find a lot of trial and error is helpful to really getting comfortable with a designing tool. Also, I feel like it’s important to document the churn. As we get better and better, there will be less and less churn, but let’s not pretend it never happened.

ButtonBar puts buttons in order according to its ButtonOrder property, naturally enough, which is basically self-explanatory…oh my god!

L_HE+U+FBIX_NCYOA_R

The complexity of this is due to the fact that ButtonBar wants to do what’s appropriate for your platform, and apparently the platforms all have opinions as to where the “Help” buttons should go, and the “Yes” and “No” buttons, and so on.

None of that is relevant for us yet. We just wanted buttons that were nicely centered in the middle. By eliminating the characters from right to left, I found which of the items was actually being used by my bar, and found that I only needed:

U

To center all the buttons! Have a look:

This is a good sign. It didn’t take much to get what we needed. Let’s take it further and add some hot-keys. This, I discovered, is done by putting an underscore before the desired letter:

Step 1: Add Underscore

If you can see the underscores, you’ve left out the key element, which is enabling “mnemonic parsing”. You can get all the buttons selected at once with control-click on each individual one:

Step 2: Ctrl-Click All Buttons

And then you can go to the Properties tab of the Inspector (on the right) and click Mnemonic Parsing. Voila, the underscores disappear:

Step 3: Mnemonic Parsing

When you run the app, they’re still not there! However, and press the Alt/Opt key and:

Nice.

Screen Switching

Now, at this point, it occurred to me that I was going to want to swap out the entire interface when the user selected a “game” from the tile window, and I was going to want to slide in or blow up or however animate the new screen coming in. (I realized shortly thereafter that, using a BorderPane, I was really only going to want to slide out the middle, but more on that in a bit.)

After digging around, I came up with some code to handle that: The idea was to create a screen controller object that would take scenes (probably read in from FXML files) and swap the root object out:

package sample;

import javafx.scene.Scene;
import javafx.scene.layout.Pane;

import java.util.HashMap;

public class ScreenController {
private final HashMap<String, Pane> screenMap = new HashMap<>();
private final Scene main;

public ScreenController(Scene main) {
this.main = main;
}

protected void addScreen(String name, Pane pane) {
screenMap.put(name, pane);
}

protected void removeScreen(String name) {
screenMap.remove(name);
}

protected void activate(String name) {
main.setRoot(screenMap.get(name));
}
}

This means I can replace the entire UI with a single call:

switchScreen("sample");

Of course, I have to put in the “sample” screen first:

screenController.addScreen("sample", FXMLLoader.load(getClass().getResource( "sample.fxml" )));

In fact, here’s my entire Main currently:

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

public ScreenController screenController; //For replacing EVERYTHING

@Override
public void start(Stage primaryStage) throws Exception{
Scene scene = new Scene(new Group(), 800, 600);
scene.getStylesheets().add(getClass().getResource("styles.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();

screenController = new ScreenController(scene); //not using, currently
screenController.addScreen("sample", FXMLLoader.load(getClass().getResource( "sample.fxml" )));

switchScreen("sample");
}

public void switchScreen(String name) {
screenController.activate(name);
}

public static void main(String[] args) {
launch(args);
}
}

As I said, I realized shortly thereafter that most (if not all) my swapping out would be done in the BorderPane’s center area, but I keep the code around because I can see potential situations where I’m going to want to swap the whole thing out.

Styling

It’s a cool feature of Java FX that it can be styled via CSS. (In fact, I think it can even be skinned via CSS, the distinction apparently being that the actual controls can be changed — a slider turned to a dial or what have you — but I’m not that far along yet.) What’s less cool is that, despite this feature, there does not appear to be a repository anywhere that collects these CSSes for JavaFX programmers to use.

CSS is kind of interesting and easy to use at some levels, but it’s also its own thing, an arguably Turing-complete programming language, and a huge digression from swallowing the already huge horse pill that is JavaFX — especially if including all the recent features of Java that FX exploits and looking at the various functional-reactive-programming concepts that power a lot of the data-driven aspects of it.

I have something of an issue with pedagogic works that purport to teach you “Z” and then require you to know all the tangential or trendy items “A” through “Y” before getting anywhere near “Z”. One “bible” of programming a particular language/framework had, at my count, literally fifteen different things you had to know before you got to the language and framework you were trying to learn.

That said, the reason I picked JavaFX over most of the other options is that most of the other options were ugly as sin. Anybody can put up a window with a few buttons and inputs on it in just about any language, but it’s not 1995 any more. Digging around, e.g., on the Tcl/Tk site looking for an example of “what can you do?” I finally found this:

Way to wow, Tcl/Tk!

Among GUI libraries, wxPython fares a little better. Here’s a screenshot from their demo page:

This is somewhat cherry-picked, to be fair. There are better examples but this was near the top.

On the other hand, you’d never find anything this ugly at Qt — probably because it’s a commercial product. This was the ugliest Qt thing I could find:

QT is pretty serious about being pretty.

But, as Dirty Harry once said, a man’s got to know his limitations and as long as I’m responsible for what the app looks like, I’m going to need to steal…a lot. And CSS should allow me to plunder freely from much better aesthetic minds than my own.

No matter how good the software is, if it’s ugly, people aren’t going to want to use it.

This is the basic lesson in all the JavaFX books about how to use CSS: Create a file, let’s call it “styles.css” and put some CSS in it. (Drop it in your main directory where you have your fxml files for now. We’ll work on cleaning things up later.) Then you attach it to your scene with this beauty:

scene.getStylesheets().add(getClass().getResource("styles.css").toExternalForm());

I think it’s the “toExternalForm” that really cinches it for me. Currently, my styles.css looks like this:

.root {
-fx-background-color: lightgoldenrodyellow;
-fx-padding: 20px;
}
.label {
-fx-background-color: black;
-fx-text-fill: white;
-fx-padding: 10px;
}

That’s why some of these screenshots have the goldenrod background. Now, since I’m attaching the CSS to the scene:

Scene scene = new Scene(new Group(), 800, 600);
scene.getStylesheets().add(getClass().getResource("styles.css").toExternalForm());

but then immediately discarding the scene for one created from an FXML:

screenController.addScreen("sample", FXMLLoader.load(getClass().getResource( "sample.fxml" )));
switchScreen("sample");

you could ask yourself, “How is it that the CSS is still applied to this new scene?” Well, if you recall the Screen Controller, it doesn’t actually create a new scene, it just sets the root of its main variable to whatever the new scene is:

main.setRoot(screenMap.get(name));

This is pretty handy. It also means we could probably put a method in Screen Controller to swap out the CSS for another file entirely. We’ll have to try that at some point, but first, let’s do something about the buttons.

Styling Buttons

After digging around and not finding any complete stylesheets or “skins” for JavaFX — much to my dismay — I found a bunch of button styles at FX Experience:

I swiped all the CSS from there I could, and played with changing the buttons on my toy app. Taking the first element and changing the selector from #green to .button, all the buttons in my app will be duly styled:

.button {
-fx-background-color: linear-gradient(#f0ff35, #a9ff00),
radial-gradient(center 50% -40%, radius 200%, #b8ee36 45%, #80c800 50%);
... etc.
Color, style and clash!

The merits of using “glowing alien green” with a goldenrod background aside, this already looks a million times better, just for being more than nothing at all. I like bevel-gray:

But the point really is to have a CSS we can play with later on. Next up we’ll look at building a way to select games from the center panel.

--

--

Blake
0 Followers

I am a poor, wayfaring stranger, traveling through this world of woe.