[{"content":" What is Snapcast? # I cannot do better than the description from the Snapcast GitHub page:\nSnapcast is a multiroom client-server audio player, where all clients are time synchronized with the server to play perfectly synced audio. It\u0026rsquo;s not a standalone player, but an extension that turns your existing audio player into a Sonos-like multiroom solution.\nAudio is captured by the server and routed to the connected clients. Several players can feed audio to the server in parallel and clients can be grouped to play the same audio stream.\nThere\u0026rsquo;s also a diagram which shows how Snapcast works:\nTo sum it up: You set up a server with some sort of music playing software, then install the Snapcast server software and configure it such that it takes its input from the output of the music playing software. Then you set up Snapcast clients, and configure them to connect to the Snapcast server. After that, you can stream the music from the music player software on the server to all of the Snapclast clients and they will be synchronized.\nIn terms of audio quality, I find it to be excellent. I think the concept of \u0026ldquo;whole-home audio\u0026rdquo;, by definition, isn\u0026rsquo;t expected to be audiophile quality. It\u0026rsquo;s designed to be background noise. But I\u0026rsquo;ve found this setup to go far beyond that basic requirement.\nSnapcast Server # As this article is part of a series about setting up a Homelab using Proxmox, this is the approach I will be describing.\nThe requirements for the Snapcast server are fairly light, so a standard LXC container running Debian is more than sufficient. I\u0026rsquo;m running it with 1 CPU and 512MB of RAM and it\u0026rsquo;s usage is averaging about 1.5% of CPU and about 128MB of RAM. That\u0026rsquo;s on a Lenovo M910Q with an i5-6500T processor, which is not particularly high powered.\nIt\u0026rsquo;s perfectly possible that you could run this a Raspberry Pi of some sort, or anything else you have lying around. The instructions would be largely the same, as long as you\u0026rsquo;re running Linux.\nSnapcast Clients # The Snapcast client software will run on Linux, Windows, Android, IOS, and MacOS - pretty much any computing device you might have.\nThe big caveat is that to be of any use, the device needs to be able to play sound in a way that you can hear it. This is also going to mean that the device will need to have some way to convert the digital stream into audio output. This conversion is handled by something called a \u0026ldquo;DAC\u0026rdquo;, or \u0026ldquo;Digital to Analog Converter\u0026rdquo;. Some devices have them built in, but may also benefit from using a dedicated DAC device of some sort. The output from a DAC will be in a form that is compatible with headphones or an amplifier of some sort.\nSince it runs Linux, a RaspberryPi is an excellent, low-cost, Snapcast client, and this is what I\u0026rsquo;m going to talk about in this article. Specifically, I\u0026rsquo;m going to use RaspberryPi Zeros.\nYou can also re-purpose an old android phone or an old iPhone to be a SnapCast client. It should probably have a headphone jack, though, as using Bluetooth to communicate with the speakers will have its own lag. It does seem possible, however, to compensate for that in the setup of the SnapCast client. I\u0026rsquo;m not sure how consistent the lag from Bluetooth is, though, so you\u0026rsquo;d need to experiment to see if it works.\nRaspberryPi Zero # The RaspberryPi Zero (Pi0) is tiny, super cheap (around $20), has built-in WiFi and is easy to get. They don\u0026rsquo;t, however, have any built-in audio output. In order to use them for this application, you\u0026rsquo;ll need to use a external DAC. I recommend getting one that works with the header pins on the Pi0.\nThey look like this:\nApparently HiFiBerry makes a great DAC designed to work with the Pi0. Unfortunately, I wasn\u0026rsquo;t able to find any place to buy them, my usual sources have had them listed as \u0026ldquo;Out of Stock\u0026rdquo; for months, and even the HiFiBerry website doesn\u0026rsquo;t list them as available. However, there was another brand \u0026ldquo;Pimoroni\u0026rdquo; that also make a Pi0 DAC. So I went with that.\nYou can see the listing for the Pimoroni Audio DAC SHIM here on PiShop.ca. When I checked to add the link, I see that it is currently sold out. Even on the Pimoroni site, it\u0026rsquo;s listed as sold out. I\u0026rsquo;m not sure if this is going to be a long-term issue.\nThis is what it looks like:\nYou just shove it down on the GPIO header and it works. No soldering. It\u0026rsquo;s a great design.\nIf you cannot get one of these, then you\u0026rsquo;ll have to source out another DAC board. There are others, but they are generally a bit more expensive, and a bit bigger, too. HiFiBerry makes a variety of them that also do not require soldering. They seem to be in stock, too. My impression is that if you use a HiFiBerry DAC, the setup instructions will be the same as for the Pimoroni SHIM.\nUpdate: I received notification from PiShop.ca that the HiFiBerry DAC is now in stock. Too late for me, but it\u0026rsquo;s a option you might want to consider.\nMopidy # You can use any music playing software that makes sense to you. Snapcast is capable of taking its input from a variety of pipelines, so as long as your music playing software is capable of feeding data into one of those, you\u0026rsquo;re good to go.\nThe concensus for homelabs seems to be \u0026ldquo;Modpidy\u0026rdquo;. Mopidy can take its source from a number of different places, and there are a variety of plugins that you can install to expand this list. This includes services like Spotify, Tidal, JellyFin, TuneIn and YouTube. It also has an extension for SomaFM, which is what I was looking for. Additionally, it can play local files natively.\nThe terminology in situations like this gets a little bit confusing. We are going to talk about Mopidy installed as a service on the server, but it acts as a client to the external streaming services.\nAdditionally, Mopidy has a number of API\u0026rsquo;s available, including a web services API. This means that create home-brewed control scripts or programs to automate aspects of Mopidy. This also means that a number of web clients have been built and are available to install. You\u0026rsquo;re going to need one of these if you want to control your Mopidy server in an ad-hoc fashion.\nInstallation and Configuration # I spun up a Debian 13 LXC in Proxmox to be both my SnapCast and Mopidy host. I didn\u0026rsquo;t see any reason to split these out into two containers because I view them as two sides of the same coin, and having them run in the same container makes things a bit easier.\nAs my Proxmox cluster is sitting in the basement and none of the hosts have any audio equipment attached, I couldn\u0026rsquo;t see any point in installing Mopidy first, as it would be difficult to test it. So I chose to install SnapServer first and test it with some white noise.\nA Quick Note About IP Addresses and Hostnames # In my homelab I have OPNSense providing the DHCP service for the whole network, and I have a Technitium DNS server. My usual process is to create any new servers or devices as DHCP clients. Then I check the \u0026ldquo;leases\u0026rdquo; page in OPNSense, find the device and set it as a static address. Then I go into the DNS server and add an entry for it with that address. This gives me centralized control over the addressing and naming without having to reconfigure the individual servers and devices.\nI\u0026rsquo;m not going to mention this at all in the rest of these instructions. You\u0026rsquo;ll have to take some steps to ensure that you have all of the devices configured the way that you want them in this respect using whatever process you have adopted.\nSnapCast Server # I just created a new LXC with Debian 13 and logged into it on the console. If you use apt to install snapserver, you\u0026rsquo;ll get version 0.31, which is a bit old.\nAt the time of writing this article, the next version of SnapCast has been released, version 0.35.0. My instructions are going to continue to use version 0.34.0, which is about 6 months older. Once again, you should be able to use the newer version without any other changes to the process. I\u0026rsquo;d be careful to make sure that both the SnapCast server and the clients are all using the same version.\nDownload the package from the project on GitHub:\nwget https://github.com/snapcast/snapcast/releases/download/v0.34.0/snapserver_0.34.0-1_amd64_trixie.deb and then use dkpg to install it:\n# dpkg -i snapserver_0.34.0-1_amd64_trixie.deb Selecting previously unselected package snapserver. (Reading database ... 20235 files and directories currently installed.) Preparing to unpack snapserver_0.34.0-1_amd64_trixie.deb ... Unpacking snapserver (0.34.0-1) ... Setting up snapserver (0.34.0-1) ... Installing new version of config file /etc/default/snapserver ... Installing new version of config file /etc/init.d/snapserver ... Installing new version of config file /etc/snapserver.conf ... Processing triggers for man-db (2.13.1-1) ... This handles the setup as a service. You can check it right away:\n# systemctl status snapserver -n40 * snapserver.service - Snapcast server Loaded: loaded (/usr/lib/systemd/system/snapserver.service; enabled; preset: enabled) Active: active (running) since Mon 2026-04-06 17:28:09 UTC; 26s ago Invocation: 6c949040b6dc401485092210bfce4ce5 Docs: man:snapserver(1) Main PID: 1386 (snapserver) Tasks: 3 (limit: 18886) Memory: 2.2M (peak: 2.5M) CPU: 83ms CGroup: /system.slice/snapserver.service `-1386 /usr/bin/snapserver --logging.sink=system --server.datadir=/var/lib/snapserver Apr 06 17:28:09 testsnap systemd[1]: Started snapserver.service - Snapcast server. Apr 06 17:28:09 testsnap snapserver[1386]: (Snapserver) Version 0.34.0, revision 0ac55084 Apr 06 17:28:09 testsnap snapserver[1386]: (Snapserver) Stream plugin directory: \u0026#39;\u0026#34;/usr/share/snapserver/plug-ins\u0026#34;\u0026#39;, sandbox directory: \u0026#39;\u0026#34;/usr/share/snapserver/\u0026gt; Apr 06 17:28:09 testsnap snapserver[1386]: (Snapserver) Adding source: pipe:///tmp/snapfifo?name=default Apr 06 17:28:09 testsnap snapserver[1386]: (Avahi) Adding service \u0026#39;Snapcast\u0026#39; Apr 06 17:28:09 testsnap snapserver[1386]: (Snapserver) Using HTTP host name: testsnap Apr 06 17:28:09 testsnap snapserver[1386]: (PcmStream) PcmStream: default, sampleFormat: 48000:16:2 Apr 06 17:28:09 testsnap snapserver[1386]: (PipeStream) PipeStream mode: create Apr 06 17:28:09 testsnap snapserver[1386]: (Server) Stream: {\u0026#34;fragment\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;host\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;/tmp/snapfifo\u0026#34;,\u0026#34;query\u0026#34;:{\u0026#34;chunk_ms\u0026#34;:\u0026#34;20\u0026#34;,\u0026#34;codec\u0026#34;:\u0026#34;flac\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;defa\u0026gt; Apr 06 17:28:09 testsnap snapserver[1386]: (FlacEnc) Init - compression level: 2 Apr 06 17:28:09 testsnap snapserver[1386]: (ControlServer) Creating TCP control acceptor for address: ::, port: 1705 Apr 06 17:28:09 testsnap snapserver[1386]: (ControlServer) Creating HTTP acceptor for address: ::, port: 1780 Apr 06 17:28:09 testsnap snapserver[1386]: (StreamServer) Creating TCP stream acceptor for address: ::, port: 1704 Apr 06 17:28:09 testsnap snapserver[1386]: (Snapserver) Number of threads: 2, hw threads: 1 Apr 06 17:28:09 testsnap snapserver[1386]: (AsioStream) Error reading message: End of file, length: 0, ec: asio.misc:2 Apr 06 17:28:10 testsnap snapserver[1386]: (Avahi) Service \u0026#39;Snapcast\u0026#39; successfully established. It\u0026rsquo;s even enabled! However, it has failed to start. Let\u0026rsquo;s see why. Look at this line of the output:\nApr 06 17:28:09 testsnap snapserver[1386]: (Snapserver) Adding source: pipe:///tmp/snapfifo?name=default See how it says \u0026ldquo;\u0026hellip;/tmp/snapfifo\u0026hellip;\u0026rdquo;? That\u0026rsquo;s going to cause problems.\nThere\u0026rsquo;s a new security feature in Debian that locks down fifo files in \u0026ldquo;sticky\u0026rdquo; directories like /tmp in order to make spoofing attempts harder. You can disable this by turning off the fs.protected_fifos kernel parameter, but this is probably not a good idea. Instead, move the Snapcast input fifo to somewhere else.\nTo do this, you\u0026rsquo;ll need to edit the /etc/snapserver.conf file. It\u0026rsquo;s big, and has lots of stuff in it, but the section you want to look at is called \u0026ldquo;[stream]\u0026rdquo;:\n# Stream settings ############################################################# # [stream] # address to listen on, can be specified multiple times # use \u0026#34;0.0.0.0\u0026#34; to bind to any IPv4 address or :: to bind to any IPv6 address # or \u0026#34;127.0.0.1\u0026#34; or \u0026#34;::1\u0026#34; to bind to localhost IPv4 or IPv6, respectively # use the address of a specific network interface to just listen to and accept # connections from that interface #bind_to_address = :: # which port the server should listen to #port = 1704 # source URI of the PCM input stream, can be configured multiple times # The following notation is used in this paragraph: # \u0026lt;angle brackets\u0026gt;: the whole expression must be replaced with your specific setting # [square brackets]: the whole expression is optional and can be left out # [key=value]: if you leave this option out, \u0026#34;value\u0026#34; will be the default for \u0026#34;key\u0026#34; # # Format: TYPE://host/path?name=\u0026lt;name\u0026gt;[\u0026amp;codec=\u0026lt;codec\u0026gt;][\u0026amp;sampleformat=\u0026lt;sampleformat\u0026gt;][\u0026amp;chunk_ms=\u0026lt;chunk ms\u0026gt;][\u0026amp;controlscript=\u0026lt;control script filename\u0026gt;[\u0026amp;controlscriptparams=\u0026lt;control script command line arguments\u0026gt;]] # parameters have the form \u0026#34;key=value\u0026#34;, they are concatenated with an \u0026#34;\u0026amp;\u0026#34; character # parameter \u0026#34;name\u0026#34; is mandatory for all sources, while codec, sampleformat and chunk_ms are optional # and will override the default codec, sampleformat or chunk_ms settings # Available types are: # pipe: pipe:///\u0026lt;path/to/pipe\u0026gt;?name=\u0026lt;name\u0026gt;[\u0026amp;mode=create], mode can be \u0026#34;create\u0026#34; or \u0026#34;read\u0026#34; # librespot: librespot:///\u0026lt;path/to/librespot\u0026gt;?name=\u0026lt;name\u0026gt;[\u0026amp;username=\u0026lt;my username\u0026gt;\u0026amp;password=\u0026lt;my password\u0026gt;][\u0026amp;devicename=Snapcast][\u0026amp;bitrate=320][\u0026amp;wd_timeout=7800][\u0026amp;volume=100][\u0026amp;onevent=\u0026#34;\u0026#34;][\u0026amp;nomalize=false][\u0026amp;autoplay=false][\u0026amp;params=\u0026lt;generic librepsot process arguments\u0026gt;] # note that you need to have the librespot binary on your machine # sampleformat will be set to \u0026#34;44100:16:2\u0026#34; # file: file:///\u0026lt;path/to/PCM/file\u0026gt;?name=\u0026lt;name\u0026gt; # process: process:///\u0026lt;path/to/process\u0026gt;?name=\u0026lt;name\u0026gt;[\u0026amp;wd_timeout=0][\u0026amp;log_stderr=false][\u0026amp;params=\u0026lt;process arguments\u0026gt;] # airplay: airplay:///\u0026lt;path/to/airplay\u0026gt;?name=\u0026lt;name\u0026gt;[\u0026amp;port=5000] # note that you need to have the airplay binary on your machine # sampleformat will be set to \u0026#34;44100:16:2\u0026#34; # tcp server: tcp://\u0026lt;listen IP, e.g. 127.0.0.1\u0026gt;:\u0026lt;port\u0026gt;?name=\u0026lt;name\u0026gt;[\u0026amp;mode=server] # tcp client: tcp://\u0026lt;server IP, e.g. 127.0.0.1\u0026gt;:\u0026lt;port\u0026gt;?name=\u0026lt;name\u0026gt;\u0026amp;mode=client # alsa: alsa:///?name=\u0026lt;name\u0026gt;\u0026amp;device=\u0026lt;alsa device\u0026gt;[\u0026amp;send_silence=false][\u0026amp;idle_threshold=100][\u0026amp;silence_threshold_percent=0.0] # meta: meta:///\u0026lt;name of source#1\u0026gt;/\u0026lt;name of source#2\u0026gt;/.../\u0026lt;name of source#N\u0026gt;?name=\u0026lt;name\u0026gt; source = pipe:///tmp/snapfifo?name=default There\u0026rsquo;s more in the \u0026ldquo;[stream]\u0026rdquo; section, but I stopped at the line we need to change, \u0026ldquo;source\u0026rdquo;.\nChange it to this:\nsource = pipe:///var/local/snapserver/snapfifo?name=default You\u0026rsquo;ll have to create /var/local/snapserver directory, and set the ownership:\n# mkdir /var/local/snapserver # chown snapserver /var/local/snapserver # chgrp snapserver /var/local/snapserver # systemctl start snapserver # systemctl status snapserver -n40 * snapserver.service - Snapcast server Loaded: loaded (/usr/lib/systemd/system/snapserver.service; enabled; preset: enabled) Active: active (running) since Mon 2026-04-06 17:36:01 UTC; 40s ago Invocation: 022347aa459a41d0b8edb82e4c5eeaf5 Docs: man:snapserver(1) Main PID: 1446 (snapserver) Tasks: 3 (limit: 18886) Memory: 2.4M (peak: 2.4M) CPU: 115ms CGroup: /system.slice/snapserver.service `-1446 /usr/bin/snapserver --logging.sink=system --server.datadir=/var/lib/snapserver Apr 06 17:36:01 testsnap systemd[1]: Started snapserver.service - Snapcast server. Apr 06 17:36:01 testsnap snapserver[1446]: (Snapserver) Version 0.34.0, revision 0ac55084 Apr 06 17:36:01 testsnap snapserver[1446]: (Snapserver) Stream plugin directory: \u0026#39;\u0026#34;/usr/share/snapserver/plug-ins\u0026#34;\u0026#39;, sandbox directory: \u0026#39;\u0026#34;/usr/share/snapserver/\u0026gt; Apr 06 17:36:01 testsnap snapserver[1446]: (Snapserver) Adding source: pipe:///var/local/snapserver/snapfifo?name=default Apr 06 17:36:01 testsnap snapserver[1446]: (Avahi) Adding service \u0026#39;Snapcast\u0026#39; Apr 06 17:36:01 testsnap snapserver[1446]: (Snapserver) Using HTTP host name: testsnap Apr 06 17:36:01 testsnap snapserver[1446]: (PcmStream) PcmStream: default, sampleFormat: 48000:16:2 Apr 06 17:36:01 testsnap snapserver[1446]: (PipeStream) PipeStream mode: create Apr 06 17:36:01 testsnap snapserver[1446]: (Server) Stream: {\u0026#34;fragment\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;host\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;/var/local/snapserver/snapfifo\u0026#34;,\u0026#34;query\u0026#34;:{\u0026#34;chunk_ms\u0026#34;:\u0026#34;20\u0026#34;,\u0026#34;codec\u0026#34;:\u0026#34;f\u0026gt; Apr 06 17:36:01 testsnap snapserver[1446]: (FlacEnc) Init - compression level: 2 Apr 06 17:36:01 testsnap snapserver[1446]: (ControlServer) Creating TCP control acceptor for address: ::, port: 1705 Apr 06 17:36:01 testsnap snapserver[1446]: (ControlServer) Creating HTTP acceptor for address: ::, port: 1780 Apr 06 17:36:01 testsnap snapserver[1446]: (StreamServer) Creating TCP stream acceptor for address: ::, port: 1704 Apr 06 17:36:01 testsnap snapserver[1446]: (Snapserver) Number of threads: 2, hw threads: 1 Apr 06 17:36:01 testsnap snapserver[1446]: (AsioStream) Error reading message: End of file, length: 0, ec: asio.misc:2 Apr 06 17:36:02 testsnap snapserver[1446]: (Avahi) Service \u0026#39;Snapcast\u0026#39; successfully established. Some online discussions about this have suggested /var/run/snapserver, but I have found that /var/run is cleaned up automatically during system boot and /var/run/snapserver gets deleted. You can automate the creation in the service startup, but /var/local/snapserver seems just fine to me.\nTesting # In order to test your SnapCast server, you\u0026rsquo;ll need some clients. The easiest one comes with the server, and it\u0026rsquo;s available in a web page.\nSnapweb # Open up a browser and go to port 1780 on whatever the address or hostname of your SnapCast server is. You should get a mostly black page with \u0026ldquo;Snapcast\u0026rdquo; and a hamburger menu at the top left, and a \u0026ldquo;Play\u0026rdquo; button on the top right. Go ahead and click on that \u0026ldquo;Play\u0026rdquo; button and you should get this:\nAt this point, you would be hearing something if there was anything streaming. Let\u0026rsquo;s pump some white noise into that fifo file back on the server:\ncat /dev/urandom \u0026gt; /var/local/snapserver/snapfifo Now you should hear something. If you do, then you have now confirmed that the server setup is correct, and if you have problems going forward, it\u0026rsquo;s probably not going to be the server, but your client setup.\nConclusion # This wraps up Part I.\nAt this point, you should have a basic understanding about how SnapCast works, and, if you\u0026rsquo;ve followed along, you\u0026rsquo;ll have a working SnapCast server. It\u0026rsquo;s not a lot of use yet, but we\u0026rsquo;ll get closer to that in Part II of this series.\nPart II continues with the Raspberry Pi SnapCast clients.\n","date":"20 May 2026","externalUrl":null,"permalink":"/homelab/snapcast0/","section":"Homelabs","summary":"A way to stream audio around your entire house using inexpensive RaspberryPi computers and SnapCast","title":"Whole Home Audio With SnapCast - Part I","type":"homelab"},{"content":" Introduction # One of the biggest advantages to JavaFX is it\u0026rsquo;s seamlessly integrated support for the \u0026ldquo;Observer\u0026rdquo; Pattern. This can also be one of the biggest challenges to learning JavaFX, because the library is so extensive it can become overwhelming.\nIn this article, we\u0026rsquo;re not going to go too deep into the details. This is intended for beginners so that they can get started and create their JavaFX applications the right way, and to avoid a lot of the pitfalls to which many beginners fall victim. By the end of this article you should have a good understanding of what JavaFX Properties are, how they work, and how you should use them.\nThe examples in this article are written in Kotlin. If you need some help understanding them, then refer to this article.\nWhat is a Property? # A Property is a special type of object designed to contain a data value and to support the \u0026ldquo;Observer\u0026rdquo; Pattern. It does this by keeping track of whenever the value is changed, and running code that other Property objects have registered with it when the value changes. Properties can also \u0026ldquo;bind\u0026rdquo; to other Properties, so that when one or more of those other Properties changes, its value will be recalculated.\nYou can use this ability to create a network of connections that link almost every aspect of your GUI to a set of data that you create. This means that you can create your layout, connect the layout Properties to other layout Properties and Properties that you create, and then discard any references to the Nodes in your layout because you won\u0026rsquo;t need them any more. You can control the layout by modifying the Properties that you have created.\nThat\u0026rsquo;s it in a nutshell, but there\u0026rsquo;s a lot of details you\u0026rsquo;ll need to understand.\nThe exhaustive nature of the Property library in JavaFX and it\u0026rsquo;s integration with all of the classes that make up JavaFX make a compelling argument that JavaFX is primarily designed to be used as a \u0026ldquo;Reactive\u0026rdquo; framework. We\u0026rsquo;ll go into this some more later, but for now we can just think of \u0026ldquo;Reactive\u0026rdquo; as what you get when you treat all your data as Properties and connect everything together through Bindings and Listeners.\nProperty Basics # There are a number of Property types, and we\u0026rsquo;ll see them all in a bit. For now, we\u0026rsquo;ll just look at StringProperty as an example. A StringProperty is a Property designed to hold a String (no big surprise there).\nAt it\u0026rsquo;s most basic, we can think of StringProperty as a container for a String. We have a get() method that returns a String and a void set(newVal : String) method to update the value. All of that is pretty straight-forward.\nval stringProperty: StringProperty = SimpleStringProperty() stringProperty.set(\u0026#34;abc\u0026#34;) println(stringProperty.get()) We can see here that our StringProperty is implemented as SimpleStringProperty. That\u0026rsquo;s because StringProperty is an abstract class, while SimpleStringProperty is concrete. However, there are no new methods in SimpleStringProperty, and we literally only need it because of its constructors. This means that there\u0026rsquo;s no reason to retain a reference to a SimpleStringProperty as a SimpleStringProperty over retaining it as a StringProperty.\nThe most common thing you might do with a StringProperty is to bind it to another StringProperty using the bind() method. This would look something like this:\nval x:StringProperty = SimpleStringProperty(\u0026#34;abc\u0026#34;) val y:StringProperty = SimpleStringProperty(\u0026#34;\u0026#34;) y.bind(x) Now, any changes in x will be immediately reflected in y. Also, any attempt to call the y.set() method will now generate a runtime error, indicating that a \u0026ldquo;bound value cannot be set\u0026rdquo;.\nBut why would you want to do this?\nUsually, one of those two StringProperties, either x or y is given to you from somewhere else. In JavaFX it will probably be an internal Property of some JavaFX class. Let\u0026rsquo;s take a look at a more typical example:\nval x = SimpleStringProperty(\u0026#34;abc\u0026#34;) val heading = Label() heading.textProperty().bind(x) Here we\u0026rsquo;ve replaced y with the textProperty() of a Label. Usually, x would be an element of a Presentation Model, or it might be a Property variable that\u0026rsquo;s global to your layout that you use to avoid coupling Nodes on your layout. Either way, whenever x changes, the text on the screen associated with that Label will change.\nProperty-Like Objects # In JavaFX there are a number of types that behave very much like Properties but aren\u0026rsquo;t actually Properties. I think a lot of people use the term \u0026ldquo;Property\u0026rdquo; as a catch-all, meaning not just Properties but also these other types.\nSo let\u0026rsquo;s have a look at them\u0026hellip;\nObservableValue # Sometimes you don\u0026rsquo;t want other code, or other people\u0026rsquo;s code, updating the value in your Property. Sometimes, you don\u0026rsquo;t want to be given something that you can update. In those cases, you can use the read-only ObservableValue interface. You can do anything you can do with a Property, but you cannot call set() or bind() on an ObservableValue.\nHere\u0026rsquo;s an example:\nfun createLabel(obVal : ObservableStringValue)= Label().apply{ textProperty().bind(obVal) } This is a function that returns a Label with its textProperty() bound to a supplied Property, but, since it isn\u0026rsquo;t going to update the value in that Property it just asks for an ObservableStringProperty. The client code can pass a StringProperty, a StringBinding or any other class that implements ObservableStringValue.\nReadOnlyProperty # This is something that you\u0026rsquo;ll see much more than you will ever create. In fact, you\u0026rsquo;ll probably never create a ReadOnlyProperty. There are lots of Nodes in JavaFX that have Properties that they really, really don\u0026rsquo;t want you to change. Internally, Node will manipulate the value, but it\u0026rsquo;s only will to give you something that you can never change. That something is ReadOnlyProperty.\nFor instance, Region has a widthProperty() function that returns a ReadOnlyDoubleProperty. You\u0026rsquo;ll find a Region.getWidth() function, but you won\u0026rsquo;t find a Region.setWidth() function. Region has things that you can do to influence how it calculates its width, but you cannot directly set it. So Region.widthProperty() doesn\u0026rsquo;t give you something that will let you change the width directly.\nFor most purposes you might have, you can treat ReadOnlyProperty exactly like ObservableValue.\nBinding # OK, Binding is a type as well as an action/method. You will need to get these straight in your head.\nFor the sake of clarity, I will always use Binding for the type, and \u0026ldquo;binding\u0026rdquo; for the action.\nEvery Binding will have a list of Properties that it is bound to, which are called its \u0026ldquo;dependencies\u0026rdquo;. Whenever any one of its dependencies changes, the next call to the Binding.get() will cause the Binding to recalculate its value.\nIt\u0026rsquo;s important to note that the dependencies are simply used to trigger a recalculation of the value. There is no rule that says that the Binding has to use any of these dependencies in its calculation, or that it cannot use other values that aren\u0026rsquo;t listed as dependencies. That being said, 99% of the time, the dependencies will be the values that are used in calculating the Binding's value.\nMore often than not, you\u0026rsquo;ll see Binding when you create one yourself, and usually you\u0026rsquo;ll use it as an argument to Property.bind(). However, you can pass a Binding around and it will behave pretty much the same as any ObservableValue.\nObservableLists # An ObservableList is very much like a Property wrapping a List of some sort. The key word there is \u0026ldquo;like\u0026rdquo;. ObservableLists are quite a bit different from the other Properties that we discuss here.\nInstead of reporting on changes to a value, it reports changes to a list of values. These changes include additions to the list, removals from the list and replacement of items in the list.\nInvalidation and Listeners # When you are talking about Properties and bind(), it\u0026rsquo;s all about \u0026ldquo;changes\u0026rdquo;. But there\u0026rsquo;s actually more going on than just that. There is a very, very important concept called \u0026ldquo;Invalidation\u0026rdquo;.\nSimply put, Properties need a mechanism to tell other properties that they need to come and fetch the latest value that they hold, because the one that they last read may not be correct any more. This mechanism is called \u0026ldquo;Invalidation\u0026rdquo;. Whenever you call Property.set(), that will cause an internal valid flag inside the Property to flip to false. The only way to flip the valid flag back to true is to call Property.get().\nSo how do the other Properties know that our Property has been invalidated?\nThey know because they register Listeners with our Property. A listener is just a snippet of code that\u0026rsquo;s called by the Property that just invalidated and is donated by the listening object. Note that a Listener can be created by any code, and doesn\u0026rsquo;t need to be part of a Property itself.\nInside the Property it keeps a list of all of the Listeners that have been registered with it. When it is invalidated, the Property will then execute each of those Listeners, one after the other.\nWarning If a Property is already invalid, then it cannot be invalidated again and it will not execute any Listeners until after it has been revalidated.\nInvalidation Chains # When a Property is bound to another Property it registers a special kind of Listener with that Property that simply invalidates the second Property too. Then, if that Property is, in turn, bound to another Property then its Listener will simply invalidate the third Property. And so on, and so on, and so on.\nNormally, at some point at the end of the chain is a Listener that actually calls get() on the Property it\u0026rsquo;s listening to, and that will trigger the binding logic to call get() on the next Property back up the chain. All the way back to the beginning. And then coming back down the chain, all of those Properties will be revalidated.\nThese chains are why an invalidated Property might only possibly have a different new value. And why invalidation doesn\u0026rsquo;t mean that a value has changed. Let\u0026rsquo;s look at an example:\nval x = SimpleIntegerProperty(3) val y = SimpleBooleanProperty(false) y.bind(x.greaterThan(7)) x.set(6) There\u0026rsquo;s actually three Properties in this snippet of code, not two. That\u0026rsquo;s because x.greaterThan(7) creates a Binding and that\u0026rsquo;s what y is bound to.\nThe line x.set(6), will invalidate x, and then the Binding that was created from x.greaterThan(7), and then y. However, even though y has been invalidated, it\u0026rsquo;s value hasn\u0026rsquo;t changed, and is still false. But anything that\u0026rsquo;s listening to y can\u0026rsquo;t know that until it calls y.get(), which will cause x.greaterThan(7) to re-evaluate (which will call x.get()) and then everything will be re-validated.\nThis is important to know and to understand, but it\u0026rsquo;s not something that you\u0026rsquo;ll need to deal with every day. A more usual use case for this is something like this:\nval x = SimpleIntegerProperty(3) val label = Label(\u0026#34;Too Big!\u0026#34;) label.visibleProperty().bind(x.greaterThan(7)) x.set(6) You can count on the fact the that internal workings of Label are going to respond immediately when its visible Property invalidates, and it will call visibleProperty().get() right away, causing everything to revalidate.\nInvalidation Listeners # The first kind of Listener than can be registered with a Property is an InvalidationListener. An InvalidationListener is one that fires whenever the Property becomes invalid, and the code that it executes is a Runnable. It is totally up to the InvalidationListener to decide if it wants to call get() on the Property which would revalidate it - but it\u0026rsquo;s usually recommended to do so.\nWhile you may or may not use InvalidationListeners yourself, you should know that they are the foundation of all of the other techniques described here. ChangeListeners, Subscriptions and Bindings all rely on InvalidationListeners internally.\nChangeListeners # ChangeListeners are different from InvalidationListeners in two ways:\nThey trigger only when the value actually has changed. They receive both the old value and the new value of the Observable in their Consumer. Here\u0026rsquo;s how you would create and register a ChangeListener on a Property\nval x:StringProperty = SimpleStringProperty(\u0026#34;abc\u0026#34;) x.addListener(ChangeListener{obVal, oldVal, newVal -\u0026gt; println(\u0026#34;Value of $obVal has changed from $oldVal to $newVal\u0026#34;) }) You can see that a ChangeListener takes a reference to the Property itself, the old value and the new value. Since you get the reference to the Property this means that you can create a single ChangeListener and install it on multiple Properties and still be able to determine which Property triggered the ChangeListener.\nSubscriptions # Now that you know about Listeners you should also know that you shouldn\u0026rsquo;t use them nowadays. We\u0026rsquo;ve got better stuff!\nIn version 21 of JavaFX, we got a new feature called Subscription. In a nutshell, Subscriptions provide the same functionality as Listeners but are better for two reasons:\nThey are easier to use. They are easier to keep track of and to remove, especially when using lambdas to define the action code. Three Types of Subscriptions # There are three different ways to create a Subscription, although all three involve calling a method named subscribe(). Let\u0026rsquo;s take a look at them, concentrating on the different parameters that you pass to subscribe().\nPassing a Runnable [() -\u0026gt; Unit] # This is equivalent to creating an InvalidationListener. The Runnable that you pass to it will be invoked whenever the Property that subscribe() is called on invalidates.\nPassing a Consumer [(x) -\u0026gt; Unit] # This is the equivalent to creating a ChangeListener and the Consumer will be invoked whenever the Property that subscribe() is called on changes its value. It\u0026rsquo;s way easier to set up than a ChangeListener however, and the only parameter passed to the Consumer is the new value of the Property.\nThere is one other important and useful feature of this Subscription: When this method is called, it immediately invokes the Consumer. Let\u0026rsquo;s look at what this means\u0026hellip;\nImagine that you have some action that you want taken every time that a Property changes its value, so you set up a ChangeListener. You\u0026rsquo;d do something like this:\nnameProperty.addListener(ChangeListener {obVal, oldVal, newVal -\u0026gt; someMethod(newVal)}) But what if its possible that nameProperty already has a value at the time that you add this ChangeListener and you want to run someMethod() on that original value. Then you\u0026rsquo;d have to do this:\nnameProperty.addListener(ChangeListener {obVal, oldVal, newVal -\u0026gt; someMethod(newVal)}) someMethod(nameProperty.get()) This is intrinsically different from setting up a Binding on nameProperty which would automatically execute computValue() immediately when it\u0026rsquo;s created. This is a huge \u0026ldquo;gotcha!\u0026rdquo; for many programmers, because it\u0026rsquo;s so easy to forget that nameProperty might already have a value depending on the circumstances in which this listener is added.\nWith a Subscription, you don\u0026rsquo;t need to do any of that. This is enough:\nnameProperty.subscribe{someMethod(it)} The {someMethod(it)} will be executed immediately, passing whatever the current value of nameProperty happens to be.\nPassing a BiConsumer [(x,y) -\u0026gt; Unit] # This is equivalent to creating a ChangeListener and it will be invoked whenever the Property this it is called on changes its value. The two parameters passed to the BiConsumer are the old value and the new value of the Property.\nHowever, unlike the previous version, passed a Consumer, this version will not be executed immediately when subscribe() is called. It makes sense, though, as there won\u0026rsquo;t be an \u0026ldquo;old value\u0026rdquo; at this point either.\nRemoving Subscriptions # If you look at this example from above:\nnameProperty.addListener(ChangeListener {obVal, oldVal, newVal -\u0026gt; someMethod(newVal)}) What if you later want to remove that ChangeListener? The only way to remove it is to call nameProperty.removeListener(), but that requires that you pass the Listener to removeListener(), which we don\u0026rsquo;t have. We would have to do this:\nval changeListener = ChangeListener {obVal, oldVal, newVal -\u0026gt; someMethod(newVal)} nameProperty.addListener(changeListener) . . . nameProperty.removeListener(changeListener) However, all the versions of subscribe() return Subscription. Which means that we can do this:\nval subscription = nameProperty.subscribe{someMethod(it)} . . . subscription.unsubscribe() You should also know that Subscription contains methods to combine Subscriptions together, so that you can call unsubscribe() on all of the Subscriptions at the same time, or to pass them between methods and classes as a single parameter.\nBindings # There are three ways to create a Binding:\nThe \u0026ldquo;Fluent API\u0026rdquo; Static methods in the Bindings class The \u0026ldquo;Low Level API\u0026rdquo; (creating a custom class) Inside the JavaFX library, the Fluent API works by calling methods in the Bindings class which, in turn, creates custom Binding classes on the fly. So you can see that everything eventually boils down to custom Binding classes.\nBecause of that, we\u0026rsquo;ll look at them in reverse order:\nCustom Binding Class # Let\u0026rsquo;s look at a simple example:\nclass NameBinding(private val fName: ObservableValue\u0026lt;String\u0026gt;, private val lName :ObservableValue\u0026lt;String\u0026gt;) : StringBinding() { init { super.bind(fName, lName) } override fun computeValue() : String { return \u0026#34;${lName.get()}, ${fName.get()}\u0026#34; } } The first thing to note is that we are extending StringBinding. Just like with Properties, we have about 5 different common types of Bindings to hold different kinds of values.\nThe next thing to note is that we are accepting ObservableValue\u0026lt;String\u0026gt; as our dependencies. This means that we are accepting the widest possible range of Observable types that we possibly can. This will work with Property\u0026lt;String\u0026gt;, StringProperty, StringBinding and ObservableStringValue, which keeps our Binding as flexible as possible.\nThe init{} section in Kotlin is very much like a constructor in Java. This init{} section establishes the two passed parameters as dependencies for the binding by passing them to super.bind(). This is super important.\nFinally, we have computeValue(). This is the method that\u0026rsquo;s called to actually figure out what the Binding's value is. Here we are just combining them together with a \u0026ldquo;, \u0026quot; between them. Note that this method returns String not an Observable wrapping String.\nIn a nutshell, a Binding consists of a list of dependencies, and a computeValue() method which is called to, well\u0026hellip; compute the current value of the Binding. Internally, Binding holds a copy of the latest value that it computed, and will return that if it is validated. However, when any of the dependencies becomes invalidated, the Binding will also become invalidated. The next call to get() will call computeValue(), reset the internal value and revalidate the Binding\nOf course, you can also create something very similar as an anonymous inner class, if you like.\nThe Bindings Class # From the JavaDocs:\nBindings is a helper class with a lot of utility functions to create simple bindings.\nThat\u0026rsquo;s an understatement. There are literally hundreds of static methods in this class, and they all build Bindings.\nIf we wanted to do the same thing as our custom Binding example, here\u0026rsquo;s how we would do it with Bindings:\nval binding = Bindings.createStringBinding({\u0026#34;${lName.get()}, ${fName.get()}\u0026#34;}, fName, lName) This is the definition of the method:\npublic static StringBinding createStringBinding(Callable\u0026lt;String\u0026gt; func, Observable... dependencies) In this case, it might be easier to think of Callable\u0026lt;String\u0026gt; as a Supplier\u0026lt;String\u0026gt;. It\u0026rsquo;s a function that takes no input parameters and returns a String.\nIt\u0026rsquo;s easy to see how this maps to our custom Binding. The first parameter corresponds to whatever code that we would put into computeValue() and the other parameters are the same dependencies that we passed to super.bind().\nYou should also know that Bindings has a method called concat() which is specifically designed to do this kind of Binding. We could use it instead:\nval binding = Bindings.concat(lName, \u0026#34;, \u0026#34;, fName) It\u0026rsquo;s clearly easier to use this method.\nYou should spend some time looking at the JavaDocs for Bindings. It\u0026rsquo;s worth the effort.\nThe Fluent API # The Fluent API provides a set of methods for Observables that allow you to transform, combine, compare and do various operations on the Observables, returning new Observables that are bound to the original Observable. That sounds like a mouthful, but it allows you to do things like this:\nval binding = integerProperty.add(2) Whenever integerProperty changes, binding will be updated to have a value 2 greater than integerProperty.\nKeeping with our name combination example, we can do this:\nval binding = lName.concat(\u0026#34;, \u0026#34;).concat(fName) In practice, the Fluent API is most useful when the operations are simple and not too plentiful. After a while, a big long string of .this() and .that() starts to become more difficult to read, understand and maintain. In those cases you\u0026rsquo;re better off using one of the other methods.\nThe ObservableValue.map() Function # Since JavaFX 19 there is a new facility that makes the Fluent API partially obsolete, ObservableValue.map() and ObservableValue.orElse().\nObservableValue.map() works very much like Stream.map() in that it creates a new ObservableValue by translating the value in the original ObservableValue. In many ways, it\u0026rsquo;s like a Binding with a single dependency. The parameter that you pass to ObservableValue.map() is a Function that transforms the value. And the result doesn\u0026rsquo;t have to be the same type as the input value either.\nObservableValue.map() is nice because you can do just about anything you want in normal Java/Kotlin code dealing with the value of the ObservableValue as a primitive. This is virtually guaranteed to be easier to read than the Fluent API unless the equivalent Fluent API calls are dead simple. I\u0026rsquo;ll give you the example from invalidation section above:\nval x = SimpleIntegerProperty(3) val label = Label(\u0026#34;Too Big!\u0026#34;) label.visibleProperty().bind(x.map{it \u0026gt; 7}) Now, maybe that\u0026rsquo;s not simpler than x.greaterThan(7), but what if it was this?\nval x = SimpleIntegerProperty(3) val label = Label(\u0026#34;Invalid!\u0026#34;) label.visibleProperty().bind(x.map{((it \u0026gt; 7) \u0026amp;\u0026amp; (it \u0026lt; 20)) || (it == 23)}) That might get a bit ugly with the Fluent API.\nThe only place where you cannot use this instead of the Fluent API is when you are combining multiple ObservableValues together. However, you can use both ObservableValue.map() and the Fluent API together:\nval binding = lName.map(\u0026#34;The Name is: $it, \u0026#34;).concat(fName) ObservableValue.orElse() is the companion function to ObservableValue.map() and it will return an ObservableValue with a fixed value if the original ObservableValue contains null.\nTypes of Properties # If you want to create a brand new place to store a value that you want to be observable, you\u0026rsquo;re going to need to instantiate a Property of some sort. There are a number of different types of Properties, and you\u0026rsquo;ll need to pick the right one. Notably, you\u0026rsquo;ll probably need to pick from one of these:\nStringProperty BooleanProperty IntegerProperty DoubleProperty ObjectProperty ObjectProperty is used for pretty much anything that isn\u0026rsquo;t a String, Boolean, Integer or Double. This could be a LocalDate, BigDecimal, an Enum or any other custom class you have created.\nBut you\u0026rsquo;ll find out pretty quickly that you can\u0026rsquo;t instantiate any of these classes because they are all abstract. But each one of these classes has a concrete subclass that starts with \u0026ldquo;Simple\u0026rdquo;. For example, SimpleStringProperty. Instantiate using these classes, but type your variables as the super-class.\nLike this:\nval nameProperty : StringProperty = SimpleStringProperty(\u0026#34;Fred\u0026#34;) val ageProperty : IntegerProperty = SimpleIntegerProperty(4) val dateProperty : ObjectProperty\u0026lt;LocalDate\u0026gt; = SimpleObjectProperty(LocalDate.now()) Notice that I\u0026rsquo;ve used val here and not var. This is the same as using final in Java. It\u0026rsquo;s a good practice to establish all of your Properties as final. The value inside them can change, even if they are final, but it will probably break everything if something changes the variable reference for the Property itself.\nNote Always make your Property variable references immutable using \u0026quot;final\u0026quot; in Java and \u0026quot;val\u0026quot; in Kotlin.\nThe Node Properties # If you take a look at the JavaDocs for Node and any of its subclasses, you\u0026rsquo;ll find a section near the top called \u0026ldquo;Property Summary\u0026rdquo;, listing all of the Properties included in that class. For subclasses of Node you\u0026rsquo;ll also see a section that shows all of the Properties inherited from its super-classes.\nJust about every aspect of the Node classes are represented by a Property.\nMost notably, for Node itself we have disable and disabled, effect, focused, hover, id, layoutX and layoutY, managed, opacity, scaleX and scaleY, scene, style, translateX and translateY and visible. Additionally, all of the various EventHandlers are also stored as Properties.\nWhat is important about these is that for virtually all of the Properties that are writeable, changing their values will impact the way that the Node behaves or appears in the layout. Things like scaleX/Y and translateX/Y will change the size and position of a Node and others like focused, disabled and hover will have corresponding PseudoClasses that can be styled.\nMoving to Region and its subclasses, we get width, minWidth, maxWidth and prefWidth along with height,minHeight,maxHeight and prefHeight.\nUtility classes also have Properties. Animations have things like autoReverse, cycleDuration, delay, status, while Transition adds interpolator. Task has all of its EventHandlers as Properties, along with progress, message, title, value, state and workDone.\nIn fact, throughout the entire library you\u0026rsquo;ll be hard pressed to find a single get{Something}() method that doesn\u0026rsquo;t delegate to somethingProperty().get(). They are literally everywhere.\nInfo This means that there\u0026rsquo;s almost nothing in JavaFX that you cannot connect to using the Observer Pattern.\u0026rdquo; %}\nGeneral Property Advice for Reactive GUI Applications # When you see the extent to which JavaFX incorporates the Observer Pattern into every element of every class, and when you see the wealth of library support to do so much with the Observer Pattern, it becomes obvious that JavaFX is designed from the ground up to be a Reactive library.\nBut what does that mean?\nI\u0026rsquo;ll steal from WikiPedia\nIn computing, reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change. With this paradigm, it is possible to express static (e.g., arrays) or dynamic (e.g., event emitters) data streams with ease, and also communicate that an inferred dependency within the associated execution model exists, which facilitates the automatic propagation of the changed data flow.\nWell, that\u0026rsquo;s mouthful, but what it really describes is an approach where your code constructs a network that defines how data will move automatically between objects, and where data changes trigger predefined actions. Which is pretty much what Properties, Bindings, Listeners and Subscriptions do.\nThis means that your code shouldn\u0026rsquo;t be oriented towards doing stuff, but oriented towards connecting things together and setting up triggers to do things when certain conditions are met. And this looks vastly different from the imperative way that you would program up a Swing application. At first, it can seem difficult to do, but there is so much stuff that the Reactive features of JavaFX do for you - and do very well - that your application code becomes much simpler to understand than if you had built it in an imperative programming style.\nOkay, so how should you use all this stuff? Here are some general guidelines you should follow:\nUse a Presentation Model to Store Your GUI\u0026rsquo;s \u0026ldquo;State\u0026rdquo; # This is the key element of Reactive programming. Take all of the possible elements of the \u0026ldquo;State\u0026rdquo; of your GUI and represent them with Properties in a \u0026ldquo;Presentation Model\u0026rdquo;. This can include the following:\nThe data stored in the Nodes Properties shared between Nodes Values that influence how Nodes work Values that are used to trigger actions. Use Observer Pattern Methods in Your Layouts # This is probably the most important guideline. Do not use methods like set() in your layout code unless it is for something guaranteed to be static.\nFor instance:\nlabel.setText(model.nameProperty.value) and\nval label = Label(model.nameProperty.value) are most likely bad. However:\nval label = Label(\u0026#34;Name:\u0026#34;) is fine. Similarly:\nhBox.setPadding(Insets(20.0)) is perfectly good. Providing, of course, that you not planning on changing the padding in the HBox or the text in the Label.\nWhat you should be doing, when the values are not 100% static, is this:\nlabel.textProperty().bind(model.nameProperty) A good rule of thumb is that if some value in a Node Property might change, then create a separate Property and bind the Node Property to it. Then never access the Node Property again.\nIf that changing value is 100% local to your layout then you can just create a Property field in your layout class. But, if that changing value might be used outside of your layout code, then create it as a field in your Presentation Model so that it can be accessed by your application/business logic.\nUse Bindings Whenever You Can # The primary tool for connecting data is Binding. Your first question should always be, \u0026ldquo;How can I do this with a Binding?\u0026rdquo; If you can\u0026rsquo;t figure out how, then maybe you\u0026rsquo;ll need to use a Listener or a Subscription.\nUnderstand the Difference Between Actions and State Changes # The sounds banal, but actions are actions and state changes are state changes.\nNote A ChangeListener or a Subscription is primarily a tool to convert a state change into an action.\nUsing a ChangeListener to do nothing more than to propagate state changes through your application is usually a bad idea. Even when the data elements being updated are primitive data types. You\u0026rsquo;re likely better off to put them into Properties and the use Binding to connect them. Yes, there\u0026rsquo;s overhead with Properties, but you\u0026rsquo;re highly unlikely to see any performance improvement by using Listeners and primitives instead.\nThere are some parts of JavaFX that are inherently actions. For instance, running an Animation will require an action (although Animations do have properties that can be bound). So if you need to run an animation in response to a state change, then you\u0026rsquo;ll need to use a Listener or Subscription to do so.\nResponses to user interactions like mouse button clicks are usually actions. Basically, anything that uses an EventHandler of some sort is going to be an action. It\u0026rsquo;s entirely possible to use this code to update state elements that are then going to propagate through your application via Bindings.\nIt\u0026rsquo;s also possible to trigger business/application logic due to a state change. In this case again, Listeners and Subscriptions might be appropriate.\nMaking a change to a layout (meaning to add or remove Nodes), is also generally going to require an action. However\u0026hellip;\nUse Node Properties to Make Your Layouts Behave Dynamically # In a Reactive GUI, static layouts are generally best. These means that you generally do not add or remove Nodes. Two Node Properties are key here: managed and visible. A Node that has visible set to false will not appear in the layout. However, if its managed property is set to true, it will still be allocated space on the layout (which usually means that you\u0026rsquo;ll see an empty space). This is usually not desirable, so you can do:\nnode.managedProperty().bind(node.visibleProperty()) and it will disappear entirely.\nGenerally speaking, unless you\u0026rsquo;re doing something way out on the fringes of normal business applications, there\u0026rsquo;s no advantage to removing Nodes from your layout, and changing the layout is inherently slow. Even if you have hundreds of invisible/unmanaged Nodes in your layout, you won\u0026rsquo;t see any performance degradation as JavaFX won\u0026rsquo;t waste CPU cycles on them.\nThis means that you can have several container classes inside something like a StackPane and have the visible/managed Properties of each container bound to the selected Property of something in a ToggleGroup (like a CheckBox or a ToggleButton). Then only one container will be visible at any given time, depending on which element of the ToggleGroup is selected. To the user it will look like the layout is changing, but it isn\u0026rsquo;t really.\nA Reactive JavaFX Example # Here\u0026rsquo;s about the simplest example I could think up that uses as many of these concepts as possible. A simple password change screen with a requirement that the new password is at least 8 characters long\u0026hellip;\nFirst, we have the Presentation Model.\nclass PresentationModel { val password: StringProperty = SimpleStringProperty(\u0026#34;\u0026#34;) val okToSave: BooleanProperty = SimpleBooleanProperty(false) init { okToSave.bind(password.map { it.length \u0026gt;= 8 }) } } Presumably whatever business logic that would go along with this screen would require the new password in order to save it. Next, the minimum length of the new password would also be something that would be decided by the business logic, along with any other requirements about the password. So this stuff all goes into the Presentation Model.\nHere\u0026rsquo;s the screen:\nclass ReactiveExample : Application() { private val model = PresentationModel() private val showWarning = model.okToSave.not().and(model.password.isNotEmpty) private val errorPC = PseudoClass.getPseudoClass(\u0026#34;error\u0026#34;) private val warningPC = PseudoClass.getPseudoClass(\u0026#34;warning\u0026#34;) override fun start(stage: Stage) { stage.scene = Scene(createContent(), 400.0, 300.0).apply { ReactiveExample::class.java.getResource(\u0026#34;example.css\u0026#34;)?.toString()?.let { stylesheets += it } } stage.show() } private fun createContent() = BorderPane().apply { center = createCentre() bottom = Button(\u0026#34;Save\u0026#34;).apply { disableProperty().bind(model.okToSave.not()) } padding = Insets(40.0) } private fun createCentre(): Region = VBox(10.0).apply { children += HBox(6.0).apply { children += Label(\u0026#34;New Password:\u0026#34;) children += TextField().apply { textProperty().bindBidirectional(model.password) } } children += Label(\u0026#34;New password must be at least 8 characters\u0026#34;).apply { styleClass += \u0026#34;status-label\u0026#34; visibleProperty().bind(showWarning) model.password.map { ((it.isNotEmpty()) \u0026amp;\u0026amp; (it.length \u0026lt; 5)) } .subscribe { newVal -\u0026gt; this.pseudoClassStateChanged(errorPC, newVal) } model.password.map { ((it.length \u0026gt;= 5) \u0026amp;\u0026amp; (it.length \u0026lt; 8)) } .subscribe { newVal -\u0026gt; this.pseudoClassStateChanged(warningPC, newVal) } } } } fun main() = Application.launch(ReactiveExample::class.java) First, showWarning is strictly a GUI concept, so it isn\u0026rsquo;t in the Presentation Model, it\u0026rsquo;s just a global variable in the layout code. We also have some PseudoClasses here to control the colour of the status Label. Application of PseudoClasses is inherently an \u0026ldquo;action\u0026rdquo; so we need to use Subscriptions to implement them. Finally, the visibility of the status Label is bound to showWarning, while the Button.disableProperty() is bound to model.okToSave.not().\nHere\u0026rsquo;s the CSS file:\n.root {} .status-label { -fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: black; } .status-label: warning { -fx-text-fill: blue; } .status-label: error { -fx-text-fill: red; } When it launches, it looks like this - with no status label and the Button disabled:\nAs soon as you type a few characters, the warning shows up:\nIt changes to blue when you get closer to having enough characters:\nAnd then the Button enables and the status Label disappears once you have 8 or more characters:\nYou can see here that the layout itself is absolutely static. All of the Nodes in the layout are created, configured and inserted into layout without creating any variable references to them, and they are never accessed by any other code or any other part of the layout. Yet, the Label appears and disappears, changes colour and generally behaves dynamically. The Button changes from disabled to enabled based solely on the Binding that was created when it was added to the layout.\nFinally, whatever business logic is going to save that new password has access to it directly from the Presentation Model. There is no reason to scrape it out of the TextField because the bidirectional Binding keeps it synchronized with the Presentation Model.\nConclusion # This article should have given you the answers to all the questions you\u0026rsquo;ve been scratching your head over about Properties. There\u0026rsquo;s enough information here to get a beginner started on the right track, and it might possibly be years before you\u0026rsquo;ll feel like you need to take a deeper dive into the subject to solve real problems that you encounter with writing real applications. And when you do get there, you can find out everything you\u0026rsquo;ll ever need to know from my Guide To the Obervable Classes.\n","date":"29 September 2024","externalUrl":null,"permalink":"/javafx/elements/beginners-properties/","section":"JavaFX","summary":"If you are a beginner with JavaFX and are wondering, “What are all these Property things anyway?”, then this is the article for you. Besides the Properties themselves, we also cover the basics of using them with Bindings and Listeners. Read this article and you should have a good idea how to start out with JavaFX Properties.","title":"An Introduction to Properties","type":"javafx"},{"content":"Just like the Nodes, there are a large number of classes and interfaces that extend from the Observable interface. This includes the ObservableList and Property interfaces which used extensively in just about any JavaFX application.\nObservables are wrappers around various data types, and the values inside them can be imperatively accessed through getters and setters.\nObservables are used in two ways. The most important is Bindings, the other is ChangeListeners.\n","date":"29 September 2024","externalUrl":null,"permalink":"/javafx/elements/observables/observable-objects/","section":"JavaFX","summary":"The absolute core element of observability in JavaFX is the Property and it’s related sub-classes and interfaces. Properties are used for virtually every aspect of every screen Node that you can interact with. Values in TextFields and Labels are stored in a StringProperty, while elements like maxWidth and minWidth are stored in DoubleProperties. The items in a ListView or a TableView are stored in an ObservableList.\nThe key to creating Reactive applications in JavaFX is to use these Properties as observables, and not simply annoying wrappers around their values. Create Bindings to link them together instead of stuffing values in them and scraping values out them.\nBut to do that, you need to understand they work, and how to use them properly.\n","title":"Observable Objects","type":"javafx"},{"content":" Introduction # One of the key elements to creating Reactive applications with JavaFX is understanding how the various Obseravble interfaces and classes work; how they relate to each other and how to use them best.\nIf you surf over to the JavaDoc page for Observable, you\u0026rsquo;ll see lists of subinterfaces and implementing classes that looks like this:\nThat really does look complicated, and in this article we\u0026rsquo;re going to break it down and put some structure to it so that you can understand exactly how you should be using the various classes and interfaces in a thoughtful and logic manner in your applications.\nThe Main Classes and Interfaces # Even though that huge list of interfaces and classes look daunting, there are lots of entries intended to wrap specific data types. If we strip those away, and just concentrate on the classes and interfaces that are defined generically, we can create a diagram like this:\nIn order to understand how this all goes together, it\u0026rsquo;s best to look first at the interfaces, as they tell us what the functionality is, and then look at the classes because they tell us how the functionality is implemented.\nSo we\u0026rsquo;ll start with the interfaces\u0026hellip;\nThe Interfaces # Starting at the Top: Observable # At the top of the chart, you\u0026rsquo;ll find the interface, Observable. This is the root for all of the other classes and interfaces, but it\u0026rsquo;s really quite simple and only defines three methods: addListener(), removeListener() and subscribe(). These are all related to the process called \u0026ldquo;Invalidation\u0026rdquo;.\nInvalidation is the key concept with Obseravbles. When an Observable has become \u0026ldquo;invalidated\u0026rdquo; it means that it might have changed its value, and all of the objects that have registered InvalidationListeners with the Observable have those InvalidationListeners triggered. It\u0026rsquo;s up to these other classes to recheck the value of the Observable inside their Listeners.\nIt\u0026rsquo;s possible for an Observable to be invalidated without having its value change. Consider this code:\nval numberProperty = SimpleIntegerProperty(2) val booleanObservable = numberProperty.greaterThan(5) numberProperty.setValue(4) After the third line of code runs, booleanObservable will have been invalidated, but its value still remains unchanged as false.\nOne important point about invalidation is that Observables remain invalidated until the value is read, at which point they are validated again.\nWarning An Observable that has been invalidated cannot be invalidated again until after it has been revalidated.\nAt this point, we don\u0026rsquo;t have any methods defined that will read and revalidate an Observable\u0026hellip;\nObservableValue # The next interface down is ObservableValue, which extends Observable. The key method in this interface is ObservableValue.getValue(), which allows us to actually read the value and will re-validate the Observable.\nNow that we can read the value we can have the methods related to ChangeListeners, including those related to Subscriptions. The other methods it adds are the map() and flatMap() methods used for transforming an ObservableValue into a different ObservableValue.\nBetween these two interfaces, we get the core functionality for observability: invalidation and changes. Changes, of course, are based on invalidation, and every ChangeListener has, at the heart of its implementation, an InvalidationListener.\nThere is also the ObservableObjectValue\u0026lt;T\u0026gt; interface, which introduces the get() method, which at this point is effectively identical to getValue().\nReadOnlyProperty and WritableValue # These are the two parent interfaces for all of the Property classes.\nReadOnlyProperty is, in my opinion, a useless and annoying interface because it simply introduces the getBean() and getName() methods. These are presumably meaningful methods if you are serializing your Properties, but honestly, who\u0026rsquo;s going to do this? Properties are, by their nature GUI elements and I\u0026rsquo;m not sure how you\u0026rsquo;d ever need Java Beans for them.\nHowever, because of this, all of our concrete Properties will need to implement these two methods.\nThe interface WritableValue gives us the setValue() method. Now we can actually put something into an Observable! There is also WritableObjectValue which adds the set() method, which is (at this point) identical to setValue().\nProperty # The Property interface brings together WritableValue and ObservableValue and adds the ability to bind to other observables. We get bind() and bindBidirectional(), and their corresponding unbinding methods. Also there is the isBound() method that will tell us if the Property has been bound to something.\nYou can see now why binding has to come below WritableValue. Whatever mechanism underlies a binding function will need to call setValue() to work.\nBinding # Binding is the last interface on the chart. It doesn\u0026rsquo;t seem to add much to Observable and ObservableValue except a method to force the Binding to invalidate, a method to check if the Binding is valid and a method to get the dependencies of the Binding.\nThese are all key methods, however. Since a Binding is the main way to connect Observables together, it really works through controlling validation. These methods give the basic tools to construct custom Bindings.\nThe Classes # Everything else on the chart is a class, let\u0026rsquo;s take a look at them.\nObjectExpression # This is a class you might not have heard about, but it is the root class for all of the Property classes, even though the diagram goes diagonally down to ObjectProperty.\nThis class provides the core functionality for the \u0026ldquo;Fluent API\u0026rdquo; for creating bindings. In ObjectExpression we have several versions of asString(), each of which provides a StringBinding. We also have methods for equality, inequality and checking for a Null value, each of which creates a BooleanBinding. That\u0026rsquo;s not really much, but when we look at the typed Observable classes in the Part II we\u0026rsquo;ll see a lot more functionality with the Fluent API.\nThis is the only class in the chart that introduces new concrete methods that don\u0026rsquo;t implement methods already defined by any of the interfaces in the chart.\nInfo Every class that extends from ObjectExpression can use the Fluent API\nReadOnlyObjectProperty # This class extends ObjectExpression and implements ReadOnlyProperty. As we\u0026rsquo;ve seen, there\u0026rsquo;s only two methods in ReadOnlyProperty and they aren\u0026rsquo;t much practical use. ReadOnlyObjectProperty doesn\u0026rsquo;t provide any implementation for these methods either. There really isn\u0026rsquo;t a practical reason to pass an Observable around as a ReadOnlyObjectProperty over an ObjectExpression, but you tend to see ReadOnlyObjectProperty used more often (we\u0026rsquo;ll see why soon).\nReadOnlyObjectProperty can also be thought of as very much like ReadOnlyProperty but with the ability to use the Fluent API to create bindings.\nObjectProperty # The best way to think about ObjectProperty is an implementation of Property but, since it inherits from ObjectExpression through ReadOnlyObjectProperty, also has the methods for creating bindings via the Fluent API. However, it only has implementations for bidirectional binding and setting the value. And it\u0026rsquo;s abstract, so you cannot use it directly without extending it and supplying a ton of functionality.\nThis is, however, a great class to pass your already instantiated Properties around as, or to declare your variables as. Like this:\nval someProperty : ObjectProperty\u0026lt;ClassA\u0026gt; = SimpleObjectProperty(someClassA) This is a bit more versatile than passing around Property\u0026lt;ClassA\u0026gt;, because it will enable the Fluent API.\nObjectPropertyBase # This is the first truly useful abstract class for Properties for extending to create custom classes. This class implements almost all the remaining methods defined in the various interfaces up hierarchy. We get methods to bind and unbind, add and remove Listeners and a get() method.\nThe methods that are missing are the two silly Java Bean related methods. This is the class that you probably want to extend from to create your own Property classes, especially if you want to ignore Java Bean stuff as much you can.\nIf you look at the JavaFX source code, you\u0026rsquo;ll see lots of examples of internally created custom Properties that are made by directly extending ObjectPropertyBase.\nSimpleObjectProperty # This is the class that everyone is familiar with instantiating, as it\u0026rsquo;s the only \u0026ldquo;normal\u0026rdquo; Property class in the chart that isn\u0026rsquo;t abstract.\nWhat makes SimpleObjectProperty different from ObjectPropertyBase?\nIt adds the getBean() and getName() methods and the infrastructure and constructors to set their values. That\u0026rsquo;s it. You\u0026rsquo;re probably never going to use those two methods either, nor are you going to use the constructors to set their values. So you can treat this is pretty much equivalent to ObjectPropertyBase 99% of the time.\nHowever, since this and ObjectPropertyBase do not add any methods to ObjectProperty other than the SimpleObjectProperty constructors, there\u0026rsquo;s no reason to retain a reference to a Property as either SimpleObjectProperty or ObjectPropertyBase. Consider these to be \u0026ldquo;implementation only\u0026rdquo; classes.\nReadOnlyObjectPropertyBase and ReadOnlyObjectWrapper # ReadOnlyObjectPropertyBase is very similar to ObjectPropertyBase in that it is an abstract class that has almost all of the interface methods implemented except for the \u0026ldquo;Bean\u0026rdquo; methods. It\u0026rsquo;s also missing the get() method (you\u0026rsquo;ll see why shortly).\nBut it also seems fairly useless. What\u0026rsquo;s the point of instantiating something as an Obseravble if the value can never change? The answer is in ReadOnlyObjectWrapper\u0026hellip;\nThe ReadOnlyObjectWrapper class extends from SimpleObjectProperty and can be used exactly like SimpleObjectProperty any time you want. However, it has one extra public method: getReadOnlyProperty() that returns a ReadOnlyObjectProperty that is synchronized with the ReadOnlyObjectWrapper that created it.\nWhat\u0026rsquo;s the point of this?\nIt all has to do with preventing client code from casting your \u0026ldquo;read only\u0026rdquo; Observable objects back to something implementing WritableValue. Consider that the following code will run and print \u0026ldquo;Is SimpleObjectProperty: true\u0026rdquo;:\nval x: ObservableValue\u0026lt;String\u0026gt; = SimpleObjectProperty(\u0026#34;\u0026#34;) println(\u0026#34;Is SimpleObjectProperty: ${x is SimpleObjectProperty}\u0026#34;) (x as ObjectProperty).set(\u0026#34;abc\u0026#34;) The variable x was instantiated as SimpleObjectProperty\u0026lt;String\u0026gt; but declared as a reference to an ObsevableValue\u0026lt;String\u0026gt;. The intention being that it should only be observable at this point. However, it\u0026rsquo;s still a SimpleObjectProperty and this cannot be hidden from any client code. There\u0026rsquo;s nothing stopping anyone from taking your ObservableValue\u0026lt;String\u0026gt; and casting it to SimpleObjectProperty or ObjectProperty and then calling its set() or bind() methods.\nHow do you stop this?\nReadOnlyObjectWrapper to the rescue!\nIf you look at the source code for ReadOnlyObjectWrapper you\u0026rsquo;ll find this:\npublic class ReadOnlyObjectWrapper\u0026lt;T\u0026gt; extends SimpleObjectProperty\u0026lt;T\u0026gt; { private ReadOnlyObjectWrapper\u0026lt;T\u0026gt;.ReadOnlyPropertyImpl readOnlyProperty; public ReadOnlyObjectProperty\u0026lt;T\u0026gt; getReadOnlyProperty() { if (this.readOnlyProperty == null) { this.readOnlyProperty = new ReadOnlyPropertyImpl(); } return this.readOnlyProperty; } protected void fireValueChangedEvent() { super.fireValueChangedEvent(); if (this.readOnlyProperty != null) { this.readOnlyProperty.fireValueChangedEvent(); } } private class ReadOnlyPropertyImpl extends ReadOnlyObjectPropertyBase\u0026lt;T\u0026gt; { private ReadOnlyPropertyImpl() { } public T get() { return ReadOnlyObjectWrapper.this.get(); } } } I\u0026rsquo;ve taken the \u0026ldquo;Bean\u0026rdquo; stuff out because it gets in the way.\nThere\u0026rsquo;s the ReadOnlyPropertyBase! And look, it delegates its get() method to the enclosing ReadOnlyObjectWrapper - and the value there can change.\nYou can see from this that getReadOnlyProperty() returns a completely separate Property object that extends ReadOnlyObjectProperty and has it\u0026rsquo;s value connected back to what is essentially a SimpleObjectProperty. And, as you can see from the charts, it does not implement WritableValue so it cannot be cast to any class that supports set().\nThe following code will print \u0026ldquo;Is SimpleObjectProperty: false\u0026rdquo; and then fail with an exception when doing the cast:\nval x: ObservableValue\u0026lt;String\u0026gt; = ReadOnlyObjectWrapper(\u0026#34;\u0026#34;).readOnlyProperty println(\u0026#34;Is SimpleObjectProperty: ${x is SimpleObjectProperty}\u0026#34;) (x as ObjectProperty).set(\u0026#34;abc\u0026#34;) But if we take off the call to getReadOnlyProperty() it will run just like the first example that allows the casting to work.\nThis technique is used inside many of the JavaFX Nodes. Let\u0026rsquo;s look at the source code for Region:\npublic final ReadOnlyDoubleProperty widthProperty() { if (this.width == null) { this.width = new ReadOnlyDoubleWrapper(this._width) { protected void invalidated() { Region.this.widthChanged(this.get()); } public Object getBean() { return Region.this; } public String getName() { return \u0026#34;width\u0026#34;; } }; } return this.width.getReadOnlyProperty(); } Internally, the width Property is implemented as ReadOnlyDoubleWrapper, but Region.widthProperty() returns width.getReadOnlyProperty() which means that you can never cast it to something that supports set(). However, inside of Region the width Property works just like any other read/write Property.\nTip Use ReadOnlyObjectWrapper and its getReadOnlyProperty() method when you really want to make sure that your client code cannot update your Property by casting.\nObjectBinding # This is the sole class on the Binding side of the chart, and it\u0026rsquo;s abstract and it extends ObjectExpression. It implements all of the methods defined in Observable and ObservableValue and inherits those in ObjectExpression.\nIn addition, there are a few protected methods designed to make it easier to create custom classes based on ObjectBinding. We get bind() and unbind() to start with. We also get allowValidation(), onInvalidating() and isObserved(). You are pretty much required to use bind() in any custom class that you extend from ObjectBinding or else your binding is not going to be much use.\nThere is one abstract method, and that\u0026rsquo;s computeValue() which is also protected.\nThe intention is clear about the standard use case for custom classes extended from ObjectBinding. It\u0026rsquo;s expected that you\u0026rsquo;ll either pass the Observables to be bound in the constructor (or use values available in the scope in which your class is defined) and bind them in the constructor of your class. You\u0026rsquo;ll also define a computeValue() method that will use those bound values to determine the return value of any calls to get() or getValue().\nNote Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nTip Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nImportant Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nWarning Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nCaution Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nAbstract Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nInfo Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nTodo Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nSuccess Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nQuestion Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nFailure Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nDanger Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nBug Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nExample Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nQuote Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\u0026quot;\nObjectBinding has internal storage for the last computed value, and will just return that when a call to get() is made while the Binding is valid.\nSince ObjectBinding extends from ObjectExpression you can use the Fluent API to modify the results or combine it with other ObjectExpressions.\nWe can look to the Bindings utility class for minimal implementation of ObjectBinding:\npublic static \u0026lt;T\u0026gt; ObjectBinding\u0026lt;T\u0026gt; createObjectBinding(final Callable\u0026lt;T\u0026gt; var0, final Observable... var1) { return new ObjectBinding\u0026lt;T\u0026gt;() { { this.bind(var1); } protected T computeValue() { try { return var0.call(); } catch (Exception var2) { Logging.getLogger().warning(\u0026#34;Exception while evaluating binding\u0026#34;, var2); return null; } } public void dispose() { super.unbind(var1); } public ObservableList\u0026lt;?\u0026gt; getDependencies() { return (ObservableList)(var1 != null \u0026amp;\u0026amp; var1.length != 0 ? (var1.length == 1 ? FXCollections.singletonObservableList(var1[0]) : new ImmutableObservableList(var1)) : FXCollections.emptyObservableList()); } }; } Most people don\u0026rsquo;t bother with the logging, the dispose() method or getDependencies() when they create their own custom Binding, but the methods in Bindings are intended for a wide variety of applications.\nReadOnlyJavaBeanObjectProperty # This is in the chart just for the sake of completeness. You aren\u0026rsquo;t going to use it. I\u0026rsquo;ve never seen it used. Just ignore it unless you really, really need to use it - in which case you can research it for yourself.\nWhere the Methods are Defined # In reality, the Observable types don\u0026rsquo;t define that many methods, and they can probably be best understand through a table:\nType Methods Description Observable addListener(InvalidationListener)\nremoveListener(InvalidationListener)\nsubscribe(Runnable) Methods for InvalidationListeners ObservableValue getValue()\naddListener(ChangeListener)\nsubscribe(Consumer)\nsubscribe(BiConsumer)\nmap() \u0026amp; flatMap()\nwhen() Methods for value based Listeners ObservableObjectValue get() WritableValue setValue() The key method for updating and binding WritableObjectValue set() Property bind()\nbindBidirectional()\nunbind()\nunbindBidirectional()\nisBound() All the binding methods ReadOnlyProperty getBean()\ngetName() The two Java Bean methods ObjectExpression asString()\nisEqualTo() \u0026amp; isNotEqualTo()\nisNull() \u0026amp; isNotNull() The Fluent API for object Properties Binding getDependencies()\ninvalidate()\nisValid() The core functionality for a Binding All the other entries in the chart simply implement these methods, but don\u0026rsquo;t introduce any new public methods. So they do not appear in this table.\nUsing this Information # I think that seeing all of this laid out in a charts and a table clarifies something that seems at first to be just mysterious and then later frustrating. But what are the practical implications of knowing this?\nYou Can\u0026rsquo;t Instantiate ObservableValue # Despite all the complexity of the hierarchial chart, any time that you encounter an ObservableValue\u0026lt;T\u0026gt; you know that it was instantiated in one of three ways:\nAs an extension of ObjectPropertyBase\u0026lt;T\u0026gt;, probably SimpleObjectProperty\u0026lt;T\u0026gt; As an extension of ObjectBinding\u0026lt;T\u0026gt;\nThis is true even if it was the result of using the Fluent API or the Bindings class library. As an extension of ReadOnlyPropertyBase\u0026lt;T\u0026gt; from the getReadOnlyProperty() function of a ReadOnlyObjectWrapper instance. This is true of any type on this chart that you receive.\nThe point of this is that how you choose to pass Observables around in your application has more to do with what you want to expose about them than anything else.\nGenerally speaking, you want to pass a type as high up the chart as you can while still providing the functionality that you need. This is going to become important in three places:\nHow you type variables How you return values from methods How you type method parameters The first two pertain to how you expose information about your Properties to other code. Never type a variable as SimpleObjectProperty\u0026lt;T\u0026gt;, use ObjectProperty\u0026lt;T\u0026gt; if you want to use the Fluent API, otherwise just use Property\u0026lt;T\u0026gt;. And only use these two types if you want to be able to call set() or bind().\nThe last item in the list refers to limiting how much you know about Properties passed to you. If all you are going to do with a Property make it the dependency of a Binding, then type it as ObservableValue\u0026lt;T\u0026gt;. If you are going to use it in a Fluent API binding, then type it as ReadOnlyObjectProperty, or ObjectExpression.\nWhat you are trying to do with all of this is to maximize versatility while minimizing risk. Don\u0026rsquo;t ask for something writable if you\u0026rsquo;re not going to call set() or bind() on it. Don\u0026rsquo;t pass something writable if you don\u0026rsquo;t want your client code to update it, pass a ReadOnlyProperty or an ObservableValue in those cases.\nOnly Properties Can be Bound # Yes, Binding\u0026lt;T\u0026gt; is bound internally, but since it only implements Observable and ObservableValue\u0026lt;T\u0026gt; there is no external bind() method that you can call.\nThis means, that if you plan to bind an Observable to anything, you have to have it exposed to you as a Property\u0026lt;T\u0026gt; or an implementing class. The same goes if you want to manually change the value through setValue(). Although you could technically pass WritableValue\u0026lt;T\u0026gt; in that case, although you don\u0026rsquo;t see that often. Perhaps if you wanted someone to be able to set the value but not bind it, you could pass them WritableValue\u0026lt;T\u0026gt;.\nConclusion # I hope that this article clears up most of the confusion that you might have had about what all these Observable types are all about and where they come from. In the next article in this series, we are going to look at the typed Observable classes and interfaces.\n","date":"3 August 2024","externalUrl":null,"permalink":"/javafx/elements/observable-classes-generics/","section":"JavaFX","summary":"The place to start looking at the Observable types is with the generic classes and interfaces. These are the observable wrappers that you can use around any other class of object.\nMore importantly, the hierarchy here is exactly the same as the hierarchy that is used for all of the typed observables. If you can understand how generic observable classes and interfaces relate to each other, without worrying about what kind of data is inside them, and special classes added to handle those types, then you’ll automatically be able to understand how they work when they are typed.\n","title":"Guide To the Observable Classes - Part I","type":"javafx"},{"content":" Introduction # Getting your Nodes on the screen in the places that you want them can be challenging at times. JavaFX has a fairly complete set of layout classes that will organize the contents in some particular fashion. Understanding the options available to you when you are designing a screen can make it much easier to get your screen to behave the way that you want it to.\nThis article isn\u0026rsquo;t going to go into deep detail about how to use any of these classes. It\u0026rsquo;s intended to give you a familiarity with how they work, and to outline some of the key parameters that you can use to change their behaviour.\nYou\u0026rsquo;ll find that there are links to the JavaDocs (version 16) of all of these classes. So if you want to know more about them, it\u0026rsquo;s just a click away.\nThe Layout Classes # There are a variety of layout classes, and they differ mostly in the way that they arrange the items in them in relationship to each other.\nHBox and VBox # These two classes are the basic building blocks of just about any layout. They work very much the same way except for their orientation. HBox places the items in it horizontally, with each new element added to the right of the ones already in the HBox. VBox places the items in it vertically, with each new element added below the ones already in the VBox.\nThat\u0026rsquo;s really all there is to it. Both of these classes will respect the boundaries of the elements inside them (meaning that they won\u0026rsquo;t overlap) unless you force them to do otherwise.\nThere are a number of parameters that you can alter that will impact the manner in which they work:\nPadding Padding is the space inside the container that surrounds the contents. Padding is set using a class called Insets. Spacing Spacing is the space that occurs between each of the elements in the container. Alignment Alignment is how the elements will be place with relation to the boundaries of the container. Both HBox and VBox have TOP_CENTER as the default alignment Margins Margins are spacing around the outside of an element in a container. Margins are imposed by the container on the contained elements. The setMargin() method is static in HBox and VBox FillHeight and FillWidth Properties These two properties control how resizable children will grow to fill the height or width of the container. Note that HBox only has FillHeight and VBox only has FillWidth. StackPane # StackPane is the third layout class that orients the contents based on how they are added to the container. In StackPane all of the elements are placed on top of each other (like they are coming out of the screen towards you) as they are added.\nStackPane doesn\u0026rsquo;t support Spacing since the items are intended to overlap. Additionally StackPane tries to resize its children to fill it completely, and the default alignment is CENTER. Otherwise it supports Margins, Alignment and Padding.\nStackPane can be particularly useful when you want to have the content of some section of your screen contain different Nodes dependant on the state of something in your application. Just put them all in a StackPane and only have the Visible property of one of them true at a time.\nBorderPane # BorderPane is perhaps the most useful of the layouts when designing a complete screen. It\u0026rsquo;s best understood via the diagram from the official JavaDocs:\nYou put things into a BorderPane by calling its methods, setCenter(), setTop() and so on. Note that you can only put one item into each area of a BorderPane, and if you want more than one thing in an area, you\u0026rsquo;ll have to put them into some other layout class.\nBorderPane is especially useful because it follows the same structure that many application screens take. There\u0026rsquo;s some kind of a header section at the top, then the main form or table in the middle, and then a status, message or button area at the bottom of the screen. These all map nicely to the structure of BorderPane.\nBecause each area of BorderPane can only hold one item, it doesn\u0026rsquo;t make any sense to have an overall alignment parameter for the BorderPane. Each of the areas has its own default alignment, and you can change the alignment of any specific contained element using a static method. Margins and Padding are supported by BorderPane.\nGridPane # GridPane is the most complicated of all of the layout classes in it\u0026rsquo;s use and structure. It allows you to carve the pane up into rows and columns and to place Nodes it particular columns and rows. There are constructs to allow you to control the nature of each row and each column, and to control the spacing between rows and columns. Additionally, there are ways to make a Node span several rows or columns.\nGridPane can become complicated to use because, while it has the concepts of row and columns this is only in regards to layout. There really is no construct to hold a row or column of Nodes. This means that, while you can set some parameters around a row, you cannot write a method to create a row of content Nodes and just append it to your GridPane. Each Node needs to be specifically put into a particular row/column position, and it\u0026rsquo;s not possible to have a true builder method for a row that doesn\u0026rsquo;t involve passing a reference to the GridPane as a parameter.\nAlso, if you find yourself putting a GridPane inside a ScrollPane, you\u0026rsquo;re likely to be getting into a situation where a TableView or ListView would be a better approach.\nOn the other hand, any Node that you put into a GridPane can itself be a layout container, which means that you can have fairly complicated constructs inside your GridPane.\nGridPane really shines when you do have Nodes that need to be arranged into a row and column arrangement where columns of Nodes need to be aligned with each other horizontally. If this isn\u0026rsquo;t a requirement, you might be better off by populating a VBox with a series of HBoxes. This can be much less complicated and easier to construct via builder methods.\nThe other concept you\u0026rsquo;ll need to understand about GridPane is the concept of RowConstraints and ColumnConstraints. These are classes that can be added to your GridPane to control the width/height, alignment and the grow priority of items in the rows and columns.\nFlowPane # FlowPane is layout container where the children are laid out in column or rows, with the items wrapping either horizontally or vertically. For instance, with a horizontal FlowPane, each child Node is added to the right of the previous one, until the wrapping width of the FlowPane has been reached, at which point it will start a new row, below the previous one.\nIf the width or the height (for a vertical FlowPane) of the FlowPane is changed, the Nodes move to fill it:\nYou can see how in this horizontal FlowPane there is clearly a concept of rows with a given height, but there is no concept of columns, as each row will simple have as many Nodes in it as will fit with a standard gap between them.\nTilePane # TilePane is very similar to FlowPane but it maintains a sense of both rows and columns regardless of its orientation:\nand it reorganizes itself as the width changes:\nBut you can see that it always keeps the Nodes organized in both rows and columns.\nAnchorPane # AnchorPane is absolutely the most abused layout container in JavaFX. Probably because it\u0026rsquo;s the first alphabetically, it becomes the \u0026ldquo;default\u0026rdquo; layout container programmers use in SceneBuilder. It\u0026rsquo;s almost a certainty, if you see code that uses AnchorPane instead of something more appropriate, it\u0026rsquo;s because someone used SceneBuilder, or they learned layouts by using SceneBuilder.\nWhat AnchorPane is intended to be used for is when you have several contained Nodes that need to be \u0026ldquo;anchored\u0026rdquo; to one or more sides of the Pane. This is generally not useful when you only have one item that needs to be anchored, as setting the alignment on something like an HBox will do the same thing with less code.\nThe Labeled Classes # The classes that inherit Labeled are all technically layout classes also. This includes Cell, Label and Button. These classes have two elements, a graphic and a text value. There are methods to control how these two elements are positioned inside the Node relative to each other, and then other methods that control the presentation of the text value.\nIf you have some layout that boils down to a Node plus a Label, you might be best off putting all of it together in a single Label node. For instance, a TextField with a prompt Label in front of it could be combined into a single Label instead of using an HBox to contain it all.\nThe Wrapper Classes # The next three classes aren\u0026rsquo;t really layout classes, but containers that you can put layout classes into to control their presentation.\nTabPane \u0026amp; Tab # A TabPane is a container that holds Tabs in a presentation that looks like a file cabinet with file folders with tabs that stick up. When you click on a Tab it brings it and its contents to the front of the TabPane, and you can only see the content of one Tab at a time.\nTabs have two regions, the tab itself - which operates a bit like Labeled, and the Content. Like BorderPane you can only put one item into the content of a Tab, which means that you\u0026rsquo;re probably going to be putting some other kind of layout container in there.\nTitledPane # TitledPane works very much the same way that \u0026ldquo;twisty\u0026rsquo;s\u0026rdquo; on web pages work. There is a title section with an arrow, and clicking on this will rotate the arrow and open and close the contents of the panel. Clicking on it again will do the reverse:\nAs with Tabs, you can only put one Node into the contents of a TitledPane, so you\u0026rsquo;ll need to use some other layout class to organize multiple Nodes in a TitledPane.\nScrollPane # ScrollPane is usually used as a wrapper around another layout class to allow it to fit into a area in the screen that would otherwise be too small for it. If the enclosed region is larger than the viewport of the ScrollPane then scroll bars will appear.\nThis image shows a TilePane inside a ScrollPane. It also illustrates the very important point that putting a container inside a ScrollPane may often fundamentally change the way that the enclosed container functions. A ScrollPane functionally has an infinite height and width, and won\u0026rsquo;t cause any resizable elements inside it to automatically expand. In the image above, you can see that the width of the ScrollPane has been increased but the TilePane inside it has not grown wider.\nHere\u0026rsquo;s the same TilePane, but this time the FitToWidth property of the ScrollPane has been set to true. This causes the TilePane to expand to fit the width of the ScrollPane's viewport:\nThe Non-Layout Containers # This next set of container classes don\u0026rsquo;t do any layout for you at all and are, therefore, of limited use.\nPane # Pane is the parent class to most of the layout classes. It extends from Region and only adds the public getChildren() method to Region. Anything that you put into a Pane will be stacked in the top left corner of the Pane. Pane won\u0026rsquo;t auto-resize to accommodate the things you put in it or do anything else particularly useful.\nIt\u0026rsquo;s parent, Region, has all of the methods that control the size of the layouts as well as a number of layout control methods. However the getChildren() in Region is inherited from Parent and is Protected, so you cannot directly put anything into either Region or Parent.\nTo position items in Pane you\u0026rsquo;re going to have to use setLayoutX/Y() or setTranslateX/Y() unless you\u0026rsquo;re happy with them all stacked on top of each other in the top left corner. This can be useful, however, if there\u0026rsquo;s something about the topology of your layout that doesn\u0026rsquo;t work well with the standard layout classes. For instance, the hexmap layouts that I\u0026rsquo;ve talked about in other articles are best done with Pane and manual placement of the hexes.\nGroup # Group is very much like Pane except that it gains its size purely from its contents. So it can\u0026rsquo;t be resized externally by an enclosing container class.\nGroup is supposed to be extremely light-weight and doesn\u0026rsquo;t do CSS styling. So in some extreme cases it might have some value if you were having performance issues. It\u0026rsquo;s also useful as a holder for Canvas so that you can put nothing more than a Canvas into a Scene. Otherwise, just don\u0026rsquo;t use Group.\nCanvas # Canvas isn\u0026rsquo;t really a container class, and it doesn\u0026rsquo;t extend from Parent, but from Node. It\u0026rsquo;s really a wrapper around a GraphicsContext that you can use to draw on.\nSince Canvas doesn\u0026rsquo;t extend from Parent, you can\u0026rsquo;t add it directly to a Scene. If all you want in your Scene is a Canvas, then the best approach is to stuff it into a Group that you use as the root of the Scene.\nTips and Techniques # Getting a layout to work the way that you want or expect can, at times, be a bit frustrating. Small changes to a design cause a ripple effect through your layout and stuff moves around in unexpected ways. Here are some tips for creating you layouts with the least amount of frustration\u0026hellip;\nUnderstand How Items Grow Inside Containers # Many of the Node classes are resizable, but for many of those there are practical constraints which means that they are essentially fixed sizes. For instance, a CheckBox isn\u0026rsquo;t going to ever need to be much bigger than the checkbox itself and the label.\nVery often, the Nodes that you\u0026rsquo;ll need to worry about resizing are the container classes. Most of the time, these way that these Nodes resize is going to be dependant on the class of the parent holding them. Sometimes, you can control how these Nodes grow based on their own methods, other times you need to use methods in the containing class.\nIn order to understand this\u0026hellip;\nUse Borders to Debug # The absolute best way to understand what\u0026rsquo;s using up space in your layouts is to use borders on your layout containers. Borders let you understand how things have grown to fill up containers and to understand why a particular Node isn\u0026rsquo;t appearing where you expect it to. Virtually all of the time, when you cannot understand why your layout is looking goofy, it\u0026rsquo;s because some container isn\u0026rsquo;t occupying the space that you think it should.\nThe easiest way to do this is to just create a few style class selectors in your CSS file, define a border of a particular colour in them, and then add them to the StyleClass list in a container Node. The CSS would look like this:\n.test-border1 { -fx-border-color: red; } .test-border2 { -fx-border-color: blue; } .test-border3 { -fx-border-color: green; } .test-border4 { -fx-border-color: black; } The examples in this article were created using these. I also added background colours to make things a bit clearer.\nDon\u0026rsquo;t Put Stuff Inside of Stuff Unless You Have a Reason # This is something that beginners do all the time. I\u0026rsquo;ve seen countless layouts where GridPane is inside an AnchorPane which is inside an HBox which is inside another AnchorPane and so on. There\u0026rsquo;s no point to it, just having the GridPane by itself would do the trick.\nGenerally speaking, having a layout container with only one child Node in it is probably a JavaFX code smell. This is especially true if that container doesn\u0026rsquo;t have any configuration done to it at all (like setting Padding or Alignment).\nAs a rule, putting your layout inside another layout container won\u0026rsquo;t fix your problem but it will make your layout more complex and harder to debug.\nDon\u0026rsquo;t Worry About \u0026ldquo;Load\u0026rdquo; of Layout Types # Unless you\u0026rsquo;re doing something really graphically complex that might tax your system, just don\u0026rsquo;t worry about whether one layout class is \u0026ldquo;heavier\u0026rdquo; than another. It just won\u0026rsquo;t matter in real life.\nLooking at the source code for JavaFX, you\u0026rsquo;ll find that many of the more complicated controls, like ComboBox or Slider are actually composed of a number of different Nodes. Generally, if one of those internal Nodes needs to be placed into a layout container (or sometimes just is the layout container), JavaFX uses StackPane. StackPane doesn\u0026rsquo;t appear to be any lighter than any other container. If this approach is good enough for the inner workings of the JavaFX classes, it\u0026rsquo;s probably good enough for you.\nBuild Size Constraints from the Inside Out # It\u0026rsquo;s usually true that you\u0026rsquo;ll have better results if you concentrate on limiting the size of your Nodes at the bottom of your hierarchy, not at the top. Leave the layout containers free to resize to the contents as much as possible.\nAt the bottom is where you\u0026rsquo;re likeliest to find Nodes that can be absolutely sized. For instance, you might be able to say that a particular TextField should be restricted to only 60px. It would probably look better than having it 5 times as big as the longest String that\u0026rsquo;s allowed in it.\nDon\u0026rsquo;t Try to Scale to Changing Fonts # One of the most difficult things to do in JavaFX is to try to set limits on a Node based on how much space its text-based contents might take up. Especially when you try to make the calculation independent of the font used for the text.\nThe best approach is probably to just pick a reasonably font and size that works well in your screen and set your height and width values for your controls based on that font. If you need bigger fonts for special situations, use setScaleX/Y() on the entire Node.\nStatic Methods # There are a number of static methods in the layout classes that can be used alter the layout of contained Nodes. Methods like setMargins(). These methods should be applied from outside the builders for the contained Nodes.\nLet\u0026rsquo;s say that you have a Label that you intend to put into an HBox and you want to have a little extra space in between this Label and the next thing to its right. Generally speaking, this is a good use for HBox.setMargin(). If you have a builder for this Label don\u0026rsquo;t include the call to HBox.setMargin() inside of it. This makes your builder assume that the Label is going to be in an HBox, and if you later change that, then you\u0026rsquo;ll have to change the builder too.\nAny code that arranges Nodes in container with regards to each other belongs in the code for the container.\nDifferent Approaches Can Work Fine # There\u0026rsquo;s often not just one way that you can design a layout. In many cases, there\u0026rsquo;s no objectively better way to construct a layout, it just boils down to what makes sense to the programmer.\nLet\u0026rsquo;s look at a simple example. We\u0026rsquo;ll start from the inside, with a Label/TextField combination in a VBox, so the prompt is above the TextField. We\u0026rsquo;ll create a builder for it, parameterize the prompt text and return the result as a very generic Region. This is Kotlin, but it should be pretty clear what\u0026rsquo;s going on:\nprivate fun inputThingy(promptText: String): Region = VBox(4.0).apply { padding = Insets(4.0) isFillWidth = true maxWidth = 230.0 children += Label(\u0026#34;$promptText:\u0026#34;) children += TextField() } We\u0026rsquo;re controlling the width of the TextField by setting setFillWidth(true) and then setting a max width for the VBox. In real life, with a Model, we\u0026rsquo;d also parameterize the StringProperty from the model that we\u0026rsquo;d bind to the TextField. But this is just about layouts, so we don\u0026rsquo;t need to go that far for this example.\nNow we\u0026rsquo;ll look at how you can get a bunch of these inputThingy's onto the screen\u0026hellip;\nWe can lay them out in a set of HBoxes contained in a VBox:\nreturn VBox(2.0).apply { children += listOf(HBox(2.0, inputThingy(\u0026#34;First Name\u0026#34;), inputThingy(\u0026#34;Last Name\u0026#34;)), HBox(2.0, inputThingy(\u0026#34;Street\u0026#34;), inputThingy(\u0026#34;City\u0026#34;)), HBox(2.0, inputThingy(\u0026#34;Province/State\u0026#34;), inputThingy(\u0026#34;Country\u0026#34;)), HBox(2.0, inputThingy(\u0026#34;Phone Number\u0026#34;), inputThingy(\u0026#34;Email Address\u0026#34;))) } or a GridPane:\nreturn GridPane().apply { add(inputThingy(\u0026#34;First Name\u0026#34;), 0, 0) add(inputThingy(\u0026#34;Last Name\u0026#34;), 1, 0) add(inputThingy(\u0026#34;Street\u0026#34;), 0, 1) add(inputThingy(\u0026#34;City\u0026#34;), 1, 1) add(inputThingy(\u0026#34;Province/State\u0026#34;), 0, 2) add(inputThingy(\u0026#34;Country\u0026#34;), 1, 2) add(inputThingy(\u0026#34;Phone Number\u0026#34;), 0, 3) add(inputThingy(\u0026#34;Email Address\u0026#34;), 1, 3) vgap = 2.0 hgap = 2.0 } or in a TilePane:\nreturn TilePane().apply { children += listOf(inputThingy(\u0026#34;First Name\u0026#34;), inputThingy(\u0026#34;Last Name\u0026#34;), inputThingy(\u0026#34;Street\u0026#34;), inputThingy(\u0026#34;City\u0026#34;), inputThingy(\u0026#34;Province/State\u0026#34;), inputThingy(\u0026#34;Country\u0026#34;), inputThingy(\u0026#34;Phone Number\u0026#34;), inputThingy(\u0026#34;Email Address\u0026#34;)) prefColumns = 2 hgap = 2.0 vgap = 2.0 } And they all look the same:\nThe only real difference is if you stretch out the window, the TilePane adjusts to the new space:\nMaybe the TilePane is better because it\u0026rsquo;s more responsive, but who knows? Maybe there is a constraint to have just two columns of inputs. This example is a little bit contrived, because all of the Nodes in the layout are designed to be the same width, which is a bit simpler. But still, this kind of situation comes up all the time.\nProbably more important than the layout class used, is the fact the that Label/TextField pair are set up in their own layout, and then that composite Node is put into the main layout. Many, many programmers would try to put the Labels and the TextFields separately into the main layout, and then it gets overly complicated very quickly.\n","date":"6 January 2023","externalUrl":null,"permalink":"/javafx/elements/layout_classes/","section":"JavaFX","summary":"In order to put anything onto the GUI you’ll need to use one of the layout Nodes to control how the screen is organized. There are a variety of different layout classes, each of which handles the organization of its content differently.\nThis article gives you a summary of how each different layout class works, and explains how each one differs from the others.\n","title":"Layout Classes 101","type":"javafx"},{"content":" Introduction # Image and ImageView are the two basic classes that you\u0026rsquo;ll need to master to be able put images into your layouts. ImageView is the layout class, while Image is the \u0026ldquo;data\u0026rdquo; class, in much the same way that String is the data class for Text.\nImage # Image is the \u0026ldquo;data\u0026rdquo; class for images. You can load BMP, GIF, JPEG or PNG formatted images into an Image object.\nHow Image Fits in the JavaFX Hierarchy # Image inherits directly from Object. It doesn\u0026rsquo;t have any JavaFX parents. However, it is still a JavaFX object and will require that the JavaFX engine is running. It\u0026rsquo;s also best to handle Images on the FXAT.\nHow To Use Image # Every constructor for Image requires that image data, or the location of image data is specified. There\u0026rsquo;s no way to create an empty Image or to load image data after an Image has been instantiated. In that respect, you may consider Image to be immutable.\nThere are two important points to remember about Image:\nA single Image can be placed into as many ImageView objects as you like. This is very different from ImageView which can only be placed once into a layout.\nLoading images is generally one of the slowest processes in rendering a screen.\nWhat this means is that you might need to implement a scheme to avoid having the cost of of Image loading affect your GUI performance. The simplest approach is to avoid loading the same Image resource over and over if it appears multiple times on a screen. It might be possible to cache Images if they are to be dynamically displayed on the layout over time. It also might be possible to treat a set of images as a Sprite, so the entire set is loaded into a single Image which is then reused.\nCreating an Image # There are two basic ways to load an image: from an InputStream, or from a URL. Each method also allows you to scale the image if you want.\nBackground Loading # If the image is being loaded from a URL, you have the option of loading it on a background thread. This is an important feature that doesn\u0026rsquo;t seem to be used as much as it should be. Since Images really should be handled on the FXAT, loading an Image from a remote location could cause blocking on the FXAT - which is a really, really bad thing.\nThere are also properties that allow you to track the progress of the loading of an image on a background thread.\nScaling the Image # It\u0026rsquo;s possible to scale an image when it is loaded in order to reduce its footprint in memory and to potentially avoid scaling calculations in the layout if the size needs to be modified in the ImageView.\nError Handling # It\u0026rsquo;s possible that your Image may fail to load. In this case there are methods that can tell you if the load failed, and any exception that was thrown by the loading process.\nWhat Can You Do With an Image? # Not much, except put it into a screen object that holds Image. This includes BackgroundImage, ImageCursor, ImagePattern and BorderImage. We\u0026rsquo;re going to stick to talking about just ImageView here.\nImageView # ImageView is a Node type that you can add to a layout. It will go anywhere that you can put Node.\nHow ImageView Fits in the JavaFX Hierarchy # ImageView inherits methods and properties from just one JavaFX class:\nNode Everything in JavaFX inherits from Node, which gives most of its methods for styling, event handling and responding to mouse actions as well as location and transformation. Instantiating ImageView # There are three ways to instantiate ImageView:\nCreate an empty ImageView.\nSpecify an image resource URL. In this case JavaFX will automatically create an Image for you and load it from the URL, but it won\u0026rsquo;t do background processing.\nPass the constructor an Image object.\nUnlike Image, ImageViews can have their contents changed, so you don\u0026rsquo;t need to consider them immutable in this respect.\nProperties of ImageView # FitHeight, FitWidth and PreserveRatio These properties control the scaling of the ImageView on the screen to fit within defined parameters. ViewPort This property defines a rectangle which allows only a portion of an image to be visible in the ImageView. Image This is the Image contained in the ImageView. Uses of ImageView # Of course, ImageView can be used on its own to put an image onto the layout somewhere. Something like this:\npublic class ImageViewDemo1 extends Application { public static void main(String[] args) { launch(); } @Override public void start(Stage stage) throws IOException { Scene scene = new Scene(createContent(), 820, 640); stage.setScene(scene); stage.show(); } private Region createContent() { Image loadingGif = new Image(Objects.requireNonNull(ImageViewDemo1.class.getResource(\u0026#34;loading.gif\u0026#34;)).toString()); Image brokenImage = new Image(Objects.requireNonNull(ImageViewDemo1.class.getResource(\u0026#34;broken.png\u0026#34;)).toString()); ImageView imageView = new ImageView(loadingGif); imageView.setImage(loadingGif); Image image = new Image(\u0026#34;https://www.pragmaticcoding.ca/assets/images/794.png\u0026#34;, 700, 0, true, true, true); image.progressProperty().addListener(observable -\u0026gt; { System.out.println(\u0026#34;Progress \u0026#34; + image.getProgress()); if (image.getProgress() == 1.0) { if (!image.isError()) { imageView.setImage(image); } else { imageView.setImage(brokenImage); } } }); VBox vBox = new VBox(imageView); vBox.setPadding(new Insets(30)); return vBox; } } This program demonstrates how to use the background loading of Image with a large file. First, we create two Images from local resources; one for \u0026ldquo;In Progress\u0026rdquo;, which is an animated GIF and the other is to show a broken link. Then we create the ImageView and load it up with the \u0026ldquo;In Progress\u0026rdquo; GIF, so we\u0026rsquo;ll see that on the screen immediately.\nThen we create the Image that we really want to show on the screen. But it\u0026rsquo;s coming from a remote site, and it\u0026rsquo;s pretty big (about 5MB), so we\u0026rsquo;ll load it in the background. Since it\u0026rsquo;s a remote file, we really do need to worry about a problem with the file, so we are going to put some handling in there to detect a problem and put up the \u0026ldquo;Broken Link\u0026rdquo; image.\nIn order to know when the Image has finished loading, we need to place a listener on the Progress property of the Image. The Progress is a Double that runs from 0 to 1.0. Unfortunately, a call to image.isBackground() always returns true, even after the image has finished loading, so we cannot use it test for load completion.\nIn the listener, we check for the progress to reach \u0026ldquo;1.0\u0026rdquo;, then check to see if it\u0026rsquo;s encountered an error and then populate the ImageView with either the freshly loaded Image, or the \u0026ldquo;Broken\u0026rdquo; Image.\nThis is typical JavaFX. All the tools are provided for you to build a slick, professional UI but they\u0026rsquo;re all relatively low level. This can result in a lot of \u0026ldquo;boilerplate\u0026rdquo; code. If you find yourself repeating this pattern over and over, it might be worth creating a custom class that handles all of this in one place. Then you can reuse it over and over.\nWhat About the \u0026ldquo;Placeholder\u0026rdquo; Image # If you really search through the JavaDocs for Image with eagle-eyes, you\u0026rsquo;ll find the following intriguing piece of information in the sample code in the description:\n// load an image in background, displaying a placeholder while it\u0026#39;s loading // (assuming there\u0026#39;s an ImageView node somewhere displaying this image) // The image is located in default package of the classpath Image image1 = new Image(\u0026#34;/flower.png\u0026#34;, true); And then nothing more about \u0026ldquo;a placeholder\u0026rdquo; anywhere else in the JavaDocs.\nI\u0026rsquo;ve looked through the source code for JavaFX and I couldn\u0026rsquo;t find any reference to a placeholder anywhere. Of course the code for Image is super complicated, since it has all kinds of different formats and threading and graphical stuff going on, so I could have missed it. My guess is that this an \u0026ldquo;unimplemented feature\u0026rdquo; that never got off the ground. It\u0026rsquo;s a shame, because it would have been so much easier to just call a setPlaceholder() method than to go through the listener route.\nIn the meantime, if you just put an Image that\u0026rsquo;s still loading into an ImageView, you\u0026rsquo;ll just get a blank.\nAs Part of Labeled # Probably the most common use of ImageView is as the Graphic in any of the classes that inherit from Labeled. This includes, Label, Button, and the various Cell classes.\nIn this example, we\u0026rsquo;re going to put the ImageViews into Buttons:\npublic class ImageViewDemo2 extends Application { private final ImageView imageView = new ImageView(); public static void main(String[] args) { launch(); } @Override public void start(Stage stage) { Scene scene = new Scene(createContent(), 500, 320); stage.setScene(scene); stage.show(); } private Region createContent() { Image happyFace = new Image(Objects.requireNonNull(ImageViewDemo2.class.getResource(\u0026#34;smileyface.png\u0026#34;)).toString()); Image angryFace = new Image(Objects.requireNonNull(ImageViewDemo2.class.getResource(\u0026#34;sadface.png\u0026#34;)).toString()); HBox hBox = new HBox(createButton(\u0026#34;Loved it!\u0026#34;, happyFace), createButton(\u0026#34;Liked it!\u0026#34;, happyFace), createButton(\u0026#34;Hated it!\u0026#34;, angryFace)); hBox.setPadding(new Insets(30)); imageView.setPreserveRatio(true); imageView.setFitHeight(160); return new VBox(20, hBox, imageView); } private Button createButton(String text, Image image) { Button results = new Button(text, createButonImageView(image)); results.setOnAction(evt -\u0026gt; imageView.setImage(image)); return results; } private Node createButtonImageView(Image image) { ImageView results = new ImageView(image); results.setSmooth(true); results.setPreserveRatio(true); results.setFitHeight(64); return results; } } And it looks like this:\nA few things to note here:\nThe happyFace Image is used twice in the Buttons, and again in the ImageView below the Buttons. This doesn\u0026rsquo;t cause any problems. The Images are all loaded in the foreground. There would be no harm in loading them in the background, and probably should be. However, with just two reasonably small images to load, there\u0026rsquo;s probably almost no impact on the UI performance. You can freely swap out the Image in an ImageView at any time while the application is running and it works just fine. This suggests the best way to handle layouts with variable images in them. Images For the Examples # ","date":"11 March 2022","externalUrl":null,"permalink":"/javafx/elements/buttons/","section":"JavaFX","summary":"The basic component of image display in JavaFX is the ImageView. In this article, we’ll look at how to use ImageView to display images in your GUI and how to incorporate them your components.","title":"All About Image and ImageView","type":"javafx"},{"content":" What\u0026rsquo;s a PseudoClass? # Have you ever noticed how a Button changes a little when you hover your mouse over it? Or darkens to look like it\u0026rsquo;s pressed down when you hold down a mouse button in it? These presentation changes are usually accomplished through the use of PseudoClasses.\nPseudoClasses are specialized CSS selectors which represent on/off states of some value of a Node. One or more aspects of the styling of a Node will be changed when the PseudoClass is turned on, and then returned back to their original state when the PseudoClass is turned off again.\nThey\u0026rsquo;re useful because the styling is all handled for you behind the scenes. The normal use case is to tie the PseudoClass state to a Boolean Property, and then bind that Property to a Property in the Model or some other part of the GUI. Sometimes the PseudoClass state is control by EventHandlers.\nBuilt-In PseudoClasses # There are a number of predefined PseudoClasses in JavaFX that you can use without any special programming. For instance, many controls have a disabled pseudo class which is automatically maintained for you whenever the control\u0026rsquo;s Disabled property changes.\nThe standard Modena CSS has definitions like this:\n.spinner:disabled { -fx-opacity: 0.4; } You can override these in your own stylesheets to make the control behave differently when it\u0026rsquo;s disabled.\nCreate Your Own PseudoClass # The built-in PseudoClasses are useful, but sometimes you need to create your own PseudoClass to handle state changes in ways unique to your application. For instance, you might want to change the colour of a Label if the data it displays indicates an error condition.\nThis article is about how to create a brand new PseudoClass and how to link it into your screen Node so that you can style it via your CSS.\nThe JavaDocs for Pseudoclass are Horrible # If you want to look, you can find them here.\nThe introduction at the top reads like this:\nPseudoClass represents one unique pseudo-class state. Introducing a pseudo-class into a JavaFX class only requires that the method Node.pseudoClassStateChanged(javafx.css.PseudoClass, boolean) be called when the pseudo-class state changes. Typically, the pseudoClassStateChanged method is called from the protected void invalidated() method of one of the property base classes in the javafx.beans.property package.\nNote that if a node has a default pseudo-class state, a horizontal orientation for example, pseudoClassStateChanged should be called from the constructor to set the initial state.\nThe following example would allow \u0026ldquo;xyzzy\u0026rdquo; to be used as a pseudo-class in a CSS selector.\nYikes! That doesn\u0026rsquo;t really explain anything unless you already understand it.\nAnd then they\u0026rsquo;ve included the following example code:\npublic boolean isMagic() { return magic.get(); } public BooleanProperty magicProperty() { return magic; } public BooleanProperty magic = new BooleanPropertyBase(false) { @Override protected void invalidated() { pseudoClassStateChanged(MAGIC_PSEUDO_CLASS, get()); } @Override public Object getBean() { return MyControl.this; } @Override public String getName() { return \u0026#34;magic\u0026#34;; } } private static final PseudoClass MAGIC_PSEUDO_CLASS = PseudoClass.getPseudoClass(\u0026#34;xyzzy\u0026#34;); Seriously, I don\u0026rsquo;t think this is a great way to explain how it works. They\u0026rsquo;ve even called it magicProperty, which might be an admission that they understood they weren\u0026rsquo;t explaining it very well when they wrote it.\nMyControl.this isn\u0026rsquo;t defined anywhere, but it appears that this snippet of code is supposed to be embedded into a class which is an extension of Node. The idea being that this class now exposes a BooleanProperty through magicProperty() that the layout can then bind to some other property to control the PseudoClass.\nAlso, there\u0026rsquo;s a typo (which I\u0026rsquo;ve fixed here) in the call to pseudoClassStateChanged(). That doesn\u0026rsquo;t help either.\nHow Does it Really Work? # In reality, PseudoClass is a lot easier to understand than you might think. There\u0026rsquo;s no magic, and it\u0026rsquo;s really easy to use PseudoClass.\nCreating the PseudoClass # In the example code from the JavaDocs, the first part that\u0026rsquo;s really important is the single line:\nprivate static final PseudoClass MAGIC_PSEUDO_CLASS = PseudoClass.getPseudoClass(\u0026#34;xyzzy\u0026#34;); What does this do?\nIt does two things, one you can see, and one that you can\u0026rsquo;t.\nThe first is to create an instance of PseudoClass as a static field. Does it need to be static? Technically, no, but as we\u0026rsquo;ll see in a little bit, it\u0026rsquo;s probably a good idea to implement it this way.\nThe second thing it does, which you cannot see, is that it updates a static Map deep inside PseudoClass, adding whatever name you used to initialize your PseudoClass - in this case, \u0026ldquo;xyzzy\u0026rdquo;. The Map makes sure that only one element with the same name exists in your application.\nIf you try to instantiate another PseudoClass anywhere in your application with the same name, it will just reuse the Map entry it already has for that name. That\u0026rsquo;s not really a big deal, but you should avoid using names that are already defined in JavaFX, like \u0026ldquo;hover\u0026rdquo; or \u0026ldquo;disabled\u0026rdquo;, because there might already be code that\u0026rsquo;s going to be manipulating any PseudoClasses with that name.\nSetting the PseudoClass Value # The only other part of the example code you need to understand is this:\n@Override protected void invalidated() { pseudoClassStateChanged(MAGIC_PSEUDO_CLASS, get()); } Even this is too complicated. You could use something like this:\npseudoClassStateChanged(MAGIC_PSEUDO_CLASS, true); What does this do? pseudoClassStateChanged() is a method in Node. You pass it a PseudoClass, and a new value (true or false) for that PseudoClass in that Node. Under the hood, this method then adds or removes this PseudoClass to its list of active PseudoClasses. In reality, this list is really just a list of the names that were given to the PseudoClasses when they were instantiated.\nImportant Things to Know About PseudoClass # PseudoClass isn\u0026rsquo;t part of the Node The PseudoClass itself isn\u0026rsquo;t installed into the Node, formally declared as part of it, or prepped in the Node in any special way at all. You can instantiate a PseudoClass anywhere and pass it to any Node\u0026rsquo;s pseudoClassStateChanged() method, and it will work. Furthermore, you can call a Node's pseudoClassStateChanged() from any number of places using completely different instances of a PseudoClass with the same name, and it will work exactly the same as if you had only used one instance of the same PseudoClass repeatedly. Node.pseudoClassStateChanged() is a public method. This means that there\u0026rsquo;s no need to extend any Node just to implement a PseudoClass on it. You can manage the PseudoClasses on a Node from outside the Node. This is absolutely not clear from the JavaDoc example. The mechanism connecting PseudoClass to CSS isn\u0026rsquo;t our concern How the Node actually turns the list of active PseudoClasses into styling changes an your screen is something that we simply don\u0026rsquo;t need to know. As long as your CSS has the selectors defined, it will work. PseudoClass itself isn\u0026rsquo;t Reactive It\u0026rsquo;s important to note that pseudoClassStateChanged() itself has NOTHING to do with Properties or Bindings. It\u0026rsquo;s a plain vanilla Java method. PseudoClass instances aren\u0026rsquo;t functionally unique There is nothing particularly special or specific about any instance of PseudoClass other than its name and the way that names are managed. The following code is perfectly fine: PseudoClass xyzzy = PseudoClass.getPseudoClass(\u0026#34;xyzzy\u0026#34;); myButton.pseudoClassStateChanged(xyzzy, true); It\u0026rsquo;s probably a bit inefficient if it\u0026rsquo;s going to be called a lot, just because there\u0026rsquo;s a fair bit of code running inside of PseudoClass.getPseudoClass(). So it might be best to declare it as a static field inside of whatever class holds this code.\nConnecting PseudoClasses to Properties and Bindings # We\u0026rsquo;ve seen that PseudoClasses are just normal Java and are implemented through normal imperative programming techniques. But if you want to build a reactive JavaFX application, then you need to control the PseudoClasses through Properties and Bindings.\nHow do you do that?\nThe way to connect imperative code to Properties is through Invalidation Listeners. This is exactly what the example from the JavaDocs does, although it does it in the most complicated way possible.\nEssentially, what an Invalidation Listener does is to define a block of code that will run whenever the system decides that the value held in a property is no longer valid because it\u0026rsquo;s potentially been changed, or some other properties that it\u0026rsquo;s been bound to have changed. Calling get() on the Property will cause it to recalculate it\u0026rsquo;s value, but you can also run whatever other code you want in the Listener. This is where you can call Node.pseudoClassStateChanged().\nThe Property that you\u0026rsquo;re listening to can be anything, anywhere. It doesn\u0026rsquo;t need to be a field in the Node, and it doesn\u0026rsquo;t need to be a BooleanProperty either. The Listener just needs to be defined in some scope that has access to both the Node and the Property. The Listener would then contain whatever logic you need to turn a change in your Property value into a boolean value to be sent to Node.pseudoClassStateChanged().\nA Simple Example # In this example, we\u0026rsquo;re going to look at the \u0026ldquo;normal\u0026rdquo; use case for PseudoClass, a BooleanProperty which is part of the Model and which is connected to a PseudoClass through an InvalidationListener:\npublic class PseudoClassDemo extends Application { private BooleanProperty displayInRed = new SimpleBooleanProperty(false); @Override public void start(Stage stage) throws IOException { Scene scene = new Scene(createContent(), 320, 240); scene.getStylesheets().add(PseudoClassDemo.class.getResource(\u0026#34;test.css\u0026#34;).toString()); stage.setScene(scene); stage.show(); } private Region createContent() { PseudoClass redDisplay = PseudoClass.getPseudoClass(\u0026#34;red\u0026#34;); Label label = new Label(\u0026#34;This is the label text\u0026#34;); displayInRed.addListener(inv -\u0026gt; { label.pseudoClassStateChanged(redDisplay, displayInRed.get()); }); Button button = new Button(\u0026#34;Click Me\u0026#34;); button.setOnAction(evt -\u0026gt; displayInRed.set(!displayInRed.get())); return new VBox(20, label, button); } public static void main(String[] args) { launch(); } } Here\u0026rsquo;s the CSS file for this:\n.label:red { -fx-text-fill: red; } When it runs, it looks like this:\nAfter the Button is clicked, it looks like this:\nI think this strips virtually all of the mystery out of PseudoClass creation. It is possible to cut the BooleanProperty completely out of this if you want and call label.pseudoClassStateChanged() directly from the Button action - although this is not a good idea in terms of creating a Reactive JavaFX program:\npublic class PseudoClassDemo2 extends Application { private boolean showInRed = false; @Override public void start(Stage stage) throws IOException { Scene scene = new Scene(createContent(), 320, 240); scene.getStylesheets().add(PseudoClassDemo2.class.getResource(\u0026#34;test.css\u0026#34;).toString()); stage.setScene(scene); stage.show(); } private Region createContent() { PseudoClass redDisplay = PseudoClass.getPseudoClass(\u0026#34;red\u0026#34;); Label label = new Label(\u0026#34;This is the label text\u0026#34;); Button button = new Button(\u0026#34;Click Me\u0026#34;); button.setOnAction(evt -\u0026gt; { showInRed = !showInRed; label.pseudoClassStateChanged(redDisplay, showInRed); }); return new VBox(20, label, button); } public static void main(String[] args) { launch(); } } Once again, this isn\u0026rsquo;t a good practice. It\u0026rsquo;s included here just to show how PseudoClass is just regular Java code.\nA More Complicated Example # At this point, we\u0026rsquo;re not really talking about PseudoClass itself any more, but about strategies around transforming non-boolean Properties into CSS pseudo-class changes.\nLet\u0026rsquo;s look at how to turn a StringProperty into pseudo-class states:\npublic class PseudoClassDemo3 extends Application { private final StringProperty alertLevel = new SimpleStringProperty(\u0026#34;Normal\u0026#34;); @Override public void start(Stage stage) throws IOException { Scene scene = new Scene(createContent(), 300, 100); scene.getStylesheets().add(PseudoClassDemo3.class.getResource(\u0026#34;test.css\u0026#34;).toString()); stage.setScene(scene); stage.show(); } private Region createContent() { PseudoClass redDisplay = PseudoClass.getPseudoClass(\u0026#34;red\u0026#34;); PseudoClass orangeDisplay = PseudoClass.getPseudoClass(\u0026#34;orange\u0026#34;); Label label = new Label(\u0026#34;This is the label text\u0026#34;); alertLevel.addListener(inv -\u0026gt; { label.pseudoClassStateChanged(redDisplay, false); label.pseudoClassStateChanged(orangeDisplay, false); switch (alertLevel.get()) { case \u0026#34;Warning\u0026#34; -\u0026gt; label.pseudoClassStateChanged(orangeDisplay, true); case \u0026#34;Emergency\u0026#34; -\u0026gt; label.pseudoClassStateChanged(redDisplay, true); } }); return new VBox(20, label, createButtons()); } private Node createButtons() { Button warningButton = new Button(\u0026#34;Warning\u0026#34;); warningButton.setOnAction(evt -\u0026gt; alertLevel.set(\u0026#34;Warning\u0026#34;)); Button emergencyButton = new Button(\u0026#34;Emergency\u0026#34;); emergencyButton.setOnAction(evt -\u0026gt; alertLevel.set(\u0026#34;Emergency\u0026#34;)); Button normalButton = new Button(\u0026#34;All Clear\u0026#34;); normalButton.setOnAction(evt -\u0026gt; alertLevel.set(\u0026#34;All Clear\u0026#34;)); return new HBox(10, normalButton, warningButton, emergencyButton); } public static void main(String[] args) { launch(); } } And the CSS file:\n.label:red { -fx-text-fill: red; } .label:orange { -fx-text-fill: darkorange; } Which looks like this when the \u0026ldquo;Warning\u0026rdquo; button is clicked:\nThe Buttons just update the StringProperty, putting a new value into it. The StringProperty has an InvalidationListener added to it, in a scope that has access to the Label so that the InvalidationListener can call its pseudoClassStateChanged() method.\nThe InvalidationListener just turns off all of the custom PseudoClasses in the Label, and then uses a switch statement to turn on a specific PseudoClass depending on the value in the StringProperty.\nConclusion # One good thing about the way that PseudoClass is introduced in the JavaDocs is that it reaffirms that the designers of JavaFX intended it to be used as a Reactive framework. You can tell this because they take care to explain it in the context of a BooleanProperty embedded into a Node.\nTo be perfectly fair, though, the second sentence in the JavaDocs introduction is a complete and concise summary of everything in this tutorial:\nIntroducing a pseudo-class into a JavaFX class only requires that the method Node.pseudoClassStateChanged(javafx.css.PseudoClass, boolean) be called when the pseudo-class state changes.\nHowever, everything else in the JavaDocs makes it very difficult to believe it could be that simple. Hopefully this tutorial has cleared that up.\n","date":"24 January 2022","externalUrl":null,"permalink":"/javafx/elements/pseudo_classes/","section":"JavaFX","summary":"Pseudo Classes are the best way to handle on/off state changes in a Node in JavaFX.  But it’s very badly explained in the JavaDocs and hard to understand.  This article should clear that up.","title":"Demystifying PseudoClasses in JavaFX","type":"javafx"},{"content":"JavaFX contains powerful tools to turn simple TextFields into specialized entry fields for any kind of data you can think of.\nIntroducing TextFormatter # If you are writing a JavaFX application that needs a user to type in information, then you\u0026rsquo;ll almost certainly need to use the TextField control. But what if you need to make sure that the user enters valid data? What if the data you need is not a string? How do you convert it to the type that you need?\nGenerally speaking, there are three approaches to handling verification of user data:\nCheck the data in all of the fields whenever the data is saved/acted on. Flag fields with invalid data and disable the save/action triggers. Prevent the user from entering invalid data. The first approach usually involves creating some kind of messaging system with a way to tell the user what\u0026rsquo;s wrong with their input after hitting the save button. It can be disruptive, and requires building a flow to handle the error messaging.\nThe second approach is most useful when there are interactions between the data entry elements and what is considered a valid entry can change based on other controls on the screen. For instance, if there are an upper and lower limit to a numeric value that can change based on user selections. In such a case, if the user changes one of those other controls, a value previously entered in a field and considered valid at the time may no longer be valid. Libraries such as ControlsFX have classes which can do this nicely.\nThe third case is what we\u0026rsquo;re going to talk about here: Setting up a TextField so that the user can only enter data which fits a particular structure when they are editing it. It also has the added benefit that it will handle conversions between types for you. TextFields are, by definition, controls to enter text, But by attaching a TextFormatter to your TextField it can automatically convert your text input into, for instance, an integer, allowing you to associate your TextField with integer data.\nInstalling a TextFormatter into a TextField is trivial, just call the TextField.setTextFormatter() method. The TextFormatter class itself, however, needs to be set up properly to handle the work. TextFormatter has two main parts; a filter which handles user inputs, and a converter which maintains synchronicity between the text in the TextField and a value property which is some other data type.\nA Simple Example - A Whole Number TextField # Let\u0026rsquo;s start by looking at a straight forward use of TextFormatter - creating a TextField that will only accept a string of numbers that will be converted into positive integer.\nIn order to test the code, we\u0026rsquo;ll need a basic screen with some controls:\npublic class Main extends Application { private ObjectProperty\u0026lt;Integer\u0026gt; valueProperty = new SimpleObjectProperty\u0026lt;\u0026gt;(0); public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Scene scene = new Scene(new TestPane(), 300, 100); primaryStage.setScene(scene); primaryStage.show(); } public class TestPane extends BorderPane { public TestPane() { TextField textField = new TextField(); setCenter(new VBox(10, new HBox(6, new Text(\u0026#34;TextField 1\u0026#34;), textField), new HBox(6, new Text(\u0026#34;TextField 2\u0026#34;), new TextField()))); } } } This has two extra items in it beyond the TextField to test the TextFormatter: An integer property that we\u0026rsquo;ll use later to test the conversion (think of this as the \u0026ldquo;model\u0026rdquo; in an MVC setup), and a second TextField just so that we can see what happens when focus is gained or lost in the test TextField. When you run this, you\u0026rsquo;ll get a window with a couple of labels and TextFields, and the data entry in both TextFields is free form and will accept anything.\nAdding a Converter to the TextField # Now let\u0026rsquo;s add a converter to the TextField:\npublic class Main extends Application { private ObjectProperty\u0026lt;Integer\u0026gt; valueProperty = new SimpleObjectProperty\u0026lt;\u0026gt;(0); public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Scene scene = new Scene(new TestPane(), 300, 100); valueProperty.addListener(((observable, oldValue, newValue) -\u0026gt; { System.out.println(\u0026#34;Value changed -\u0026gt; Old Value: \u0026#34; + oldValue + \u0026#34;, New Value: \u0026#34; + newValue); })); primaryStage.setScene(scene); primaryStage.show(); } public class TestPane extends BorderPane { public TestPane() { TextField textField = new TextField(); TextFormatter\u0026lt;Integer\u0026gt; textFormatter = new TextFormatter(new IntegerStringConverter()); textFormatter.valueProperty().bindBidirectional(valueProperty); textField.setTextFormatter(textFormatter); setCenter(new VBox(10, new HBox(6, new Text(\u0026#34;TextField 1\u0026#34;), textField), new HBox(6, new Text(\u0026#34;TextField 2\u0026#34;), new TextField()))); } } } Here we\u0026rsquo;ve added a TextFormatter with just a converter applied to it. This TextFormatter wraps an Integer property, so now we can bind that property to the already existing valueProperty field. From the perspective of externally accessing the value in the TextField, we aren\u0026rsquo;t really interested in the TextField's, textPropery any more, all the access should be done through the TextFormatter. So that it\u0026rsquo;s easier to see what\u0026rsquo;s going on with the converter, and when it fires, there\u0026rsquo;s a now listener on the valueProperty field which will display how it\u0026rsquo;s changing on the console.\nThe converter here is an IntegerStringConverter, which extends StringConverter\u0026lt;Integer\u0026gt; and is a standard part of JavaFX. StringConverter has just two methods, fromString() and toString().\nIf you run this code, you\u0026rsquo;ll see that it allows you to type in anything you like, but if you trigger the converter by hitting \u0026lt;Enter\u0026gt; or moving the focus away from the TextField, it will reset your value back to the last valid value if the conversion fails. So if you start with \u0026ldquo;0\u0026rdquo;, and then replace it with \u0026ldquo;abcd\u0026rdquo; and then hit \u0026lt;Tab\u0026gt;. It will put the value back to \u0026ldquo;0\u0026rdquo;.\nThe one thing IntegerStringConverter won\u0026rsquo;t do is prevent the user from entering negative numbers. For this, we\u0026rsquo;ll need to create a custom converter that only accepts positive integers and ensures that negative values cannot be entered:\npublic class PositiveIntegerStringConverter extends IntegerStringConverter { @Override public Integer fromString(String value) { int result = super.fromString(value); if (result \u0026lt; 0) { throw new RuntimeException(\u0026#34;Negative number\u0026#34;); } return result; } @Override public String toString(Integer value) { if (value \u0026lt; 0) { return \u0026#34;0\u0026#34;; } return super.toString(value); } } You can install this new converter when the TextFormatter is instantiated. Call the constructor like this: new TextFormatter(new PositiveIntegerStringConverter()).\nIf you look at the source code for IntegerStringConverter, you\u0026rsquo;ll see that the fromString() method doesn\u0026rsquo;t do anything special to check for non-numeric characters in the string value. It does some checking for empty or Null strings, and then just returns using, Integer.valueOf(value). As I\u0026rsquo;m sure you know, calling Integer.valueOf(value) on a string with non-numerics will throw an exception and IntegerStringConverter does nothing to prevent this from happening. The TextFormatter has a try/catch block that resets the string value if the new string fails to convert. To be consistent with this, the new converter explicitly throws a RuntimeException if the new, converted value is negative. This seems ugly, but since the fromString() method is only ever called in response to changes in the TextField to which it is attached, it\u0026rsquo;s probably okay.\nThe reverse direction is a little more problematic. The toString() method is only going to be called from external changes to the Value property of the TextFormatter. So throwing a RuntimeException is probably going to cause issues. Returning Null is also a problem, as it doesn\u0026rsquo;t trigger an update in the bound View Model property. Setting it to \u0026ldquo;0\u0026rdquo;, causes the change to the bound property when the field loses focus.\nControlling the Input with a Filter # At this point, the code just about meets the minimum criteria to work. It won\u0026rsquo;t let you put in invalid data, and it provides a value of the correct type for the View Model. From a user experience perspective, this is still less than optimal, as it silently changes the user\u0026rsquo;s input without any warning if they enter invalid data. That\u0026rsquo;s never a good thing.\nThe way to handle this is to add a filter to the TextFormatter. A filter is a class that accepts the users change to the string in the TextField, and ensures that it conforms to a set of rules. The \u0026ldquo;change\u0026rdquo; is sent through the filter in the form of an object of type TextFormatter.Change. The filter can reject the change, let it go through, or it can modify it in some way so that it changes the way that the TextField behaves.\npublic class PositiveIntegerFilter implements UnaryOperator\u0026lt;TextFormatter.Change\u0026gt; { @Override public TextFormatter.Change apply(TextFormatter.Change change) { if (change.getControlNewText().matches(\u0026#34;[0-9]*\u0026#34;)) { return change; } return null; } } A UnaryOperator is just a specialized case of the Function interface which returns data of the same type that it accepts. As the filter, it accepts TextFormatter.Change, and returns TextFormatter.Change.\nThis filter is very simple, it just checks to make sure that the new string after the change is applied will only contain digits. It does this by checking return value of getControlNewText(), which is what the string in the TextField would be if this change was applied, and then letting the change go through if it meets the regex test. Otherwise, the filter returns Null, and the change is blocked.\nYou can apply this filter by changing the instantiation of the TextFormatter by calling new TextFormatter(new PositiveIntegerStringConverter(), 0, new PositiveIntegerFilter()). When you do this, the TextField will now allow you to type in numbers, but anything else, including \u0026ldquo;-\u0026rdquo; and \u0026ldquo;.\u0026rdquo;, will be swallowed up by the filter.\nAt this point, the TextFormatter is complete and fully functional. Technically, we could probably go back and remove the negative checking in the fromString() function of the converter, but it\u0026rsquo;s probably best to leave it in so that PostiveIntegerStringConverter really does what its name implies. The important thing is that the filter and the converter need to be compatible with each other. It is possible to set up a converter with at toString() method that generates a string which cannot be edited at all by the rules enforced by the filter.\nIn Part 2, we\u0026rsquo;ll take a deeper look at TextFormatter.Change, and how to create a more complicated filter that makes large differences to the way that TextField works.\n","date":"11 May 2021","externalUrl":null,"permalink":"/javafx/elements/nodes/textformatter/part-1/","section":"JavaFX","summary":"An introduction to TextFormatter, showing you how to integrate it into a TextField and how to use it to convert the input from String to some other kind of value. Then we’ll look at how to add a filter to prevent the user from entering invalid data.","title":"TextFields and TextFormatter - Part 1","type":"javafx"},{"content":" What You\u0026rsquo;ll Learn # How to get a JavaFX project up and running fast. The Application class. The Stage class. The Scene class. Your first Nodes: Label and Region A little bit about organizing your code. The Fastest Start Possible # The quickest way to get started is to download this build.gradle file and put it in a directory all by itself.\nNow, open it with Intellij Idea. I\u0026rsquo;m sure you can do the same thing with Eclipse, too.\nThis should start up a new project. Let it build the Gradle environment and initialize the project. The select the \u0026ldquo;build-\u0026gt;build\u0026rdquo; option from the Gradle menu.\nNow go to the top level of the project and select, \u0026ldquo;New Directory\u0026rdquo;. The dialogue box should give you option for the Gradle defined source directories. Highlight them all and hit \u0026lt;Enter\u0026gt;.\nNext, if you want to change the group information in build.gradle from \u0026ldquo;ca.pragmaticcoding\u0026rdquo;, you should do it now. Then go to the \\src\\java directory and select \u0026ldquo;New -\u0026gt; Package\u0026rdquo;. You should create something like \u0026ldquo;ca.pragmaticcoding.beginners.part1\u0026rdquo;.\nYou\u0026rsquo;ll need a module-info.java file in the root of the java directory. It should look like this:\nmodule ca.pragmaticcoding.beginners { requires javafx.controls; requires javafx.graphics; requires javafx.base; exports ca.pragmaticcoding.beginners.part1; } Make sure that you change both references to the package ca.pragmaticcoding.beginners to whatever you are using.\nFinally, select that package in the Project listing, and then use the menu \u0026ldquo;New -\u0026gt; Java Class\u0026rdquo;, and call it \u0026ldquo;Main\u0026rdquo;. Copy the following into it:\nimport javafx.application.Application; import javafx.stage.Stage; public class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { } } And there you go! Now we have the skeleton of your first JavaFX application.\nApplication, Stage and Scene # Let\u0026rsquo;s take a quick look at this little bit of code, because there\u0026rsquo;s more going on there than it might seem.\nApplication # Application is an abstract class that has all of the functionality that gets the JavaFX engine up and running for you. This is all started up when its launch() method is called. Eventually, launch() will call start() which is an abstract method. We\u0026rsquo;re going to be putting all of our customized code for the application in here.\nStage and Scene # The most mystifying thing when you start with JavaFX is all this Stage and Scene stuff. Simply put, the Stage is the element that corresponds to the window on your screen. If you want to have multiple windows, you\u0026rsquo;ll generally need to have multiple Stages.\nScene is the component that holds the contents of your window. Why is Scene separate from Stage? I don\u0026rsquo;t honestly know, and I don\u0026rsquo;t know what advantage is gained by having the functionality split between two classes.\nOrdinarily, you\u0026rsquo;ll create a Scene and then put a JavaFX Region subclass object inside it. Then you put the Scene inside the Stage, and call the Stage's, show() method.\nSo let\u0026rsquo;s do that:\nimport javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.layout.Region; import javafx.stage.Stage; public class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Scene scene = new Scene(createContent()); primaryStage.setScene(scene); primaryStage.show(); } private Region createContent() { return new Label(\u0026#34;Hello World\u0026#34;); } } Running the Application # You can run it now. I\u0026rsquo;d suggest using the \u0026ldquo;Application -\u0026gt; Run\u0026rdquo; from the Gradle menu. The output should look like this:\nNot very exciting really, but it is something that runs and opens a window.\nTwo little pieces of explanation first, and then we\u0026rsquo;ll conclude Part 1.\nYou\u0026rsquo;ll notice that I\u0026rsquo;ve split createContent() out from the rest of the code in start(), even though it\u0026rsquo;s really simple. Personally, I think start() should be all about Scene and Stage and getting the window on the screen. The details about the contents of the Scene really don\u0026rsquo;t belong in start(). Keeping methods small and purposeful is just as important with JavaFX as it is with any other programming.\nYou should also notice that createContent() returns Label as a Region. Strangely enough, all of the Control classes in JavaFX, including Label extend from Region, even though they don\u0026rsquo;t seem at first to be Region kind of objects. In the specific case of Label, there\u0026rsquo;s actually more going on to its structure than you think, and it actually does make sense to think of it as a Region. We\u0026rsquo;ll look at that later.\n","externalUrl":null,"permalink":"/beginners/part1/","section":"JavaFX","summary":"The very first step.  What else?  “Hello World” in JavaFX.","title":"Part 1 - A Quick Start","type":"javafx"},{"content":" Introduction # This is Part II of a series about setting up whole-home audio using SnapCast, Mopidy and RaspberryPi Zero\u0026rsquo;s (Pi0\u0026rsquo;s). Part I gives an introduction to how SnapCast works, how we\u0026rsquo;re going to use it with Mopidy, and instructions for setting up the SnapCast server.\nIn this article we\u0026rsquo;ll look at setting up the Pi0\u0026rsquo;s as SnapCast clients.\nRaspberryPi Zero\u0026rsquo;s # The RaspberryPi Zero is one of the smallest of the RaspberryPi\u0026rsquo;s (with the Pico being a bit smaller). It looks like this:\nThere\u0026rsquo;s no banana for scale, but the board is about 6cm long.\nIt has built-in WiFi, which some of the earlier full-sized Pi\u0026rsquo;s did not, that adds a little bit more versatility to it. It also has the full set GPIO pins, just like the larger models. It\u0026rsquo;s a bit less powerful than most of the other models, with less memory, but it will do just fine as a SnapCast client.\nAt the time that I did the installations there were images available for Raspbian based on Debian 12, also known as \u0026ldquo;Bookworm\u0026rdquo;. I didn\u0026rsquo;t see anything listed for Debian 13, or \u0026ldquo;Trixie\u0026rdquo; (there was, and I missed it). So I went with the older Bookworm-based Raspbian.\nIn these instructions I\u0026rsquo;m going to stick with the Bookworm, because that\u0026rsquo;s what I used. However, it is highly likely that you could install Trixie and then use the Trixie compatible version of the SnapCast client software and it would work just fine.\nI consider these things, configured as they are, to be essentially \u0026ldquo;appliances\u0026rdquo;. They do a specific job, they\u0026rsquo;re not exposed to the outside world, and, once they are up and running, I don\u0026rsquo;t expect to be updated - ever. It is possible that one day there might be some new feature in SnapCast that I absolutely have to have, but I cannot imagine what that might be right now. They are working fine for me, and I wouldn\u0026rsquo;t be looking at upgrading just for bug fixes or minor changes. I certainly wouldn\u0026rsquo;t update just to move the devices to a newer O/S.\nBy the same token, it wouldn\u0026rsquo;t affect my homelab if the SnapCast project was halted and removed from GitHub tomorrow. My installation would just continue to work as it always had.\nThere\u0026rsquo;s really two elements that need to be configured on the RPi to make it work. The first is the audio hardware, and the second is the SnapCast client, which runs as a service. By far, the audio hardware was the most difficult to figure out, and required a fair bit of trial and error. Consequently, I didn\u0026rsquo;t create a step-by-step guide as I was doing it. I did end up with a process that I could follow 3 more times after I had figured it out.\nFor what it\u0026rsquo;s worth, when setting up the 2nd, 3rd and 4th clients I just SSH\u0026rsquo;d into the first one and typed history, then replicated the steps on the new RPi0.\nInstall the DAC Board # The RPi0 does not have any analog audio output, so you\u0026rsquo;ll need an add-on board to do this. For this project, I\u0026rsquo;ve used the Pimoroni DAC SHIM because it was readily available and inexpensive. It\u0026rsquo;s possible, even if you are using a larger RPi, that you\u0026rsquo;ll still need an external DAC of some sort to get adequate quality sound.\nThe Pimoroni SHIM looks like this:\nIf you are using the Pimoroni board, then orient it such that the 3.5mm jack is over top of the SD card slot on the RPi0. Then line up the holes with the GPIO pins and push it down, all the way to the bottom. It should be a tight fit and require a fair amount of pressure to get it to slide down. But not so much that you would describe it as \u0026ldquo;forcing\u0026rdquo; it down.\nThat\u0026rsquo;s all it takes. Some of the HiFiBerry boards have the same kind of connection, so they should be just as easy. If you\u0026rsquo;re using something else, then RTFM to figure it out.\nInstall Raspbian # If you are new to Raspberry Pi\u0026rsquo;s, then you need to understand that they usually boot from an operating system loaded onto an SD card of some sort. The RPi0\u0026rsquo;s use MicroSD cards. 8GB or 16GB cards should be more than enough. Bigger ones work just fine. The operating system is loaded via some other computer that you have that can read/write to SD cards.\nTo load the O/S onto the the SD card you should use the official Imager software. The instructions for installing it are here. I\u0026rsquo;m using Ubuntu on my desktop system, so I was able to just use sudo apt install rpi-imager and it was all done.\nWhen you run the imager, the first step is to pick the type of RPi device and then the O/S version. As of the time of writing this, the correct option was \u0026ldquo;Legacy\u0026rdquo;, not \u0026ldquo;Trixie\u0026rdquo;. This should give you the \u0026ldquo;Bookworm\u0026rdquo; based Raspbian, which is compatible with the SnapCast client software.\nOnce you\u0026rsquo;ve picked the output device, which should be your new MicroSD card, you\u0026rsquo;ll get an option to edit the settings. Do that.\nChange the hostname to something that makes sense to you. I picked \u0026ldquo;snapclient0\u0026rdquo;, then \u0026ldquo;snapclient1\u0026rdquo; for the next one, and so on.\nSet a username and a password, make note of both. Then input the WiFi connection details and set the time zone and locale.\nIn the \u0026ldquo;SERVICES\u0026rdquo; tab, enable SSH and use password authentication.\nClick \u0026ldquo;SAVE\u0026rdquo;, and then pick \u0026ldquo;YES\u0026rdquo;. Go through the \u0026ldquo;Are you sure?\u0026rdquo; warnings and let it run.\nWhen it\u0026rsquo;s done, stick the MicroSD card in the RPi0 and plug it in the power. Theoretically at least, in a little while, you should be able to SSH into it. In practice, I have found that they get stuck on the first boot waiting for some kind of keyboard input. So you might need to plug in a monitor, keyboard and mouse.\nBe warned that the RPi0 only has two USB \u0026ldquo;B\u0026rdquo; ports, and one is taken up with the power input. So you\u0026rsquo;ll need a USB hub to connect both a mouse and keyboard at the same time. Also note that those two USB ports are very close together, so if you\u0026rsquo;re using a bunch of adaptors and converters, you\u0026rsquo;ll need to make sure that they fit when the power port is populated.\nSecondly, the HDMI connector on the RPi0 is a mini HDMI, You\u0026rsquo;ll need a compatible HDMI cable or a converter for that.\nI\u0026rsquo;m going off memory here, but I think that one of the key things you\u0026rsquo;ll need to do with the KVM hooked up is to tell the RPi0 not to load the GUI desktop at boot. This will speed up subsequent boots and boost your performance. Also, you\u0026rsquo;ll never use the GUI environment.\nOnce you\u0026rsquo;ve got the RPi0 fully booted, you should be able to SSH into it. I\u0026rsquo;d give it one more test boot, just to make sure completes a \u0026ldquo;hands free\u0026rdquo; boot, before disconnecting the KVM equipment.\nI know these instructions seem a little vague, but there\u0026rsquo;s nothing difficult or tricky about this part, and the steps should be obvious as you run through it.\nSSH into your RPi0 and start configuring the SnapCast client\u0026hellip;\nEdit The Firmware Configuration # This is how you tell the RPi to use the add-on board and not any native audio equipment.\nThe file you need to edit is /boot/firmware/config.txt. I\u0026rsquo;ve marked the lines that needed to be changed or added:\n# For more options and information see # http://rptl.io/configtxt # Some settings may impact device functionality. See link above for details # Uncomment some or all of these to enable the optional hardware interfaces dtparam=i2c_arm=on \u0026lt;---- uncomment dtparam=i2s=on \u0026lt;---- uncomment dtparam=spi=on \u0026lt;---- uncomment # Enable audio (loads snd_bcm2835) dtparam=audio=off # Additional overlays and parameters are documented # /boot/firmware/overlays/README # Automatically load overlays for detected cameras camera_auto_detect=1 # Automatically load overlays for detected DSI displays display_auto_detect=1 # Automatically load initramfs files, if found auto_initramfs=1 # Enable DRM VC4 V3D driver dtoverlay=vc4-kms-v3d max_framebuffers=2 # Don\u0026#39;t have the firmware create an initial video= setting in cmdline.txt. # Use the kernel\u0026#39;s default instead. disable_fw_kms_setup=1 # Disable compensation for displays with overscan disable_overscan=1 # Run as fast as firmware / board allows arm_boost=1 dtoverlay=hifiberry-dac \u0026lt;--- add this line gpio=25=op,dh [cm4] # Enable host mode on the 2711 built-in XHCI USB controller. # This line should be removed if the legacy DWC2 controller is required # (e.g. for USB device mode) or if USB support is not required. otg_mode=1 [cm5] dtoverlay=dwc2,dr_mode=host [all] gpio=25=op,dh \u0026lt;--- add this line You will also need a file called /etc/asound.conf. You\u0026rsquo;ll probably have to create it, and it needs to have just two lines:\ndefaults.pcm.card 1 defaults.ctl.card 1 Installing the SnapClient # The next step is to install the SnapClient service software. Just as with the SnapServer software, it\u0026rsquo;s best to download it from GitHub and then use dpkg to install it. It\u0026rsquo;s also advisable to make sure that all of your O/S elements are updated before you attempt the installation, so I\u0026rsquo;ve included those steps in here:\n4 wget https://github.com/snapcast/snapcast/releases/download/v0.34.0/snapclient_0.34.0-1_armhf_bookworm.deb 5 sudo apt update 6 sudo apt upgrade 7 sudo dpkg -i snapclient_0.34.0-1_armhf_bookworm.deb This will install the snapclient software and set it up as a service. You should set it up to run at boot:\nsudo systemctl enable snapclient In most versions of Linux, you use systemctl to control sevices on the system. The enable command in systemctl tells the system to load the service at boot time.\nBefore you do anything else, you should configure the client. Edit the file /etc/default/snapclient. The file should already be there, and it should look something like this when you are done:\n# Start the client, used only by the init.d script START_SNAPCLIENT=true # Additional command line options that will be passed to snapclient # note that user/group should be configured in the init.d script or the systemd unit file # For a list of available options, invoke \u0026#34;snapclient --help\u0026#34; SNAPCLIENT_OPTS=\u0026#34;-h 10.93.50.107 -s 1\u0026#34; Obviously, you\u0026rsquo;ll need to put in whatever IP address your SnapServer is running on, instead of mine. The -s 1 is important as it tells the SnapCast client to use the DAC board.\nAt this time, you should reboot the RPi0, which will cause it to load the new audio configuration and start the SnapClient service.\nTesting and Debugging # If everything has gone well, then your SnapCast server should have received a connection request from your RPi0. This means that you should see it if you go back into your SnapCast web page.\nIf you issue the following command on your SnapCast server:\nsudo journalctl -u snapserver -n40 You should see a line near the bottom that looks something like this:\nApr 02 18:28:40 snapcast snapserver[41889]: (Server) Hello from b8:27:eb:a3:3d:60, host: snapcast0, v0.34.0, ClientName: Snapclient, OS: Raspbian GNU/Linux 12 (bookworm), Arch: armv6l, Protocol version: 2, Auth: {\u0026#34;param\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;scheme\u0026#34;:\u0026#34;\u0026#34;} If not, then your RPi0 hasn\u0026rsquo;t connected to SnapCast server. Back on the RPi0, you should be able to do this:\n$ sudo systemctl status snapclient --lines 30 ● snapclient.service - Snapcast client Loaded: loaded (/lib/systemd/system/snapclient.service; enabled; preset: enabled) Active: active (running) since Fri 2026-04-03 12:46:35 EDT; 3min 16s ago Docs: man:snapclient(1) Main PID: 17962 (snapclient) Tasks: 2 (limit: 373) CPU: 32.803s CGroup: /system.slice/snapclient.service └─17962 /usr/bin/snapclient --hostID SomeRoom --logsink=system -h 10.93.50.107 -s 1 Apr 03 12:46:35 snapcast1 systemd[1]: Started snapclient.service - Snapcast client. Apr 03 12:46:35 snapcast1 snapclient[17962]: (Snapclient) Options \u0026#39;--host\u0026#39; and \u0026#39;--port\u0026#39; are deprecated. Please add the server URI as last command line argument Apr 03 12:46:35 snapcast1 snapclient[17962]: (Snapclient) Version 0.34.0, revision 0ac55084 Apr 03 12:46:35 snapcast1 snapclient[17962]: (Connection) Resolving host IP for: 10.93.50.107 Apr 03 12:46:35 snapcast1 snapclient[17962]: (Connection) Connecting to host: 10.93.50.107:1704, port: 1704, protocol: tcp Apr 03 12:46:35 snapcast1 snapclient[17962]: (Connection) Connected to 10.93.50.107 Apr 03 12:46:35 snapcast1 snapclient[17962]: (Connection) My MAC: \u0026#34;b8:27:eb:7b:b4:33\u0026#34;, socket: 9 Apr 03 12:46:36 snapcast1 snapclient[17962]: (Controller) ServerSettings - buffer: 2000, latency: 0, volume: 90, muted: 0 Apr 03 12:46:36 snapcast1 snapclient[17962]: (Controller) Codec: flac, sampleformat: 48000:16:2 Apr 03 12:46:36 snapcast1 snapclient[17962]: (Player) Player name: alsa, device: lavrate, description: Rate Converter Plugin Using Libav/FFmpeg Library, idx: 1, sharing mode: unspecified, parameters: \u0026lt;none\u0026gt; Apr 03 12:46:36 snapcast1 snapclient[17962]: (Player) Mixer mode: software, parameters: \u0026lt;none\u0026gt; Apr 03 12:46:36 snapcast1 snapclient[17962]: (Player) Sampleformat: 48000:16:2, stream: 48000:16:2 Apr 03 12:46:36 snapcast1 snapclient[17962]: (Alsa) Using default buffer_time: 80 ms, default fragments: 4 Apr 03 12:46:36 snapcast1 snapclient[17962]: (Alsa) PCM name: lavrate, sample rate: 48000 Hz, channels: 2, buffer time: 80000 us, periods: 4, period time: 20000 us, period frames: 960 Apr 03 12:46:36 snapcast1 snapclient[17962]: (Stream) No chunks available Apr 03 12:46:36 snapcast1 snapclient[17962]: (Alsa) Failed to get chunk Apr 03 12:46:36 snapcast1 snapclient[17962]: (Controller) diff to server [ms]: -3.68704e+08 You should see that it is active in the \u0026ldquo;Active:\u0026rdquo; line. You should also see that it says \u0026ldquo;enabled\u0026rdquo; somewhere in the \u0026ldquo;Loaded:\u0026rdquo; line.\nYou should also see in the \u0026ldquo;CGroup:\u0026rdquo; line 2, that you have a host IP address that is correct for your SnapCast server.\nUnderneath the summary is most recent journal log output since the service has been started. You should see the interaction between the client and the server happening here.\nYou can push the white noise through the fifo again, and you should hear it through the RPi0. Just about anything that you can plug into a 3.5mm jack should work. A powered speaker, an amplifier, even headphones.\nWhat If You Don\u0026rsquo;t Hear Anything # If you saw the client connecting to the server, but you don\u0026rsquo;t hear anything. Then you have a problem with your sound configuration or you haven\u0026rsquo;t included that \u0026ldquo;-s 1\u0026rdquo; in the client options. Check that last bit first.\nYou can confirm the sound devices with aplay:\n$ aplay -l **** List of PLAYBACK Hardware Devices **** card 0: vc4hdmi [vc4-hdmi], device 0: MAI PCM i2s-hifi-0 [MAI PCM i2s-hifi-0] Subdevices: 1/1 Subdevice #0: subdevice #0 card 1: sndrpihifiberry [snd_rpi_hifiberry_dac], device 0: HifiBerry DAC HiFi pcm5102a-hifi-0 [HifiBerry DAC HiFi pcm5102a-hifi-0] Subdevices: 0/1 Subdevice #0: subdevice #0 That \u0026ldquo;card 1\u0026rdquo; should line up with your \u0026ldquo;-s 1\u0026rdquo;. If the DAC has some different number then use it for the \u0026ldquo;-s\u0026rdquo; option.\nIt took me a lot of fiddling about to find out how to get the setup right on the very first RPi0 that I set up, because I didn\u0026rsquo;t get any sound at the beginning. Looking back at my history stack from that first RPi0, I see that aplay was really the tool that let\u0026rsquo;s you figure it out.\nYou can use aplay to play a file. This let\u0026rsquo;s you eliminate the SnapCast elements and confirm that, yes, your RPi0 is sending sound through the DAC:\n$ wget https://sample-files.com/downloads/audio/wav/pure-tone.wav $ aplay pure-tone.wav Take a good look at the man pages for aplay. There are lots of different options you can play with to understand what\u0026rsquo;s going on with you sound system.\nConclusion # At this point you should have a Raspberry Pi Zero running as a SnapCast client that you can send sound to from a SnapCast server!\nI can say that the first one took me a couple of days to get running because the documentation just isn\u0026rsquo;t there, and there are quite a few moving parts to figure out at the beginning. However, if you are using a RPi0 and a HiFiBerry or Pimoroni DAC there is no reason that you need to have go through the same experience because all of the details are right here in this article.\nThis is Actually Easy # I\u0026rsquo;ve now set up 4 of these RPi0\u0026rsquo;s and it takes about half an hour all-in, including the 15 minutes or so that the Imager program takes to set up the SD card. Here is the entire history stack from the 4th one:\n1 ip a 2 sudo apt update 3 sudo apt upgrade 4 sudo vi /boot/firmware/config.txt 5 wget https://github.com/snapcast/snapcast/releases/download/v0.34.0/snapclient_0.34.0-1_armhf_bookworm.deb 6 sudo dpkg -i snapclient_0.34.0-1_armhf_bookworm.deb 7 sudo systemctl status snapclient 8 sudo vi /etc/default/snapclient 9 sudo systemctl restart snapclient 10 sudo systemctl status snapclient 11 aplay -l 12 sudo reboot 13 aplay -l 14 sudo systemctl status snapclient 15 sudo vi /etc/asound.conf 16 sudo reboot The ip a at the beginning was to make it easier for me to find the device in my DHCP leases and set the address as static there.\nAfter line 10, I don\u0026rsquo;t think it was playing anything when I plugged it into the amp. Then you can see me checking aplay -l, rebooting it to make sure it had reread the configuration, then checking again. I finally realized that I had forgotten to create /etc/asound.conf, so I created it. Then another reboot, and sound was coming out of it.\nAt that point I disconnected from it and haven\u0026rsquo;t log in again until I started writing this article.\nIn Part III\u0026hellip; # Part III is the final article in the series, and shows how to set up Mopidy to stream music and how to connect it to your SnapCast server. It also shows how you can use the Mopidy REST API to do some simple automation and stream recovery.\n","date":"21 June 2026","externalUrl":null,"permalink":"/homelab/snapcast1/","section":"Homelabs","summary":"Part 2 of a 3 part series describing how to set up whole-home audio using SnapCast, Mopidy and Rasberry Pi’s.  In this article, we look at how to configure a Raspberry Pi Zero as a SnapCast client.","title":"Whole Home Audio With SnapCast - Part II","type":"homelab"},{"content":" Introduction # In Part 1 of this series, we looked at the Observable classes designed to wrap aribitrary Object values. In this article we are going to move on to the typed Observable classes and interfaces. We\u0026rsquo;ll look at how they are different from the generic types, and how to deal with compatibility issues between the two.\nIf you haven\u0026rsquo;t read Part 1 of this series, you should probably go and do that now, as the information in this article will make more sense to you.\nThe Typed Classes and Interfaces # There are 6 sets of typed Observable classes and interfaces. The each deal with one of the following types:\nString Boolean Integer Double Long Float We don\u0026rsquo;t need to look at all of them because they break down into two groups: The numeric types, and the non-numeric types. We will look at just one type from each group, String for the non-numeric and Integer for the numeric. The non-numeric is simpler, so we\u0026rsquo;ll start with that:\nString Observables # The String types are a little bit more interesting to look at than Boolean because the Fluent API library for String is larger. So we\u0026rsquo;ll use String as our example type for a deep dive. Here\u0026rsquo;s the chart:\nHere you can see that all of the generic \u0026lt;T\u0026gt;\u0026rsquo;s have been replaced with \u0026lt;String\u0026gt;. Understand, though, that there is no interface called ObservableValue\u0026lt;String\u0026gt;, it\u0026rsquo;s just ObservableValue\u0026lt;T\u0026gt; with the \u0026lt;T\u0026gt; resolved as a \u0026lt;String\u0026gt;. The same holds true for all of the other entries on the chart defined with \u0026lt;String\u0026gt;.\nThe Interfaces # We\u0026rsquo;ll start with the interfaces. Some of the descriptions are a little bit stripped down from what you\u0026rsquo;ll see in Part 1. I want this article to be complete within itself, but I don\u0026rsquo;t want to repeat a lot of information that you can find in Part 1.\nObservable \u0026amp; ObservableValue\u0026lt;String\u0026gt; # At the top of the chart, you\u0026rsquo;ll find the interface, Observable. You\u0026rsquo;ll notice that it is not typed at all, it\u0026rsquo;s not even a generic. That\u0026rsquo;s because all of its methods do not deal with data.\nThis is the root for all of the other classes and interfaces, but it\u0026rsquo;s really quite simple and only defines three methods: addListener(), removeListener() and subscribe(). These are all related to the process called \u0026ldquo;Invalidation\u0026rdquo;.\nThe next interface down is ObservableValue\u0026lt;String\u0026gt;, which extends Observable. The key method in this interface is ObservableValue.getValue(), which allows us to actually read the value and will re-validate the Observable.\nNow that we can read the value we can have the methods related to ChangeListeners, including those related to Subscriptions. The other methods it adds are the map() and flatMap() methods used for transforming an ObservableValue into a different ObservableValue.\nThere is also the ObservableObjectValue\u0026lt;String\u0026gt; interface, which introduces the get() method, which at this point is effectively identical to getValue().\nObservableStringValue # ObservableStringValue adds no new methods and simply extends ObservableObjectValue\u0026lt;String\u0026gt;. Essentially, you can use these two interfaces interchangeably.\nHowever, if you are writing code that deals with the specific String Observables using ObservableStringValue might give a more consistent feel to the code.\nReadOnlyProperty\u0026lt;String\u0026gt; \u0026amp; WritableValue\u0026lt;String\u0026gt; # These are the two parent interfaces for all of the Property classes.\nReadOnlyProperty\u0026lt;String\u0026gt; simply introduces the getBean() and getName() methods. These are presumably meaningful methods if you are serializing your Properties.\nThe interface WritableValue\u0026lt;String\u0026gt; gives us the setValue() method. Now we can actually put something into an Observable!\nWritableObjectValue\u0026lt;String\u0026gt; and WritableStringValue # WritabaleStringValue is an extension of WritableObjectValue\u0026lt;String\u0026gt; that adds no methods. So you can treat it as a synonym for WritableObjectValue\u0026lt;String\u0026gt;.\nThese two classes add the set() method, which, for String types is identical to setValue().\nProperty\u0026lt;String\u0026gt; # The Property interface extends the previous two interfaces and adds the ability to bind to other observables. We get bind() and bindBidirectional(), and their corresponding unbinding methods. Also there is the isBound() method that will tell us if the Property has been bound to something.\nBinding\u0026lt;String\u0026gt; # Binding\u0026lt;String\u0026gt; is the last interface on the chart. It extendsObservableValue\u0026lt;String\u0026gt; adding a method to force the Binding to invalidate, a method to check if the Binding is valid and a method to get the dependencies of the Binding.\nThe Classes # Everything else on the chart is a class, let\u0026rsquo;s take a look at them.\nStringExpression # This is the root class for all of the Property\u0026lt;String\u0026gt; classes, even though the diagram goes diagonally down to ObjectProperty\u0026lt;String\u0026gt;. Note that StringExpression is a drop-in replacement for ObjectExpression from Part I and does not extend or implement ObjectExpression\nThis is the only class in the chart that introduces new concrete methods that don\u0026rsquo;t implement methods already defined by any of the interfaces in the chart.\nNote StringExpression is where this chart really diverges from the generic chart. Every class that inherits from StringExpression is fundamentally different from the generic equivalent.\nThis class provides the core functionality for the \u0026ldquo;Fluent API\u0026rdquo; for creating String bindings. StringExpression is an abstract class that implements a number of String related operations such as concat() and length() and isEmpty() in an Observable fashion. We also have methods for equality, inequality, greater than, less than, greater than or equal, less than or equal, and checking for a Null value, each of which creates a BooleanBinding.\nThere\u0026rsquo;s also a method called getValueSafe(). It returns a String and will return an empty String if the value in the StringExpression is null.\nIt is StringExpression that lets you do things like this:\nvar label = Label() label.textProperty().bind(model.lastNameProperty().concat(\u0026#34;, \u0026#34;).concat(model.firstNameProperty())) This inclusion of StringExpression in the class hierarchy is what makes ObjectProperty\u0026lt;String\u0026gt; different from StringProperty. The code snippet above assumes that model.lastNameProperty() is a StringProperty, not an ObjectProperty\u0026lt;String\u0026gt;. Because the latter wouldn\u0026rsquo;t work because ObjectExpression (which is the base class for ObjectProperty does not define concat().\nReadOnlyStringProperty # This class extends StringExpression and implements ReadOnlyProperty. This class can also be thought of as very much like ReadOnlyProperty but with the ability to use the Fluent API with it to create bindings.\nStringProperty # The best way to think about StringProperty is an implementation of Property\u0026lt;String\u0026gt; but, since it inherits from StringExpression through ReadOnlyStringProperty, also has the methods for creating bindings via the Fluent API. However, it only has implementations for bidirectional binding and setting the value.\nThis is, however, a great class to declare your instantiated Properties as. Like this:\nval someProperty : StringProperty = SimpleStringProperty(\u0026#34;abc\u0026#34;) StringPropertyBase # This class adds almost all the remaining methods defined in the various interfaces up hierarchy. We get methods to bind and unbind, add and remove Listeners and a get() method.\nThe methods that are missing are the two silly Java Bean related methods. This is the class that you probably want to extend from if you want to create your own StringProperty classes, especially if you want to ignore Java Bean stuff as much you can.\nSimpleStringProperty # This is the only class in the chart that you can directly instantiate, so everybody is familiar with it. It adds the getBean() and getName() methods and the infrastructure and constructors to set their values.\nReadOnlyStringPropertyBase and ReadOnlyStringWrapper # ReadOnlyStringPropertyBase is very similar to StringPropertyBase in that it is an abstract class that has almost all of the interface methods implemented except for the \u0026ldquo;Bean\u0026rdquo; methods. It\u0026rsquo;s also missing the get() method.\nThe only \u0026ldquo;only out of the box\u0026rdquo; implementation of ReadOnlyStringPropertyBase is contained in ReadOnlyStringWrapper. This is a fairly involved topic that was covered in depth here in Part I.\nStringBinding # This is the sole class on the Binding side of the chart, and it\u0026rsquo;s abstract and it extends StringExpression. Any utility (including the Fluent API) that creates String Bindings will do so by extending from this class.\nIt doesn\u0026rsquo;t add any new public methods but there are a few protected methods that are the framework for custom Binding classes. We get bind() and unbind() to start with. We also get allowValidation(), onInvalidating() and isObserved(). There is one abstract method, and that\u0026rsquo;s computeValue() which is also protected.\nThe intention is clear about the standard use case for custom classes extended from StringBinding. It\u0026rsquo;s that the Observables to be bound are defined when the class is instantiated and that you\u0026rsquo;ll also define a computeValue() method that will use those bound values to determine the return value of any calls to get() or getValue().\nTip Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\nSince StringBinding extends from StringExpression you can use the Fluent API to modify the results or combine it with other StringExpressions.\nCompatibility Issues # The main difference between the entries on this chart and the one for the generic Observables is that ObjectExpression has been swapped out for StringExpression. These two classes have different methods, which will create some type incompatibilities.\nHowever, it turns out that you can fairly freely mix and match StringProperty and ObjectProperty\u0026lt;String\u0026gt; in bindings without a lot of problems.\nThis is mostly because the bind() method in ObjectPropertyBase\u0026lt;String\u0026gt; takes an ObservableValue\u0026lt;? extends String\u0026gt;, while StringPropertyBase.bind() also takes ObservableValue\u0026lt;? extends String\u0026gt;. And of course, both classes implement ObservableValue\u0026lt;String\u0026gt;.\nYou can even put a ObjectProperty\u0026lt;String\u0026gt; as a parameter in the Fluent API methods and it will work without a hitch. Generally speaking, the Fluent API methods will accept primitive classes as well as Observable classes, but obviously won\u0026rsquo;t trigger an invalidation if a primitive value changes. Internally, a dependency is created if the parameter is an instance of ObservableValue. Which of course, ObjectProperty\u0026lt;String\u0026gt; is.\nHowever, ObjectProperty\u0026lt;String\u0026gt; and StringProperty are not type compatible as method parameter declarations. Unless you are planning on using the Fluent API do not create your method parameters as StringProperty. The same goes for ObjectProperty\u0026lt;String\u0026gt; although you\u0026rsquo;re probably not going to be using the methods from ObjectExpression over StringExpression. Specify your parameters as Property\u0026lt;String\u0026gt; and it will accept both ObjectProperty\u0026lt;String\u0026gt; and StringProperty.\nNumber Types # This is the most confusing part of the entire discussion. JavaFX utilizes the standard Java class called Number to create a framework for all of the numeric Observables. I\u0026rsquo;m not sure what this adds, other than a layer of complexity, but it\u0026rsquo;s a layer that you can\u0026rsquo;t ignore.\nNumber is an abstract class that is the super-class of all of the \u0026ldquo;boxed\u0026rdquo; number types in Java. For intstance, Integer (as opposed to int) is a subclass of Number. Number defines a set of methods that deal with conversion between the various subtypes of Number.\nThe Number types of Observables share many of the same differences from the generic Observables as the String types do. So in this section we\u0026rsquo;re going to concentrate on the extra complexity that comes from using Number for all of these variants.\nAll four of the numeric types work the same way. Let\u0026rsquo;s look at the chart for Integer:\nThe Generically Defined Types # The generic classes in this chart all resolve to \u0026lt;Number\u0026gt;, not \u0026lt;Integer\u0026gt;. So IntegerProperty implements Property\u0026lt;Number\u0026gt;, not Property\u0026lt;Integer\u0026gt;. This means that if you have an instance of ObjectProperty\u0026lt;Integer\u0026gt; it is not type compatible with IntegerProperty. This can be a source of frustration.\nObservableNumberValue \u0026amp; ObservableIntegerValue # The ObservableNumberValue interface extends ObservableValue\u0026lt;Number\u0026gt;, which gives it the Listener, mapping and Subscription methods. Its getValue() method returns Number.\nAdditionally, it has type specific getValue() equivalents: doubleValue(), floatValue(), intValue() and longValue().\nObservableIntegerValue extends ObservableNumberValue() and adds one method, get(). However ObservableIntegerValue.get() returns int not Number.\nThis is very significant.\nThis is the first example that we\u0026rsquo;ve seen where get() returns a different value from getValue(). This suggests to me, that when you are retrieving the value from a numeric Observable, you can largely ignore their \u0026lt;Number\u0026gt; nature if you just use get() all the time instead getValue().\nAlso, ObservableBooleanValue.get() returns boolean instead of Boolean returned from ObseravbleBooleanValue.getValue(). It\u0026rsquo;s a smaller change, but still a difference.\nWritableNumberValue \u0026amp; WritableIntegerValue # WritableNumberValue is what the JavaDocs describe as a \u0026ldquo;tagging interface\u0026rdquo;. It has no methods defined, and simply appears to be a common ancestor for the numeric Writable{Type}Value interfaces.\nWritableIntegerValue is a parallel to ObservableIntegerValue but for writing. We have a difference between the two setters, as WritableIntegerValue.set() takes an int, while the setValue() method takes Number.\nNumberExpression # Unlike ObjectExpression and StringExpression, NumberExpression is an interface, not a class. It defines methods for the four basic math operations - add, subtract, multiply and divide - and these operations all return a NumberBinding. Additionally, it defines a number of comparison methods, all of which return BooleanBinding. There\u0026rsquo;s also asString() which returns a StringBinding.\nFor the math operations, each method is overloaded to take 5 possible parameter types. The first four are non-Obseravble number classes: double, int,long and float. The fifth is for ObservableNumberValue. Regardless of the parameter type, you always get a NumberBinding (which we haven\u0026rsquo;t seen yet) in return.\nNumberExpressionBase # This is our first class on the binding side of the chart, and this is abstract and partially implements NumberExpression.\nThis class has concrete implementations of all of the comparison methods, and for the numeric operation methods taking ObservableNumberValue as a parameter. In other words, you\u0026rsquo;ll find add(ObservableNumberValue other), but not add(int other), or add(double other).\nIntegerExpression # Another abstract class, IntegerExpression supplies all of the concrete implementations of the methods defined in NumberExpression that are not implemented in NumberExpressionBase. However, it returns values that match the type of the result of the operation.\nThis means that IntegerExpression.add(int other) will return a IntegerBinding instead of a NumberBinding.\nAlso, this class has asObject() which returns a linked ObjectExpression\u0026lt;Integer\u0026gt;. More on this later.\nNumberBinding \u0026amp; IntegerBinding # NumberBinding is another \u0026ldquo;tagging\u0026rdquo; interface. It has no methods, but extends Binding\u0026lt;Number\u0026gt; and NumberExpression.\nIntegerBinding, just like StringBinding is an abstract class that provides the starting point for any custom Bindings that you want to write, but for Integer values. Note though, that it implements Binding\u0026lt;Number\u0026gt; and ObservableValue\u0026lt;Number\u0026gt;, not Binding\u0026lt;Integer\u0026gt; and ObservableValue\u0026lt;Integer\u0026gt;.\nUsing this Information # At this point you should have a handle on when and how to use both the generic and the typed Observable classes. What are the some of the factors you need to keep in mind when using both the generic and the typed Obsevables?\nConverting Between Generic and Typed Properties # As we\u0026rsquo;ve seen IntegerProperty implements Property\u0026lt;Number\u0026gt;, not Property\u0026lt;Integer\u0026gt;. This means that it also implements ObservableValue\u0026lt;Number\u0026gt;.\nAnd that means that you cannot pass IntegerProperty as a parameter when Property\u0026lt;Integer\u0026gt; or ObservableValue\u0026lt;Integer\u0026gt; is required.\nThis quickly becomes frustrating.\nFortunately all of the Property\u0026lt;Number\u0026gt; implementations have a function called asObject(). For IntegerProperty this will return you a Property\u0026lt;Integer\u0026gt; that is bidirectionally bound to your IntegerProperty. It\u0026rsquo;s not the same object cast differently, but an actual new object that\u0026rsquo;s bidirectionally bound to the original.\nAll of these typed classes also have a static method that does the reverse. For instance, DoubleProperty has a method called doubleProperty(). You pass it a Property\u0026lt;Double\u0026gt; and it gives you back a new DoubleProperty that is bidirectionally bound to the original Property\u0026lt;Double\u0026gt;. This goes for StringExpression and BooleanExpression\nKnowing about this makes any issues about whether or not to use typed or generic Properties a lot less critical. You can very easily convert from one to the other when required.\nFluent API # The Fluent API is delivered via the {Type}Expression classes, and is the main motivation to use typed Observable classes over the generic ones.\nSince the introduction of ObservableValue.map() in JFX 19, a lot of the utility in the Fluent API is a bit less compelling, since map() is simpler to use because the Function passed to it is just Java (or Kotlin) code. What ObservableValue.map() cannot do, however, is to combine multiple Observables together as dependencies.\nI find the Fluent API to be fine when the transformations are fairly simple. But when the operations start to get long and involved, then the Fluent API tends to become less clear. And if it\u0026rsquo;s less clear, then it\u0026rsquo;s more likely to be the source of a bug or cause headaches in maintenance. In those cases, it can be better to use a static methods from the Bindings class, or to create a Binding by extending one of the {Type}Binding classes.\nIn my opinion, then, the use case for the Fluent API is fairly narrow: Simple transformations involving two or more Observables.\nNot that this is uncommon. Use cases like this happen all the time:\nval fullName : ObservableValue\u0026lt;String\u0026gt; = lastNameProperty.concat(\u0026#34;, \u0026#34;).concat(firstNameProperty) It\u0026rsquo;s hard to think of an easier, cleaner way to do this. However\u0026hellip;\nval nameString : ObservableValue\u0026lt;String\u0026gt; = lastNameProperty.map{\u0026#34;The last name is $it\u0026#34;} Cannot be performed via the Fluent API alone and you would have to otherwise use the Bindings library:\nval nameString : ObservableValue\u0026lt;String\u0026gt; = Bindings.concat(\u0026#34;The last name is \u0026#34;).concat(lastNameProperty) Note that the first concat() is from Bindings and the second is from StringExpression.\nYou can combine ObservableValue.map() with the Fluent API:\nval nameString : ObservableValue\u0026lt;String\u0026gt; = lastNameProperty.concat(\u0026#34;, \u0026#34;).concat(firstNameProperty).map{\u0026#34;The full name is $it\u0026#34;} But the map() call has to come at the end, because it returs ObservableValue\u0026lt;String\u0026gt; which doesn\u0026rsquo;t support the Fluent API. If you really did want to do it the other way:\nval nameString : ObservableValue\u0026lt;String\u0026gt; = StringExpression.stringExpression(lastNameProperty.map{\u0026#34;The full name is $it,\u0026#34;}).concat(firstNameProperty) But, honestly, that seems like a lot of work.\nMethod Overloading # In Java, you cannot overload methods where the signatures only differ by the types of their enclosed generics. For instance this code will fail to compile:\nfun doSomething(param1 : Property\u0026lt;String\u0026gt;) { . . } fun doSomething(param1: Property\u0026lt;Int\u0026gt;) { . . } You get an error that says something about \u0026ldquo;erasure\u0026rdquo; and non-unique signatures. What you can do instead is this:\nfun doSomething(param1 : StringProperty) { . . } fun doSomething(param1: IntegerProperty) { . . } This will work just fine. This is the second reason that you might want to use the typed Observable classes.\nDealing With Node Properties # You should be aware that virtually all of the JavaFX Nodes expose their Properties as typed Properties whenever possible. For instance, Region.widthProperty() will return a ReadOnlyDoubleProperty while, Region.paddingProperty() will return ObjectProperty\u0026lt;Insets\u0026gt;.\nThis makes sense, since, as a general purpose API, they would want to provide the most \u0026ldquo;out of the box\u0026rdquo; functionality, and returning Properties that can be used in the Fluent API without any conversion seems like the way to go. You\u0026rsquo;ll have to decide for yourself if this would influence the way that you declare your own Properties.\nThere are a few places in the JavaFX API where it\u0026rsquo;s hard to avoid understanding some of the nuances about the typed classes versus the generic classes. For instance, TextFormatter\u0026lt;Integer\u0026gt; has a method valueProperty() that returns ObjectProperty\u0026lt;Integer\u0026gt;, which is going to give you issues if you try to bidirectionally bind it with an IntegerProperty in your Model.\nWhen to Use ReadOnly{Type}Property # If you look at the JavaDocs for Region you\u0026rsquo;ll see that Region.widthProperty() returns ReadOnlyDoubleProperty, because they are using ReadOnlyIntegerWrapper internally and want widthProperty() to return a truly \u0026ldquo;read only\u0026rdquo; object. This pattern is repeated frequently through the JavaFX library.\nI think that most programmers are used to dealing with ReadOnly{Type}Property as return values from the JavaFX API, and probably don\u0026rsquo;t even know about {Type}Expression, even though these two class categories differ only in support for the Java Bean methods.\nAt the end of the day, ReadOnlyDoubleProperty does no harm. Yes, you\u0026rsquo;re giving/taking more than you need to, but that extra functionality is trivial and meaningless, so there\u0026rsquo;s really no added coupling to worry about. Nobody is ever going to call those two extra methods.\nSo, go with the flow and just use ReadOnly{Type}Property when you need something bind-able through the Fluent API\nAvoid Calling getValue() # Unless you want Number (and who does?), there\u0026rsquo;s no advantage to calling getValue() on any Observable object. Just use get() instead and you can largely ignore the differences between typed numeric Obsevables and the object versions.\nConclusion # When I started out in JavaFX I used IntegerProperty, DoubleProperty, StringProperty and BooleanProperty virtually exclusively. I never thought much about ObjectProperty\u0026lt;T\u0026gt; at all. Eventually I became aware of ObjectProperty\u0026lt;T\u0026gt; and it wasn\u0026rsquo;t clear to me how it was any better or worse than the typed Properties.\nAt some point I considered the typed Properties to be mostly convenience classes, that just made it a bit easier to declare stuff without all those angle brackets. Later, I started to encounter occasional issues stemming from IntegerProperty implementing Property\u0026lt;Number\u0026gt; and not Property\u0026lt;Integer\u0026gt;. At that point I started to view these as inconvenience classes. I never had the same issues with StringProperty or BooleanProperty, though.\nI\u0026rsquo;m not a big user of the Fluent API, and I went through a period when I didn\u0026rsquo;t really use the typed Properties at all. But there are still those times when the API forces you to contend with the typed Properties as most of the Node Properties are defined this way.\nHowever, through years of learning more about this, I\u0026rsquo;ve always used StringProperty and BooleanProperty over the generic Properties. This is because you don\u0026rsquo;t ever the encounter the issues created by Property\u0026lt;Number\u0026gt;.\nPresently I lean towards just using the typed Properties unless there is a good reason not to. Here\u0026rsquo;s why:\nPeople are used to them, even if they don\u0026rsquo;t understand them. The standard JavaFX Nodes are going to give them to you. 99% of the time, it just doesn\u0026rsquo;t matter\nYou\u0026rsquo;ll probably end up binding a DoubleProperty to a DoubleProperty anyway. It\u0026rsquo;s trivial to convert when you need it. The biggest remaining issue is defining parameters for your methods. If you just need an Observable value to bind to something else without using the Fluent API, then you don\u0026rsquo;t need anything more than ObseravbleValue\u0026lt;T\u0026gt;. If you define the parameter as ObservableValue\u0026lt;Double\u0026gt; then it won\u0026rsquo;t accept DoubleProperty. Can you get away with ObservableValue\u0026lt;Number\u0026gt;? If you can, then client code can pass you DoubleProperty. But if you really, really to need deal with it as ObservableValue\u0026lt;Double\u0026gt;, then the client code can pass you DoupbleProperty.asObject().\n","date":"22 August 2024","externalUrl":null,"permalink":"/javafx/elements/observable-classes-typed/","section":"JavaFX","summary":"This article is about the all of the typed observable classes and interfaces except the ones concerned with List.\nYou’ll find that all of the relationships and structure found in the generic classes is just about exactly the same, but then there are a few extra classes and interfaces added to the hierarchy depending on the type of data that they hold. We’ll concentrate mostly on two types, String and Integer here, because dealing with the others would just be redundant. String is typical of stand-alone types, and Integer is typical of types that are included in Number.\n","title":"Guide To the Observable Classes - Part II","type":"javafx"},{"content":" Introduction # This is Part II in a series about creating custom controls that starts with this article. In the first instalment, we looked at how to look at DRY as the gateway technique towards viewing everything in your layout as a custom control and then examined the basic techniques to create custom controls.\nThe final result of Part I was a custom control made by extending the standard Region class of JavaFX. This is useful because the method Region.getChildren() is protected, which means that we won\u0026rsquo;t be exposing the implementation of our control to the client code.\nIn this article, we\u0026rsquo;re going to look at how to complete the setup of our custom control to turn it into a true utility class that is virtually indistinguishable from the standard JavaFX Node classes, and can be used and styled in exactly the same ways.\nOur Example # For this tutorial we\u0026rsquo;re going to continue (from the previous article) to work with a control that looks like this:\nIt\u0026rsquo;s an HBox with a Label, a TextField and a Button in it. When the TextField has focus, then the Button becomes the default button, so you can just type in the TextField and hit to trigger the Button. When the TextField is empty, then the Button is disabled.\nThe Original Version - Extended From Region # In the first part of this series, we created a class that extends from Region, which means that the client code can\u0026rsquo;t mess with its implementation:\nclass CustomRegion(private val labelText: String, private val buttonText: String, private val boundProperty: StringProperty) : Region() { private var buttonAction: ButtonRunner = {} private val actionRunning: BooleanProperty = SimpleBooleanProperty(false) private val hBox: HBox init { hBox = createLayout() children.add(hBox) } private fun createLayout() = HBox().apply { val textField = TextField().apply { textProperty().bindBidirectional(boundProperty) } children += Label(labelText) children += textField children += Button(buttonText).apply { onAction = EventHandler\u0026lt;ActionEvent\u0026gt; { actionRunning.value = true buttonAction { actionRunning.value = false } } defaultButtonProperty().bind(textField.focusedProperty().and(textField.textProperty().isNotEmpty)) disableProperty().bind((textField.textProperty().isEmpty).or(actionRunning)) } alignment = Pos.CENTER_LEFT } fun setButtonAction(newAction: ButtonRunner) = this.apply { buttonAction = newAction } fun withSpacing(newSpacing: Double) = this.apply { hBox.spacing = newSpacing } } And it was called from this:\nclass RegionStyle : Application() { private val name: StringProperty = SimpleStringProperty(\u0026#34;\u0026#34;) private val results: StringProperty = SimpleStringProperty(\u0026#34;No search done\u0026#34;) override fun start(stage: Stage) { stage.scene = Scene(createContent()) stage.show() } private fun createContent(): Region = BorderPane().apply { center = CustomRegion(\u0026#34;Last Name:\u0026#34;, \u0026#34;Search\u0026#34;, name).setButtonAction { postRunAction -\u0026gt; doSearch() PauseTransition(Duration(3000.0)).apply { onFinished = EventHandler { postRunAction.run() } play() } }.withSpacing(50.0) bottom = Label().apply { textProperty().bind(results) } padding = Insets(20.0) } private fun doSearch() { results.value = \u0026#34;Nothing found for: ${name.value}\u0026#34; } } fun main() = Application.launch(RegionStyle::class.java) Adding More Client Options # This original version is okay, but the implementation is specific in its layout, and doesn\u0026rsquo;t really allow client code to adjust it for a wide range of applications. The first thing we\u0026rsquo;ll do is create some delegate properties to the component Node properties:\nclass InputActionWidget1(private val boundProperty: StringProperty, labelText: String = \u0026#34;Prompt:\u0026#34;, buttonText: String = \u0026#34;Start\u0026#34;) : Region() { private var buttonAction: ButtonRunner = {} private val actionRunning: BooleanProperty = SimpleBooleanProperty(false) private val hBox: HBox private val label = Label(labelText) private val textField = TextField() private val button = Button(buttonText) var labelText: String get() = label.text set(value) = run { label.text = value } val labelTextProperty: StringProperty get() = label.textProperty() var buttonText: String get() = button.text set(value) = run { button.text = value } val buttonTextProperty: StringProperty get() = button.textProperty() var textFieldText: String get() = textField.text set(value) = run { textField.text = value } val textFieldTextProperty: StringProperty get() = textField.textProperty() init { hBox = createLayout() children.add(hBox) } private fun createLayout() = HBox().apply { children += listOf(label, textField, button) textFieldTextProperty.bindBidirectional(boundProperty) configureButton() alignment = Pos.CENTER_LEFT } private fun configureButton() { button.apply { onAction = EventHandler\u0026lt;ActionEvent\u0026gt; { actionRunning.value = true buttonAction { actionRunning.value = false } } defaultButtonProperty().bind(textField.focusedProperty().and(textField.textProperty().isNotEmpty)) disableProperty().bind((textField.textProperty().isEmpty).or(actionRunning)) } } fun setButtonAction(newAction: ButtonRunner) = this.apply { buttonAction = newAction } fun withSpacing(newSpacing: Double) = this.apply { hBox.spacing = newSpacing } } Here\u0026rsquo;s what we\u0026rsquo;ve done:\nChanged the Name The original name, \u0026ldquo;CustomRegion\u0026rdquo; didn\u0026rsquo;t really say anything about what this control does. The new name \u0026ldquo;InputActionWidget\u0026rdquo; describes what it does a bit better. It takes some input, then performs an action on it. Delegate Fields for Node Text Properties The Button, Label and TextField all have a \u0026ldquo;Text\u0026rdquo; property. We\u0026rsquo;ve created delegate fields for all of these properties so that we can access them from the client code. Kotlin makes this approach really easy, but you can do the same thing with Java methods that just delegate to the Node properties. These all have unique names, like textFieldText, in our custom control. Default Constructor Values This is another Kotlin bonus, but you can do the same thing in Java by supplying additional constructors that take different parameters. In order to make this work cleanly, the only mandatory parameter, boundProperty has been moved to the front. Cleaned Up createLayout() Now that we have all of the Nodes instantiated as fields, it makes sense to clean up the layout code and move the Button configuration into its own method. This version is a bit more versatile than the original one.\nCleaning up the Layout # The layout of our custom class is a bit lame. Let\u0026rsquo;s try making the Button and the TextField look more like a single element.\nThe first thing we can do is to remove the space between them. This is done easiest by putting them in their own HBox and setting the spacing in that HBox to zero. We change this line:\nchildren += listOf(label, textField, button) to this:\nchildren += listOf(label, HBox(0.0, textField, button)) The result looks like this:\nAdding Styling # That doesn\u0026rsquo;t look too bad, but the Button and the TextField look more like they\u0026rsquo;re squished up together than attached to each other as a single thing. It\u0026rsquo;s the rounded corners that cause the problem. Let\u0026rsquo;s fix that.\nTo do this, we need to understand nested style classes in the CSS file. The first thing that we\u0026rsquo;re going to do is to add a style class for our custom region, we\u0026rsquo;ll call it \u0026ldquo;input-action-widget\u0026rdquo;. We\u0026rsquo;ll add a line to init{}:\ninit { styleClass += \u0026#34;input-action-widget\u0026#34; hBox = createLayout() children.add(hBox) } The styling for Buttons in Modena doesn\u0026rsquo;t have borders, but it has a set of backgrounds stacked on top of each other with different insets. The effect looks like a border with shadows and highlights, but it\u0026rsquo;s not actually a border.\nIn our StyleSheet, we\u0026rsquo;ll add this:\n.input-action-widget .button { -fx-background-radius: 0 7 7 0px, 0 7 7 0px, 0 6 6 0px, 0 5 5 0px; } This changes the styling for any Buttons in our custom class to be rounded on the right side, and square on the left. I\u0026rsquo;ve added a Button to the layout outside the custom class so you can see the difference:\nYou can see that the nested styling is working. We need to do the same for the TextField, making the right side have sharp corners.\nNow we need to have the Button have the same kind of focus highlighting that the TextField does but when the TextField has focus. We\u0026rsquo;re looking for this:\nThis is a little bit trickier to achieve. To do this, we need to create a new PseudoClass for the Button to be true when the TextField has focus. If you\u0026rsquo;re confused about PseudoClases, then you can look at this article which explains the concept. Next, we put a ChangeListener on the FocusProperty of the TextField and trigger a state change in the PseudoClass through it. Finally, we create a CSS entry for it.\nHere\u0026rsquo;s the code that does this:\ncompanion object PseudoClasses { val TEXTFIELD_FOCUSED: PseudoClass = PseudoClass.getPseudoClass(\u0026#34;textfield-focused\u0026#34;); } init { styleClass += \u0026#34;input-action-widget\u0026#34; hBox = createLayout() children.add(hBox) textField.focusedProperty().addListener { observable, oldValue, newValue -\u0026gt; button.pseudoClassStateChanged(TEXTFIELD_FOCUSED, newValue); } } A companion object is the Kotlin way to do something like static elements. It\u0026rsquo;s actual a nested singleton, but that\u0026rsquo;s not really important. Kotlin let\u0026rsquo;s you refer to its members without specifying the companion object. In the init{} block we connect the PseudoClass to textField.focusedProperty() with a ChangeListener. Now our PseudoClass will stay synchronized with the FocusedProperty of the TextField.\nHere\u0026rsquo;s the StyleSheet entry for the PseudoClass\n.input-action-widget .button:textfield-focused { -fx-background-color: -fx-focus-color, -fx-outer-border, -fx-inner-border, -fx-body-color; -fx-background-insets: -0.5, 0.7,1.3, 2; } This was figured out by looking at the Modena stylesheet and putting together the outside blue ring of TextField onto the inner colours of Button.\nIn the end, it now looks and acts very much like a single control.\nThe Code So Far # Here\u0026rsquo;s the entire code for InputActionWidget2 so far:\nclass InputActionWidget2(private val boundProperty: StringProperty, labelText: String = \u0026#34;Prompt:\u0026#34;, buttonText: String = \u0026#34;Start\u0026#34;) : Region() { private var buttonAction: ButtonRunner = {} private val actionRunning: BooleanProperty = SimpleBooleanProperty(false) private val label = Label(labelText) private val textField = TextField() private val button = Button(buttonText) var labelText: String get() = label.text set(value) = run { label.text = value } val labelTextProperty: StringProperty get() = label.textProperty() var buttonText: String get() = button.text set(value) = run { button.text = value } val buttonTextProperty: StringProperty get() = button.textProperty() var textFieldText: String get() = textField.text set(value) = run { textField.text = value } val textFieldTextProperty: StringProperty get() = textField.textProperty() companion object PseudoClasses { val TEXTFIELD_FOCUSED: PseudoClass = PseudoClass.getPseudoClass(\u0026#34;textfield-focused\u0026#34;); } init { styleClass += \u0026#34;input-action-widget\u0026#34; children.add(createLayout()) } private fun createLayout() = HBox(4.0).apply { children += listOf(label, HBox(0.0, textField, button)) textFieldTextProperty.bindBidirectional(boundProperty) configureButton() textField.focusedProperty().addListener { _, _, newValue -\u0026gt; button.pseudoClassStateChanged(TEXTFIELD_FOCUSED, newValue); } alignment = Pos.CENTER_LEFT } private fun configureButton() { button.apply { onAction = EventHandler\u0026lt;ActionEvent\u0026gt; { actionRunning.value = true buttonAction { actionRunning.value = false } } isFocusTraversable = false defaultButtonProperty().bind(textField.focusedProperty().and(textField.textProperty().isNotEmpty)) disableProperty().bind((textField.textProperty().isEmpty).or(actionRunning)) } } fun setButtonAction(newAction: ButtonRunner) = this.apply { buttonAction = newAction } } The only other change here is that the setSpacing() function didn\u0026rsquo;t seem needed any more, so it\u0026rsquo;s been removed.\nAnd here\u0026rsquo;s the complete set of StyleSheet entries for it:\n.input-action-widget .button { -fx-background-radius: 0 7 7 0px, 0 7 7 0px, 0 6 6 0px, 0 5 5 0px; } .input-action-widget .button:textfield-focused { -fx-background-color: -fx-focus-color, -fx-outer-border, -fx-inner-border, -fx-body-color; -fx-background-insets: -0.5, 0.7,1.3, 2; } .input-action-widget .text-input { -fx-background-radius: 3 0 0 3, 2 0 0 2; } Custom Styling Elements # Regular JavaFX Nodes allow you to do a lot of styling through either code or CSS. For instance that spacing in the HBox can be controlled through HBox.setSpacing() or by using the -fx-spacing tag in the style sheet. I tend to think of spacing as a coding thing more than a style element because there\u0026rsquo;s a version of the constructor for both HBox and VBox that let\u0026rsquo;s you specify the spacing. Otherwise, I usually lean towards the style sheet implementations for most styling.\nLet\u0026rsquo;s keep looking at that space item, and deal with it as a custom styling element.\nWe don\u0026rsquo;t want to expose the outer HBox of our layout (nor the inner one around the TextField and Button), because we don\u0026rsquo;t want client messing with that. So, let\u0026rsquo;s keep it hidden.\nBut how do we allow a stylesheet entry for the spacing?\nStyleable Properties # Have you ever wondered how that -fx-spacing line in the style sheet ends up changing the spacing in an HBox? Or how any style sheet attribute controls a Node property?\nClearly there\u0026rsquo;s some stuff going on deep inside the layout manager in JavaFX that we don\u0026rsquo;t want to look at, but all of the pieces that allow the authors of JavaFX to configure HBox to respond to -fx-spacing in the style sheet are available to us to create our own style sheet properties.\nOnce the layout manager has found a style sheet selector that applies to a Node is has to follow a process close to this list to apply a style sheet property from that selector to that Node on the screen:\nDetermine if the style sheet property is associated with the Node. Determine if the style sheet is allowed to update the Node. Identify the StyleableProperty associated with the style sheet property. Convert the value in the style sheet to something compatible with the StyleableProperty. Update the StyleableProperty with the converted value from the style sheet. This is done through two elements:\nStyleableProperty This is an interface that has one key method that we care about, applyStyle(). There\u0026rsquo;s a suit of SimpleStyleable{Type}Property classes that implement this interface, which also extend or implement all of the appropriate Observable classes and interfaces. CssMetaData This is the class that provides the connection between the style sheet and the StyleableProperty. It has all of the data and the methods to do virtually all of those steps listed above. If you look at the JavaDocs for StyleableProperty, StyleablePropertyFactory and CssMetaData your head is probably going to explode. It\u0026rsquo;s not that the concepts are hard to understand, but the explanations in the JavaDocs seem designed to make it nearly impossible to figure it out.\nAdding a \u0026ldquo;TagGap\u0026rdquo; Property # We\u0026rsquo;re going to look at how this works by implementing a fairly simple scenario, creating a custom stylesheet property to hold the size of the gap between the Label and the TextField in our custom widget. In reality, this is the spacing between the Label and the HBox holding the TextField and the Button. Also, in reality, these are the only two Nodes in the outer HBox so we can control this by changing the Spacing property of the outer HBox.\nThe StyleableProperty # The Spacing property is a Double, so we need a compatible StyleableProperty as a field in InputActionWidget:\nval tagGap: StyleableDoubleProperty = SimpleStyleableDoubleProperty(GAP_META_DATA, this, \u0026#34;tagGap\u0026#34;) Here, GAP_META_DATA is the CssMetaData that will be associated with this property, we haven\u0026rsquo;t defined it yet, but it\u0026rsquo;s next.\nThe next two constructor parameters are \u0026ldquo;Bean\u0026rdquo; stuff. I\u0026rsquo;m not sure how they are used, or if they are used. In this case, this is the InputActionWidget, and \u0026ldquo;tagGap\u0026rdquo; is it\u0026rsquo;s \u0026ldquo;Name\u0026rdquo;.\nThat\u0026rsquo;s it. Now we have a StyleableProperty!\nThe CssMetaData # This is where it get a little bit more complicated. Now, this is one place where Kotlin really, really makes everything super easy, because it has great tools for doing List stuff\u0026hellip;\ncompanion object CssStuff { val TEXTFIELD_FOCUSED: PseudoClass = PseudoClass.getPseudoClass(\u0026#34;textfield-focused\u0026#34;); val GAP_META_DATA: CssMetaData\u0026lt;InputActionWidget3, Number\u0026gt; = object : CssMetaData\u0026lt;InputActionWidget3, Number\u0026gt;(\u0026#34;-wfx-gap\u0026#34;, StyleConverter.getSizeConverter()) { override fun isSettable(styleable: InputActionWidget3) = !styleable.tagGap.isBound override fun getStyleableProperty(styleable: InputActionWidget3) = styleable.tagGap } private val cssMetaDataList = (Region.getClassCssMetaData() + GAP_META_DATA) as MutableList fun getClassCssMetaData() = cssMetaDataList } override fun getCssMetaData() = getClassCssMetaData() First, we have more than just Pseudo Class stuff in our companion object, so the name has been changed to something more appropriate.\nWe create GAP_META_DATA as a (essentially static) constant of an anonymous inner class extending CssMetaData. That\u0026rsquo;s a mouthful but it\u0026rsquo;s actually really simple.\nThe = object : AbstractClass(){} structure is how an anonymous inner class is made in Kotlin. It essentially says, \u0026ldquo;\u0026hellip; is an object which is an extension of the CssMetaData class\u0026rdquo;. We call the abstract class constructor, then extend it with the stuff in the {}. In this case, we\u0026rsquo;re going to provide implementations for the two abstract methods: isSettable() and getStyleableProperty().\nThe constructor sets the name of the property in the style sheet, it\u0026rsquo;s going to be \u0026ldquo;-wfx-gap\u0026rdquo; (\u0026ldquo;wfx\u0026rdquo; for WidgetsFX and to avoid collisions with the \u0026ldquo;-fx\u0026rdquo; namespace). The second constructor parameter specifies the converter to use to translate the value from the String in the style sheet to a data value in the StyleableProperty. In this case, we\u0026rsquo;re using the standard converter for sizes, which should deal with all the possible ways that it could be expressed in the style sheet - like \u0026ldquo;px\u0026rdquo; or \u0026ldquo;em\u0026rdquo;.\nNext, we have the implementations of the two abstract methods. Our tagGap property is exposed to the client code, so it could be bound to something, which would cause an error if the layout manager attempts to set it.\nThe last implementation is the \u0026ldquo;getter\u0026rdquo; for the property in the CSS metadata. It just returns our property.\nThe last piece is publishing our metadata to the Layout Manager. This is where Kotlin really shines because of the List handling stuff:\nStep 1 is to effectively inherit the metadata from the class that we extended, which is Region. This is a List of some sort. Then we add our metadata with the \u0026ldquo;+\u0026rdquo; operator, generating a new List and we cast it to MutableList because that maintains compatibility with the Java based functions (Kotlin defaults to immutable Lists).\nStep 2 is to override the InputActionWidget.ClassMetaData() function to return our new List. Providing this static function means that if we extend InputActionWidget, the subclass will also inherit our gap metadata.\nStep 3 is to override the member function, getCssMetaData() to delegate to the static method we just created. This is the method that is actually going to be called by the Layout Manager.\nAnd that\u0026rsquo;s it.\nIn Java # This is sufficiently different in Kotlin that it\u0026rsquo;s worth looking at the version from the JavaDocs, which is, of course, in Java:\nprivate static final CssMetaData GAP_META_DATA = new CssMetaData\u0026lt;MyWidget,Number\u0026gt;(\u0026#34;-my-gap\u0026#34;, StyleConverter.getSizeConverter(), 0d) { @Override public boolean isSettable(MyWidget node) { return node.gapProperty == null || !node.gapProperty.isBound(); } @Override public StyleableProperty\u0026lt;Number\u0026gt; getStyleableProperty(MyWidget node) { return (StyleableProperty\u0026lt;Number\u0026gt;)node.gapProperty; } }; private static final List\u0026lt;CssMetaData\u0026lt;? extends Node, ?\u0026gt;\u0026gt; cssMetaDataList; static { List\u0026lt;CssMetaData\u0026lt;? extends Node, ?\u0026gt;\u0026gt; temp = new ArrayList\u0026lt;CssMetaData\u0026lt;? extends Node, ?\u0026gt;\u0026gt;(Region.getClassCssMetaData()); temp.add(GAP_META_DATA); cssMetaDataList = Collections.unmodifiableList(temp); } public static List\u0026lt;CssMetaData\u0026lt;? extends Node, ?\u0026gt;\u0026gt; getClassCssMetaData() { return cssMetaDataList; } @Override public List\u0026lt;CssMetaData\u0026lt;? extends Node, ?\u0026gt;\u0026gt; getCssMetaData() { return getClassCssMetaData(); } One of the reasons that the Java looks more complicated is that all of the generic types of the Lists and methods need to be explicitly declared, while in Kotlin they can be inferred. You can see how the List handling is a lot simpler in Kotlin, too.\nThe StyleSheet # Here\u0026rsquo;s the selector for the InputActionWidget with the gap specified:\n.input-action-widget { -wfx-gap: 8.0; } And that\u0026rsquo;s all it takes. It\u0026rsquo;s not really that complicated, especially when you understand how the mechanism works, and how the steps involved set up the data.\nAdding a Dynamic Layout # One of the reasons that we didn\u0026rsquo;t want to expose the outer HBox in our layout was because it adds a dependency to the implementation of our layout. What if we wanted to give the client code the ability to style the widget with the Label either beside or above the combined TextField/Button?\nThe way this component is designed, we can achieve this by simply swapping out the outer HBox with a VBox. You can\u0026rsquo;t really just swap it though, you have to create new container, configure it and populate it with the other components. Then put the new container in as the only child of the outer Region.\nConveniently, JavaFX has an Enum type called Orientation which has the two values, \u0026ldquo;HORIZONTAL\u0026rdquo; and \u0026ldquo;VERTICAL\u0026rdquo;, and we can use this for out StyleableProperty. Let\u0026rsquo;s take a look at the code we need to implement that:\nval orientation: StyleableObjectProperty\u0026lt;Orientation\u0026gt; = SimpleStyleableObjectProperty(ORIENTATION_META_DATA, this, \u0026#34;orientation\u0026#34;, Orientation.HORIZONTAL) companion object CssStuff { val TEXTFIELD_FOCUSED: PseudoClass = PseudoClass.getPseudoClass(\u0026#34;textfield-focused\u0026#34;); val GAP_META_DATA: CssMetaData\u0026lt;InputActionWidget4, Number\u0026gt; = object : CssMetaData\u0026lt;InputActionWidget4, Number\u0026gt;(\u0026#34;-wfx-gap\u0026#34;, StyleConverter.getSizeConverter(), 6.0) { override fun isSettable(styleable: InputActionWidget4) = !styleable.tagGap.isBound override fun getStyleableProperty(styleable: InputActionWidget4) = styleable.tagGap } val ORIENTATION_META_DATA: CssMetaData\u0026lt;InputActionWidget4, Orientation\u0026gt; = object : CssMetaData\u0026lt;InputActionWidget4, Orientation\u0026gt;(\u0026#34;-wfx-orientation\u0026#34;, EnumConverter(Orientation::class.java)) { override fun isSettable(styleable: InputActionWidget4) = !styleable.orientation.isBound override fun getStyleableProperty(styleable: InputActionWidget4) = styleable.orientation } private val cssMetaDataList = (Region.getClassCssMetaData() + GAP_META_DATA + ORIENTATION_META_DATA) as MutableList fun getClassCssMetaData() = cssMetaDataList } That\u0026rsquo;s all it takes. We just add the StyleableProperty as a field, then add the metadata in the companion object and add it to the List along with the gap metadata. Notice that we\u0026rsquo;re using EnumConverter, which is easy enough to find if you poke around in the JavaDocs.\nThe next thing is to incorporate the new property into the layout build:\nprivate fun createLayout() { children.clear() children += if (orientation.value == Orientation.HORIZONTAL) createHBox() else createVBox() } private fun createHBox() = HBox(4.0).apply { children += listOf(label, HBox(0.0, textField, button)) spacingProperty().bind(tagGap) alignment = Pos.CENTER_LEFT minWidth = 200.0 } private fun createVBox() = VBox(4.0).apply { children += listOf(label, HBox(0.0, textField, button)) spacingProperty().bind(tagGap) alignment = Pos.CENTER_LEFT } The configuration of the individual components was separated out from the layout code to keep it cleaner.\nSince the layout build is an \u0026ldquo;action\u0026rdquo;, and orientation is an observable value, we need to trigger the action from a change in the observable value. This means adding a Listener to orientation:\ninit { styleClass += \u0026#34;input-action-widget\u0026#34; configureComponents() createLayout() orientation.addListener(InvalidationListener { createLayout() }) } We can get away with an InvalidationListener because createLayout calls the getter for the value of orientation.\nThis is controlled via the style sheet entry for the widget:\n.input-action-widget { -wfx-gap: 8.0; -wfx-orientation: vertical; } And it looks like this when it\u0026rsquo;s set to vertical:\nThe Label felt like it needed a little bit of styling to make it look more like an integrated part of the widget. So it was made bigger, bold and coloured in the same tone as the focus colour for the style sheet using this entry:\n.input-action-widget .label { -fx-text-fill: derive(-fx-focus-color, -30%); -fx-font-size: 14px; -fx-font-weight: bold } Exposing the Styleable Properties to Client Code # If we want our custom class to work the same way as any other JavaFX Node, then we should give the client code the ability to manipulate the StyleableProperty directly. And this means we should present it in the Bean format that all of the other Node properties use. We just need the following change (here both the StyleableProperties have been updated):\nfun tagGapProperty() = tagGapImpl val tagGapImpl: StyleableDoubleProperty = SimpleStyleableDoubleProperty(GAP_META_DATA, this, \u0026#34;tagGap\u0026#34;) var tagGap: Double get() = tagGapImpl.value set(value) = tagGapImpl.set(value) fun orientationPropery() = orientationImpl val orientationImpl: StyleableObjectProperty\u0026lt;Orientation\u0026gt; = SimpleStyleableObjectProperty(ORIENTATION_META_DATA, this, \u0026#34;orientation\u0026#34;, Orientation.HORIZONTAL) var orientation: Orientation get() = orientationImpl.get() set(value) = orientationImpl.set(value) All we\u0026rsquo;ve done here is change the name of the actual properties to tagGapImpl and orientationImpl and make them private, then set up the usual JavaFX Bean structure for the client code. Now setOrientation(), getOrientation() and orientationProperty() all work as they should from Java code. The same for tagGap.\nFinal Code # That\u0026rsquo;s it! Here\u0026rsquo;s a look at the complete code for our custom InputActionWidget:\nclass InputActionWidget4(private val boundProperty: StringProperty, labelText: String = \u0026#34;Prompt:\u0026#34;, buttonText: String = \u0026#34;Start\u0026#34;) : Region() { private var buttonAction: ButtonRunner = {} private val actionRunning: BooleanProperty = SimpleBooleanProperty(false) private val label = Label(labelText) private val textField = TextField() private val button = Button(buttonText) fun labelTextProperty() = label.textProperty() var labelText: String get() = label.text set(value) { label.text = value } fun buttonTextProperty() = button.textProperty() var buttonText: String get() = button.text set(value) { button.text = value } fun textFieldTextProperty() = textField.textProperty() var textFieldText: String get() = textField.text set(value) { textField.text = value } fun textFormatterProperty() = textField.textFormatterProperty() var textFormatter: TextFormatter\u0026lt;*\u0026gt;? get() = textField.textFormatter set(value) { textField.textFormatter = value } fun tagGapProperty() = tagGapImpl private val tagGapImpl: StyleableDoubleProperty = SimpleStyleableDoubleProperty(GAP_META_DATA, this, \u0026#34;tagGap\u0026#34;) var tagGap: Double get() = tagGapImpl.value set(value) = tagGapImpl.set(value) fun orientationPropery() = orientationImpl private val orientationImpl: StyleableObjectProperty\u0026lt;Orientation\u0026gt; = SimpleStyleableObjectProperty(ORIENTATION_META_DATA, this, \u0026#34;orientation\u0026#34;, Orientation.HORIZONTAL) var orientation: Orientation get() = orientationImpl.get() set(value) = orientationImpl.set(value) companion object CssStuff { val TEXTFIELD_FOCUSED: PseudoClass = PseudoClass.getPseudoClass(\u0026#34;textfield-focused\u0026#34;); val GAP_META_DATA: CssMetaData\u0026lt;InputActionWidget4, Number\u0026gt; = object : CssMetaData\u0026lt;InputActionWidget4, Number\u0026gt;(\u0026#34;-wfx-gap\u0026#34;, StyleConverter.getSizeConverter(), 6.0) { override fun isSettable(styleable: InputActionWidget4) = !styleable.tagGapImpl.isBound override fun getStyleableProperty(styleable: InputActionWidget4) = styleable.tagGapImpl } val ORIENTATION_META_DATA: CssMetaData\u0026lt;InputActionWidget4, Orientation\u0026gt; = object : CssMetaData\u0026lt;InputActionWidget4, Orientation\u0026gt;(\u0026#34;-wfx-orientation\u0026#34;, EnumConverter(Orientation::class.java)) { override fun isSettable(styleable: InputActionWidget4) = !styleable.orientationImpl.isBound override fun getStyleableProperty(styleable: InputActionWidget4) = styleable.orientationImpl } private val cssMetaDataList = (Region.getClassCssMetaData() + GAP_META_DATA + ORIENTATION_META_DATA) as MutableList fun getClassCssMetaData() = cssMetaDataList } override fun getCssMetaData() = getClassCssMetaData() init { styleClass += \u0026#34;input-action-widget\u0026#34; configureComponents() createLayout() orientationImpl.addListener(InvalidationListener { createLayout() }) } private fun configureComponents() { textFieldTextProperty().bindBidirectional(boundProperty) configureButton() textField.focusedProperty().addListener { _, _, newValue -\u0026gt; button.pseudoClassStateChanged(TEXTFIELD_FOCUSED, newValue); } } private fun createLayout() { children.clear() children += if (orientationImpl.value == Orientation.HORIZONTAL) createHBox() else createVBox() } private fun createHBox() = HBox(4.0).apply { children += listOf(label, HBox(0.0, textField, button)) spacingProperty().bind(tagGapImpl) alignment = Pos.CENTER_LEFT minWidth = 200.0 } private fun createVBox() = VBox(4.0).apply { children += listOf(label, HBox(0.0, textField, button)) spacingProperty().bind(tagGapImpl) alignment = Pos.CENTER_LEFT } private fun configureButton() { button.apply { onAction = EventHandler\u0026lt;ActionEvent\u0026gt; { actionRunning.value = true buttonAction { actionRunning.value = false } } isFocusTraversable = false defaultButtonProperty().bind(textField.focusedProperty().and(textField.textProperty().isNotEmpty)) disableProperty().bind((textField.textProperty().isEmpty).or(actionRunning)) } } fun setButtonAction(newAction: ButtonRunner) = this.apply { buttonAction = newAction } } The only extra thing in this is that we now have some code to provide access to the TextFormatter property of the TextField.\nHere\u0026rsquo;s the complete set of style sheet entries for this class:\n.input-action-widget { -wfx-gap: 8.0; -wfx-orientation: horizontal; } .input-action-widget .button { -fx-background-radius: 0 7 7 0px, 0 7 7 0px, 0 6 6 0px, 0 5 5 0px; } .input-action-widget .button:textfield-focused { -fx-background-color: -fx-focus-color, -fx-outer-border, -fx-inner-border, -fx-body-color; -fx-background-insets: -0.5, 0.7,1.3, 2; } .input-action-widget .text-input { -fx-background-radius: 3 0 0 3, 2 0 0 2; } .input-action-widget .label { -fx-text-fill: derive(-fx-focus-color, -30%); -fx-font-size: 14px; -fx-font-weight: bold } Conclusion # By my count, this is about 120 lines of code to implement a complete custom control that can be utilized in almost exactly the same as any other standard JavaFX Node. The only thing that\u0026rsquo;s missing is some JavaDocs and you could publish it in a library.\nThe important thing to learn from all of this is that there\u0026rsquo;s really nothing magic or mysterious about creating your own custom Node classes. When you look at it, it\u0026rsquo;s just a simple layout wrapped up in a class to control access to its properties and styling. It\u0026rsquo;s easy to do, and if you have patterns that you use repeatedly in your layouts should really be thinking about implementing them as custom controls.\nI think the only thing involved in this that\u0026rsquo;s a bit challenging to understand are the StyleableProperties and the CssMetaData structure. But even here, it\u0026rsquo;s mostly difficult because the standard documentation is appalling.\n","date":"15 March 2023","externalUrl":null,"permalink":"/javafx/techniques/customizing_custom_controls/","section":"JavaFX","summary":"Once you’ve decided extend Region to create a custom control, there are some basic things you should do to make your new control look professional and unified.  Here’s how to do that.","title":"JavaFX: How to Customize Custom Controls","type":"javafx"},{"content":" Using Sprites # Sprites are an old concept. I remember that Commodore 64's and the Atari 800's in the early 1980's had the concept of Sprites included in their operating systems. A Sprite is just stand-alone graphic element that can be independently manipulated within a larger image environment. In the context of this tutorial, we're going to concentrate on the idea of a Sprite as an image container with a set of related images which are displayed within that container. By rapidly changing the image displayed in the container, we can give the illusion of motion. Single Image Sprites # Nowadays, and especially where CSS and web pages are concerned, a Sprite is usually a single image which contains a number of related images. For web design, this is a good technique as it avoids loading multiple images and improves page performance.\nFor JavaFX, it can also handy. As an example, let\u0026rsquo;s look at a single image file containing a number of images for a running troll:\nThis is one of the trolls from the CraftPix.net free troll series. I think they\u0026rsquo;re pretty cool, and the images are actually huge, with lots of details. The guy at the top of the page is another one of them.\nIt\u0026rsquo;s important to note that your individual images all have to be the same size, and are positioned identically relative to each other, and regularly spaced out.\nTo start with, let\u0026rsquo;s just get the Image on the screen in an ImageView, then we can move on from there:\npublic class SpriteDemo1 extends Application { @Override public void start(Stage primaryStage) throws Exception { primaryStage.setScene(new Scene(createContent())); primaryStage.show(); } private Region createContent() { BorderPane results = new BorderPane(); ImageView sprite = new ImageView(this.getClass().getResource(\u0026#34;Troll.png\u0026#34;).toExternalForm()); results.setCenter(sprite); return results; } } Which yields this: Okay, now we have the image loaded properly. It\u0026rsquo;s always a good idea to start off super simple, that way you know you don\u0026rsquo;t have problems locating the resources or anything else. Now, let\u0026rsquo;s use it as a Sprite.\npublic class SpriteDemo2 extends Application { @Override public void start(Stage primaryStage) throws Exception { primaryStage.setScene(new Scene(createContent())); primaryStage.show(); } private Region createContent() { ObjectProperty\u0026lt;Integer\u0026gt; indexProperty = new SimpleObjectProperty\u0026lt;\u0026gt;(1); BorderPane results = new BorderPane(); results.setMinWidth(500); results.setMinHeight(400); ImageView sprite = new ImageView(this.getClass().getResource(\u0026#34;Troll.png\u0026#34;).toExternalForm()); Button button = new Button(\u0026#34;Click Me!\u0026#34;); results.setCenter(sprite); results.setBottom(button); button.setOnAction(evt -\u0026gt; { int currentValue = indexProperty.get(); indexProperty.set((currentValue \u0026lt; 9) ? currentValue + 1 : 1); }); Rectangle2D viewPort = new Rectangle2D(0, 0, 100, 140); sprite.setViewport(viewPort); indexProperty.addListener(observable -\u0026gt; { int column = indexProperty.get() % 5; if (column == 0) { column = 5; } int xOffset = (column - 1) * 100; int yOffset = (indexProperty.get() / 5) * 150; sprite.setViewport(new Rectangle2D(xOffset, yOffset, 100, 140)); }); return results; } } The individual troll images occupy a 100 x 140 pixel space, so that\u0026rsquo;s the size of or ViewPort rectangle.\nHere we\u0026rsquo;ve added on ObjectProperty\u0026lt;Integer\u0026gt; to hold the index of the current troll image. The Button \u0026ldquo;click\u0026rdquo; action just increments the value from 1 to 10 and then back around to start over.\nThere\u0026rsquo;s an InvalidationListener on the Property, which handles the display swapping inside the Sprite. Since there are two rows of trolls, there\u0026rsquo;s a little bit of calculation to turn a single index number into a row and column. There\u0026rsquo;s also a 10 pixel gap between the rows, so the vertical offset needs to be a bit bigger than the image height.\nWhich gives this:\nHow This Works # Now the ImageView has a ViewPort set on it. Essentially, this is rectangular area of the Image in the ImageView which will be displayed on the screen.\nAnd then we move the origin of the ViewPort rectangle over 1 troll to the right, like this:\nSince the ViewPort is locked onto the screen in a single position, it looks to the user like the Image behind it is snapping around to put a new troll under the window.\nAnimating the Sprite # Animating the Sprite is just a matter of automating the changing of the index value in the Property. The InvalidationListener doesn\u0026rsquo;t need to be changed at all, although it has been split off into its own method to keep the layout code looking clean.\nInstead of using Button clicks to trigger the change to the index value, we\u0026rsquo;re going to use an Animation, which is the JavaFX tool for automating things like this.\nThe Animation that we\u0026rsquo;re using is a Transition. What Transition is going to do is to increment a Double counter from 0.0 to 1.0 over the time span of a cycle. In this case the cycle is going to be 1,200 ms, or 1.2 s. Periodically, it\u0026rsquo;s going to fire the interpolate() method, passing the current value of the counter to it. We don\u0026rsquo;t know how often it\u0026rsquo;s going to fire, but we can be fairly sure that it\u0026rsquo;s going to be many, many times during the cycle.\nWe need our Transition to turn these calls to interpolate() into a set of discrete changes to the ViewPort of our Sprite, evenly spaced out over the cycle. To do this, we multiple the Double counter by the number of trolls inside the Sprite, and then take the integer value (which rounds down). Since we have 10 trolls in the Sprite, this will yield an integer between 0 and 9, which we shift over to 1 -10 by adding 1.\nEven if the interpolate() method is called 100 times before the index moves from 1 to 2, this won\u0026rsquo;t trigger the InvalidationListener until it actually changes to 2. And so on. This turns the interpolate() calls into discrete events.\npublic class SpriteDemo3 extends Application { @Override public void start(Stage primaryStage) throws Exception { primaryStage.setScene(new Scene(createContent())); primaryStage.show(); } private Region createContent() { ObjectProperty\u0026lt;Integer\u0026gt; indexProperty = new SimpleObjectProperty\u0026lt;\u0026gt;(1); BorderPane results = new BorderPane(); results.setMinWidth(500); results.setMinHeight(400); ImageView sprite = new ImageView(this.getClass().getResource(\u0026#34;Troll.png\u0026#34;).toExternalForm()); results.setCenter(sprite); setViewPort(sprite, 1); indexProperty.addListener(observable -\u0026gt; setViewPort(sprite, indexProperty.get())); createAnimation(indexProperty).play(); return results; } private void setViewPort(ImageView sprite, int value) { int column = value % 5; if (column == 0) { column = 5; } int xOffset = (column - 1) * 100; int yOffset = (value \u0026lt;= 5) ? 0 : 155; sprite.setViewport(new Rectangle2D(xOffset, yOffset, 100, 140)); } private Animation createAnimation(ObjectProperty\u0026lt;Integer\u0026gt; indexProperty) { Animation animation = new Transition() { private int oldValue = 99; { setCycleDuration(Duration.millis(1200)); setInterpolator(Interpolator.LINEAR); } protected void interpolate(double frac) { int value = ((int) (frac * 10)) + 1; indexProperty.set(value); } }; animation.setCycleCount(Animation.INDEFINITE); return animation; } } A key thing to notice here is that you need to use Linear interpolation, not the default interpolation. Otherwise, he speeds up and slows down as it cycles, which is annoying (and took me a while to figure out).\nScrolling Background # Now we\u0026rsquo;re going to add a scrolling background to the application. The background will be this image:\nThe important thing about this image is that the extreme right edge of the image is identical to the left edge, which means that we can paste a copy of it at either side and it will look like a continuous scene. Even more importantly, the second half will be identical to the first half (obviously).\nNow the technique is very similar to the one with the Sprites. We\u0026rsquo;ll create a rectangular ViewPort, and then move it across the image, a few pixels at a time. Just like with the Sprites, since the ViewPort is fixed on the screen, it will look like the image is moving through the ViewPort, not the other way around.\nInfinite Scrolling # In order to handle the infinite scrolling aspect, we need to reset the offset every time the ViewPort is fully into the second half of the image:\nHere there\u0026rsquo;s a big line showing the join between the images, and you can see how the part of image inside the blue ViewPort is identical to the content shown in the pink ViewPort. So you can flip between the two of them and the user wouldn\u0026rsquo;t see any difference. This means that we can vary the X offset between 0 and the width of one of the images, resetting it back to zero each time, and it will look like a continuous scroll.\nIn reality, it would never get that far over to the right, you\u0026rsquo;ll see\u0026hellip;\nHere\u0026rsquo;s the code:\npublic class SpriteDemo4 extends Application { @Override public void start(Stage primaryStage) throws Exception { primaryStage.setScene(new Scene(createContent())); primaryStage.show(); } private Region createContent() { ObjectProperty\u0026lt;Integer\u0026gt; indexProperty = new SimpleObjectProperty\u0026lt;\u0026gt;(1); ObjectProperty\u0026lt;Integer\u0026gt; backgroundOffsetProperty = new SimpleObjectProperty\u0026lt;\u0026gt;(0); ImageView sprite = new ImageView(this.getClass().getResource(\u0026#34;Troll.png\u0026#34;).toExternalForm()); ImageView background = new ImageView(this.getClass().getResource(\u0026#34;Background.png\u0026#34;).toExternalForm()); background.setViewport(new Rectangle2D(0, 0, 400, 238)); setViewPort(sprite, 1); sprite.setTranslateY(50); indexProperty.addListener(observable -\u0026gt; setViewPort(sprite, indexProperty.get())); backgroundOffsetProperty.addListener(observable -\u0026gt; { background.setViewport(new Rectangle2D(backgroundOffsetProperty.get(), 0, 400, 238)); }); createSpriteAnimation(indexProperty).play(); createBackgroundAnimation(backgroundOffsetProperty).play(); StackPane results = new StackPane(background, sprite); results.setMinWidth(500); results.setMinHeight(400); return results; } private void setViewPort(ImageView sprite, int value) { int column = value % 5; if (column == 0) { column = 5; } int xOffset = (column - 1) * 100; int yOffset = (value \u0026lt;= 5) ? 0 : 155; sprite.setViewport(new Rectangle2D(xOffset, yOffset, 100, 140)); } private Animation createSpriteAnimation(ObjectProperty\u0026lt;Integer\u0026gt; indexProperty) { Animation animation = new Transition() { private int oldValue = 99; { setCycleDuration(Duration.millis(900)); setInterpolator(Interpolator.LINEAR); } protected void interpolate(double frac) { int value = ((int) (frac * 10)) + 1; if (value != oldValue) { if (value == 11) { value = 1; } oldValue = value; indexProperty.set(value); } } }; animation.setCycleCount(Animation.INDEFINITE); return animation; } private Animation createBackgroundAnimation(ObjectProperty\u0026lt;Integer\u0026gt; backgroundOffsetProperty) { Animation animation = new Transition() { { setCycleDuration(Duration.millis(36000)); setInterpolator(Interpolator.LINEAR); } protected void interpolate(double frac) { int backgroundOffset = (int) ((frac * 6350)) % 635; backgroundOffsetProperty.set(backgroundOffset); } }; animation.setCycleCount(Animation.INDEFINITE); return animation; } } This is very similar to the previous example, but we\u0026rsquo;ve swapped out the BorderPane for a StackPane since we want our running troll to be on top of the scrolling background. We also had to apply a vertical translation to the troll so that his feet lined up with the grass at the bottom, so he didn\u0026rsquo;t look like he was running in the air.\nAll of the scrolling is done be manipulating the offset Property in the Animation. The 0.0 -\u0026gt; 1.0 frac is multiplied by an even multiple of the single image width (635 pixels), and then the modulo when divided by the single image width is taken as the new offset. In this case, the multiplier is 6350, which means that each complete cycle of the Transition will go across the image 10 times. In needs to be an even multiplier of the width so that the cycle ends at the right edge of the single image and the next cycle - starting at the left edge - will be a smooth transition.\nThe only trick here is getting the cycle times right for both Animations, so that the troll looks like he\u0026rsquo;s running at the same speed as the background is moving. Otherwise he looks like he\u0026rsquo;s sliding.\nNow it looks like this:\nNow let\u0026rsquo;s look at Sprites again\u0026hellip;\nMultiple Image Sprites # When I started out writing this tutorial, I was under the impression that most Sprites were packaged up into single image files the way I\u0026rsquo;ve shown above. But when I started looking for good example Sprites to use, I found that many of them are packaged up as individual images.\nIn fact, this is the way that the trolls were packaged up - a Zip file with individual images. I wasn\u0026rsquo;t going to use them at first, but they are just too awesome not to use. So I ended up creating a composite image Sprite file myself to show how that works.\nBut now, let\u0026rsquo;s look at how to use individual files. There were three different trolls in the Zip file, so let\u0026rsquo;s use a different troll, but we\u0026rsquo;ll stick to the \u0026ldquo;running\u0026rdquo; Sprites.\nThe Technique # In many ways, this is simpler than the single image file Sprite. There are ten images, all named \u0026ldquo;Run_00{x}.png\u0026rdquo;, where the \u0026ldquo;x\u0026rdquo; is an index. So we load them into a Queue. At this point we aren\u0026rsquo;t even really interested in the numbering of the images at all. They are just a sequence that we\u0026rsquo;ll rotate through. To keep the Queue going, we add each Image that we remove from the ImageView to the end of the Queue, then load the one from the front into the ImageView.\nI\u0026rsquo;ve kept the index Property, in part just to keep the separation between the layout and the animation control. But the Listener doesn\u0026rsquo;t even look at the value of the Property. There is a call to get(), but it\u0026rsquo;s just there to revalidate the Property. However, the mechanics of the index Property still convert the continuum of values for frac into 10 discrete, fairly evenly spaced out, events.\npublic class SpriteDemo5 extends Application { @Override public void start(Stage primaryStage) throws Exception { primaryStage.setScene(new Scene(createContent())); primaryStage.show(); } private Region createContent() { ObjectProperty\u0026lt;Integer\u0026gt; indexProperty = new SimpleObjectProperty\u0026lt;\u0026gt;(1); ObjectProperty\u0026lt;Integer\u0026gt; backgroundOffsetProperty = new SimpleObjectProperty\u0026lt;\u0026gt;(0); Queue\u0026lt;Image\u0026gt; imageQueue = getImages(); ImageView sprite = new ImageView(imageQueue.remove()); ImageView background = new ImageView(this.getClass().getResource(\u0026#34;Background.png\u0026#34;).toExternalForm()); background.setViewport(new Rectangle2D(0, 0, 400, 238)); sprite.setTranslateY(18); indexProperty.addListener(observable -\u0026gt; { indexProperty.get(); changeTrollImage(sprite, imageQueue); }); backgroundOffsetProperty.addListener(observable -\u0026gt; { background.setViewport(new Rectangle2D(backgroundOffsetProperty.get(), 0, 400, 238)); }); createSpriteAnimation(indexProperty).play(); createBackgroundAnimation(backgroundOffsetProperty).play(); StackPane results = new StackPane(background, sprite); results.setMinWidth(500); results.setMinHeight(400); return results; } private Queue\u0026lt;Image\u0026gt; getImages() { Queue\u0026lt;Image\u0026gt; imageQueue = new ArrayDeque\u0026lt;\u0026gt;(); for (int x = 0; x \u0026lt; 10; x++) { URL url = this.getClass().getResource(\u0026#34;Run_00\u0026#34; + x + \u0026#34;.png\u0026#34;); imageQueue.add(new Image(url.toExternalForm(), 160.0, 0.0, true, true)); } return imageQueue; } private void changeTrollImage(ImageView sprite, Queue\u0026lt;Image\u0026gt; imageQueue) { imageQueue.add(sprite.getImage()); sprite.setImage(imageQueue.remove()); } private Animation createSpriteAnimation(ObjectProperty\u0026lt;Integer\u0026gt; indexProperty) { Animation animation = new Transition() { { setCycleDuration(Duration.millis(900)); setInterpolator(Interpolator.LINEAR); } protected void interpolate(double frac) { int value = ((int) (frac * 10)); indexProperty.set(value); } }; animation.setCycleCount(Animation.INDEFINITE); return animation; } private Animation createBackgroundAnimation(ObjectProperty\u0026lt;Integer\u0026gt; backgroundOffsetProperty) { Animation animation = new Transition() { { setCycleDuration(Duration.millis(36000)); setInterpolator(Interpolator.LINEAR); } protected void interpolate(double frac) { int backgroundOffset = (int) ((frac * 6350)) % 635; backgroundOffsetProperty.set(backgroundOffset); } }; animation.setCycleCount(Animation.INDEFINITE); return animation; } } One thing I did was to scale the Images to the correct size, rather than have the ImageView scale them for display. This might make some difference in performance, but it\u0026rsquo;s hard to tell with such a simple example. In a busy screen with many Sprites, it could become important.\nI also scaled this troll a bit bigger than the last one. So his vertical positioning needed adjustment.\nIt looks like this:\nConclusion # I hope that you can see that both animated Sprites and scrolling backgrounds are fairly easy to implement in JavaFX.\nMost Sprites are bundled up with a variety of different action sequences. The trolls have walking, jumping, dying, running, attacking and idle. Some others might have \u0026ldquo;firing\u0026rdquo;, or \u0026ldquo;exploding\u0026rdquo;. I can see a strong case for packaging everything up into a single class that extends ImageView, and having methods to start up different animations. So you could have a class called Troll, and then call troll.run(), or troll.attack() to trigger the appropriate animation. That would allow you to have different kinds of trolls on the screen and your layout code wouldn\u0026rsquo;t contain any of the mechanics about the animation - which would be very clean.\n","date":"10 April 2022","externalUrl":null,"permalink":"/javafx/elements/sprites/","section":"JavaFX","summary":"How to handle image animation with Sprites and scrolling backgrounds","title":"Sprites and Scrolling Images","type":"javafx"},{"content":" What is a Button? # A Button is the basic control to allow the user trigger an action in a screen. JavaFX has two Button classes; Button and ToggleButton. In this article, we\u0026rsquo;re going to look at the Button class itself.\nHow Button Fits in the JavaFX Hierarchy # Button inherits methods and properties from these important classes:\nNode Everything in JavaFX inherits from Node, which gives most of its methods for styling, event handling and responding to mouse actions as well as location and transformation. Control This is the base class for user interaction controls on a screen. This base class adds skinning, tool-tips and context menus to Node. Region All Control classes are Regions, which means that they can contain other Nodes and have layout properties like padding and borders. Labeled In JavaFX, the Label class contains both a graphical Node and a text property, and the Labeled classes also support this same idea as the contents of the Control as a Region. The version 16 JavaDocs for Button can be found here\nLayout # You can think of a Button as styled Region with a background and a border containing a Label (which, of course, contains a Graphic and/or some text). All of the button-like feel of a Button is just the styling responding to different states of the properties in the class.\nThese states change when:\nThe Button is disabled. Gains or loses focus. Becomes or stops being default. When the cursor enters or leaves the Region. When a mouse button is clicked and released when the cursor is in the region. Of course, you can change the way that the Button responds to all of these changes.\nCSS Elements # Modena # To understand how the styling works with the properties on Buttons, we\u0026rsquo;ll look at the Modena stylesheet, which is the standard stylesheet that ships with the latest versions of JavaFX.\nThe basic colours in Modena are as follows:\n-fx-base -fx-background -fx-outer-border -fx-default-button -fx-focus-color The rest of the colours you see referenced are more complicated gradients and ladders based on these fundamental colours, but this should give you the idea.\nIt\u0026rsquo;s important to note that -fx-color is initially set be -fx-base. Most of the transitions are accomplished by changing -fx-color to another value for the Button.\nHere\u0026rsquo;s what the various stylings look like:\nThe following selectors control Buttons in the CSS, these are the definitions from \u0026ldquo;Modena\u0026rdquo;:\nBase layout for Button # .button { -fx-background-insets: 0 0 -1 0, 0, 1, 2; -fx-background-radius: 3px, 3px, 2px, 1px; -fx-padding: 0.333333em 0.666667em 0.333333em 0.666667em; /` 4 8 4 8 `/ -fx-text-fill: -fx-text-base-color; -fx-alignment: CENTER; -fx-content-display: LEFT; } The first three lines here create the border and shadow effects by layering 4 background colours on top of each other with differing insets and corner radii. There\u0026rsquo;s no actual \u0026ldquo;border\u0026rdquo; defined. The colours are standard colours for the Modena theme.\nThe text colour is set to be the standard theme text colour.\nMouse Hover # This is the styling added when the cursor hovers over the Button. It simply lightens it up a little.\n{% highlight css %} .button:hover { -fx-color: -fx-hover-base; } {% endhighlight %}\n-fx-hover-base is supposed to be just a little bit lighter than -fx-base.\nArmed # When the mouse button is pressed when the cursor is over the Button it appears to push down a little bit. This is accomplished by darkening it a by 6%. JavaFX refers to this as \u0026ldquo;armed\u0026rdquo;.\nIt\u0026rsquo;s virtually impossible to have an armed Button that doesn\u0026rsquo;t also have focus. So the example in the image above shows both states.\n.button:armed { -fx-color: -fx-pressed-base; } Focused # When the Button has focus, the border colour scheme changes to a \u0026ldquo;glowing\u0026rdquo; blue ring:\n.button:focused { -fx-background-color: -fx-focus-color, -fx-inner-border, -fx-body-color, -fx-faint-focus-color, -fx-body-color; -fx-background-insets: -0.2, 1, 2, -1.4, 2.6; -fx-background-radius: 3, 2, 1, 4, 1; } Disabled # Disabled Buttons are a quite a bit translucent. Personally, I find this amount to be extreme and it makes the buttons almost difficult to see.\n.button:disabled { -fx-opacity: 0.4; } Mnemonic Underline # This is specifically the colour of the underline which is shown under key letters when the mnemonics are activated (usually by pressing the \u0026lt;Alt\u0026gt; key). Ordinarily the colour of the underline is \u0026ldquo;transparent\u0026rdquo;.\n.button:show-mnemonics \u0026gt; .mnemonic-underline { -fx-stroke: -fx-text-base-color; } Default # When a Button has been set as the \u0026ldquo;default\u0026rdquo;, the base colour changes to a light blue.\n.button:default { -fx-base: -fx-default-button; } Properties # Button has all of the standard properties of Node and Region, which define things like MaxHeight, MaxWidth, Visible, Managed and so on. In addition, it has a few other properties which are interesting:\nLet\u0026rsquo;s take a look at what some of those properties actually do:\nGraphic # Since Button extends Labeled, it has two properties holding the contents. The Graphic is simply any kind of Node that you want to put on the Button. It can be an ImageView, an icon, or something bigger and complex, like a TableView, BorderPane, or Chart. That would probably end up with a really big Button, but it would still work.\nHere\u0026rsquo;s a sample showing how the Graphic property can be manipulated:\npublic class ButtonsGraphicExample extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setScene(new Scene(createView())); primaryStage.show(); } private Region createView() { HBox results = new HBox(10); Button button1 = new Button(\u0026#34;\u0026#34;, new ImageView(new Image(\u0026#34;/images/JavaFXLogo.png\u0026#34;))); ImageView logo = new ImageView(new Image(\u0026#34;/images/JavaFXLogo.png\u0026#34;)); logo.setFitHeight(32); Button button2 = new Button(\u0026#34;Click Me!\u0026#34;, logo); Shape circle1 = createCircle(Color.TEAL); Shape circle2 = createCircle(Color.TEAL); Shape circle3 = createCircle(Color.RED); Button button3 = new Button(\u0026#34;Button\u0026#34;, circle1); Button button4 = new Button(\u0026#34;Focused?\u0026#34;); button4.graphicProperty().bind(Bindings.createObjectBinding(() -\u0026gt; button4.isFocused() ? circle3 : circle2, button4.focusedProperty())); results.getChildren().addAll(button1, button2, button3, button4); results.setPadding(new Insets(30)); return results; } private Shape createCircle(Paint colour) { Circle results = new Circle(40); results.setFill(colour); results.setStroke(Color.BLACK); results.setStrokeWidth(2.0); return results; } } The first two Buttons show an ImageView loaded in as the graphic, with and without a Text value. The last two Buttons have a Shape loaded into the Graphic.\nThe last Button, \u0026ldquo;button4\u0026rdquo; has its Graphic property bound to its Focused property, swapping out a red circle when the Button has focus.\nThis is what it looks like with the focus somewhere other than the last Button:\nAnd when the last Button has focus:\nText # The other part of Labeled is a StringProperty called Text, and is simply any text you want to show on the Button and is what people generally think of as the Button \u0026ldquo;label\u0026rdquo;. Since it\u0026rsquo;s a property, it can be bound to other properties in your screen and Model and change dynamically.\nHere\u0026rsquo;s an example where the Text is bound to the results of an animation loop, so that the Button displays the number of seconds (almost) since the Button was clicked:\npublic class ButtonBoundText extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { HBox pane = new HBox(20, createTimerButton(), createTimerButton()); pane.setPadding(new Insets(30)); primaryStage.setScene(new Scene(pane)); primaryStage.show(); } @NotNull private Button createTimerButton() { Button timerButton = new Button(\u0026#34;Timer\u0026#34;); timerButton.setOnAction(evt -\u0026gt; createTimer(timerButton.textProperty())); return timerButton; } private void createTimer(StringProperty boundProperty) { IntegerProperty counter = new SimpleIntegerProperty(0); Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(10000), new KeyValue(counter, 10000))); boundProperty.bind(Bindings.createStringBinding(() -\u0026gt; Integer.toString(counter.get()), counter)); timeline.play(); } } This is what it looks like with the timer on the second Button running:\nDefaultButton # Straight from the JavaDocs for Button, as it says it all:\nA default button is the button that receives a keyboard VK_ENTER press, if no other node in the scene consumes it.\nThis can be super useful if you have a screen with multiple Buttons but you want your UI to respond to \u0026ldquo;Enter\u0026rdquo; key in an appropriate manner. Or if you have several data entry controls that need to be completed in order to activate a Button to respond to \u0026ldquo;Enter\u0026rdquo;.\nFor instance, you can bind the DefaultButton property of a Button to the Focused property of a TextField. So whenever a user hits \u0026ldquo;Enter\u0026rdquo; from that TextField, it activates the Button, but not if they are not in that TextField.\nEvents # The OnAction Event # OnAction is really the only event you\u0026rsquo;re likely to use with a Button. It\u0026rsquo;s the one that\u0026rsquo;s fired when you click and release the Button.\nFor practical purposes, there\u0026rsquo;s no useful information in event itself, and just the fact that it\u0026rsquo;s been fired is the main piece of information you\u0026rsquo;ll need in order to do anything that\u0026rsquo;s triggered by the event. My recommendation is to keep the Event Handler itself as short as possible, and create a method to handle the actual work that the Button is going to do, possibly passing the Button itself as a parameter so that you can access its properties to temporarily disable it.\nA Simple example # In this example, we\u0026rsquo;re just going to update a Label on the screen with contents from a TextField. The entire action of the Button is set with just this one line:\nbutton.setOnAction(evt -\u0026gt; target.setText(textField.getText())); That\u0026rsquo;s pretty simple, but you can see that there\u0026rsquo;s no need to reference any data from the Event itself, it\u0026rsquo;s just a trigger for the action to happen.\npublic class ButtonActionExample extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setScene(new Scene(createView())); primaryStage.show(); } private Region createView() { Label actionTarget = new Label(\u0026#34;Button Has Not Yet Been Clicked\u0026#34;); VBox results = new VBox(10, inputLine(actionTarget), new HBox(6, new Label(\u0026#34;Results: \u0026#34;), actionTarget)); results.setPadding(new Insets(20)); return results; } private Node inputLine(Label target) { TextField textField = new TextField(); Button button = new Button(\u0026#34;Fetch\u0026#34;); button.defaultButtonProperty().bind(textField.focusedProperty()); button.setOnAction(evt -\u0026gt; target.setText(textField.getText())); return new HBox(6, new Label(\u0026#34;Enter Some Text Here: \u0026#34;), textField, button); } } Before you click on the Button, the screen looks like this:\nAnd then it looks like this:\nTips # Disable vs Disabled Properties # This confuses everyone. Disable is the read/write property that you can change to control whether or not the Button is to be disabled. Disabled is a read-only property that will tell you if the Button is currently disabled. Disabled should always be used for querying, as it will change if a containing Node of the Button has been disabled, while querying Disable will only tell you if the Button has been specifically disabled.\nAvoid Double-Clicks # If a Button's action takes more than a couple of milliseconds to complete it\u0026rsquo;s a good idea to immediately set Disable to true in the OnAction event so that the user can\u0026rsquo;t trigger it a second or third time while it\u0026rsquo;s running.\nThis can get a little tricky if Disable has been bound to other elements of the GUI. In that case you should create a boolean property called \u0026ldquo;xyzRunning\u0026rdquo; (or something similiar) and add it to your binding. Then toggle that property instead of directly messing with the Disable property.\nBinding the Default Property # Being able to just hit \u0026lt;Enter\u0026gt; can be a huge convenience for users, and is often the most intuitive behaviour. But there might be multiple Buttons that could be candidates for the \u0026ldquo;default\u0026rdquo;, and the correct one might change depending on what\u0026rsquo;s happening in the GUI. Here\u0026rsquo;s an example where there are two lookup buttons, and the one that is default is determined by which TextField has the focus at any given time:\npublic class DefaultButtonExample extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setScene(new Scene(createView())); primaryStage.show(); } private Region createView() { VBox results = new VBox(10, inputLine(\u0026#34;Customer Name:\u0026#34;), inputLine(\u0026#34;Account Number:\u0026#34;)); results.setPadding(new Insets(20)); return results; } private Node inputLine(String label) { TextField textField = new TextField(); Button button = new Button(\u0026#34;Fetch\u0026#34;); button.defaultButtonProperty().bind(textField.focusedProperty()); return new HBox(6, new Label(label), textField, button); } } This looks like this:\nIt\u0026rsquo;s also easier than trying to capture \u0026lt;Enter\u0026gt; in the keystrokes in the TextField!\nUse Multiple Buttons - Don\u0026rsquo;t Reconfigure a Single Button # Sometimes you\u0026rsquo;ll have multiple contexts in which a Button could operate, and it might need to have different labels and actions depending on the context. For instance, in a CRUD screen the Button could read \u0026ldquo;Create\u0026rdquo; for a new record, or \u0026ldquo;Update\u0026rdquo; to save an existing record. Unless the differences are entirely cosmetic, it\u0026rsquo;s usually cleaner to have two or more Buttons and simply hide any Buttons that don\u0026rsquo;t apply, rather that keeping track of the context and having the OnAction Event Handler figure which action code it should be running.\nHere\u0026rsquo;s an example where the Button that\u0026rsquo;s shown depends on whether or not a TextField has anything entered into it or not. There are four versions of it on the same screen, to show both states and confirm which action was run when clicked:\npublic class ButtonHideExample extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { HBox row1 = new HBox(20, createExample(), createExample()); HBox row2 = new HBox(20, createExample(), createExample()); primaryStage.setScene(new Scene(new VBox(20, row1, row2))); primaryStage.show(); } private Region createExample() { TextField textField = new TextField(); Label targetLabel = new Label(\u0026#34;Nothing has been clicked\u0026#34;); HBox inputRow = new HBox(6, new Label(\u0026#34;Number: \u0026#34;), textField); HBox outputRow = new HBox(6, new Label(\u0026#34;Results:\u0026#34;), targetLabel); VBox results = new VBox(10, inputRow, outputRow, createButtons(textField.textProperty(), targetLabel.textProperty())); results.setPadding(new Insets(20)); return results; } private Node createButtons(StringProperty triggerProperty, StringProperty targetProperty) { Button saveButton = new Button(\u0026#34;Save\u0026#34;); saveButton.visibleProperty().bind(triggerProperty.isEmpty().not()); saveButton.managedProperty().bind(saveButton.visibleProperty()); saveButton.setOnAction(evt -\u0026gt; targetProperty.set(\u0026#34;Save was clicked\u0026#34;)); Button generateButton = new Button(\u0026#34;Generate\u0026#34;); generateButton.visibleProperty().bind(triggerProperty.isEmpty()); generateButton.managedProperty().bind(generateButton.visibleProperty()); generateButton.setOnAction(evt -\u0026gt; targetProperty.set(\u0026#34;Generate was clicked\u0026#34;)); return new StackPane(saveButton, generateButton); } } Which looks like this:\nThings to note about this:\nYou need to set both the Visible and Managed properties.\nIf you don\u0026rsquo;t do this then the invisible Buttons will still take up space on the screen. There\u0026rsquo;s no connection between the two Buttons Each Button is fully configured in 4 lines of code. The Button creation method is essentially stand-alone.\nIt\u0026rsquo;s given a pair of properties to work with, but there\u0026rsquo;s no global variables or any other connection to the rest of the screen. In fact, the same method is called 4 times, and the results of each are completely independent. Content Drives Minimum Size # If you have large content in your Button, the Button will grow to accommodate it. Setting MaxHeight or MaxWidth won\u0026rsquo;t cause the button to shrink if it\u0026rsquo;s smaller than the size of the contents. You\u0026rsquo;ll need to apply some sort of scaling transformation on your content to make the Button smaller.\nHowever, setting MinHeight and MinWidth can make a Button larger.\n","date":"3 January 2022","externalUrl":null,"permalink":"/javafx/elements/buttons/","section":"JavaFX","summary":"Buttons are the basic component that triggering actions in your applications. While there is just one basic Button class, it can be styled and configured in a multitude of ways that make it look and behave quite differently to suit a variety of uses.","title":"All About Buttons","type":"javafx"},{"content":"In Part 1 of this series, we looked at how to use TextFormatter to customize the behaviour of a TextField to handle specialized data entry. In this part we\u0026rsquo;ll build a filter and converter which will handle decimal data input with a fixed number of decimal places.\nThe Objective # The best way to see how TextFormatter works is to walk through the creation of a TextField customized to accept only a certain kind of input. For this example, we\u0026rsquo;ll build a decimal input field with a fixed number of decimal places. To make it interesting, we\u0026rsquo;ll stipulate the following requirements:\nOnly a set amount of decimal places are allowed If the data entry would result in too many decimal places then some will be trimmed out: If the data entry is at the very end of the number, then the first decimal place will be removed If the data entry is not at the very end of the number, then the last decimal place will be removed If the data entry would result in too few decimal places, a \u0026ldquo;0\u0026rdquo; will be appended to the end of the number When the TextField gains focus, the whole number portion of the field will be selected. When the decimal point key is pressed: If the cursor is currently in the whole number portion, then the decimal portion will be selected. If the cursor is currently in the decimal portion, then the whole number portion will be selected. When the \u0026ldquo;-\u0026rdquo; key is pressed, no matter where the cursor is: If the number is currently negative, then the \u0026ldquo;-\u0026rdquo; will be removed from the beginning of the number and the cursor will remain where it is. If the number is currently positive, then a \u0026ldquo;-\u0026rdquo; will be placed at the beginning of the number, and the cursor will remain in the same position. In other words, this will functionally split the contents of the TextField into two parts with the \u0026lt;.\u0026gt; key used to toggle between them. The \u0026lt;-\u0026gt; key can be pressed when the cursor is in any place in either portion and it will toggle a negative sign at the beginning of the number. Any data entry that would cause the number to have the wrong number of decimal places will be automatically corrected by scrolling off one of the digits, or appending a \u0026ldquo;0\u0026rdquo; to the number.\nIs this a good way to design a fixed place decimal field? # I\u0026rsquo;m not sure.\nOn one hand, it\u0026rsquo;s highly functional and works smoothly. If someone just types \u0026ldquo;1234.56\u0026rdquo; when there are two decimal places allowed, it will result in \u0026ldquo;1234.56\u0026rdquo;, as expected.\nThe rest of the actions, however, are a little counter-intuitive and would require explanation or some exploration on the part of the user. That\u0026rsquo;s probably not the best design, especially if it\u0026rsquo;s deployed in an application with a lot of single-time users.\nCaution Any UI design that requires explanation or training to use is probably not awesome.\nAll that aside, it is a good example to explore how you can use TextFormatter to create a much more sophisticated input control out of a simple TextField.\nThe Converter # We\u0026rsquo;ll start with the converter since it\u0026rsquo;s the easiest part:\npublic class FixedDecimalConverter extends DoubleStringConverter { private final int decimalPlaces; public FixedDecimalConverter(int decimalPlaces) { this.decimalPlaces = decimalPlaces; } @Override public String toString(Double value) { return String.format(\u0026#34;%.\u0026#34; + decimalPlaces + \u0026#34;f\u0026#34;, value); } @Override public Double fromString(String valueString) { if (valueString.isEmpty()) { return 0d; } return super.fromString(valueString); } } The two things that make this different from the DoubleStringConverter is that it always forces the toString() result to have the correct number of decimal places, and that it turns an empty input string into a zero in the fromString() method.\nThe Filter # The Filter is an implementation of UnaryOperator\u0026lt;TextFormatter.Change\u0026gt;, which is used to validate and modify any change that the user makes in the TextField. UnaryOperator is just a Function which returns the same data type as it takes. So in the case of the Filter, it takes TextFormatter.Change as the input value, and returns a TextFormatter.Change as its result.\nThe Structure of TextFormatter.Change # You can think of TextFormatter.Change as clean way to capture each key presses and mouse actions in the TextField, with JavaFX itself handling all of the troublesome details about capturing and applying the keystrokes.\nNote Using TextFormatter.Change means you don\u0026rsquo;t need to fuss about capturing KeyEvents.\nThe Filter is a step between capturing the user action and applying it, and that allows us to write logic that can not just filter out actions, but change them and the way that they impact the TextField.\nTextFormatter.Change contains a lot more information than just the keystroke itself. Here are some of the essential components of TextFormatter.Change:\nThe \u0026ldquo;Change\u0026rdquo; Part of the Change:\nThese are the elements of the change itself. All of these can be manipulated to alter the impact of the change:\nThe text of the change. The range that the change applies to in the TextField contents The new positions of the anchor and caret The current TextField value:\nThese are accessed through methods that are called, getControl...(). TextFormatter.Change refers to the TextField as the \u0026ldquo;Control\u0026rdquo;, because it can be used with more than just TextField.\nThe current text in the TextField The current position of the caret and the anchor The new TextField value. This is access through the getControlNewText() method.\nYou can see what the impact of the change will be on the text value held in the TextField.\nThere is one caveat when manipulating the change:\nWarning The value returned by getControlNewText() is updated immediately when the change has been modified.\nSo if you need to reference the impact of the original change after you\u0026rsquo;ve modified it, you\u0026rsquo;ll need to make a copy before you start.\nFiltering the Change # The first thing to do is to ensure that the results of the change are a valid decimal string, so we\u0026rsquo;ll use regex to check that:\npublic class FixedDecimalFilter implements UnaryOperator\u0026lt;TextFormatter.Change\u0026gt; { @Override public TextFormatter.Change apply(TextFormatter.Change change) { if (change.getControlNewText().matches(\u0026#34;-?([0-9]*)?(\\\\.[0-9]*)?\u0026#34;)) { return change; } return null; } } Here you can see we\u0026rsquo;ve created a new class which implements UnaryOperator, and then supplies the logic for the apply() method. The code in there now will act as the backstop to the filter, making sure that whatever the final version of the change looks like, it won\u0026rsquo;t violate the requirement to have a decimal string. This code will allow any number of decimal places, but we\u0026rsquo;ll see how the rest of the controls in the Filter will handle this nicely.\nSo that we can test it, here\u0026rsquo;s a \u0026ldquo;Main\u0026rdquo; class which will launch a window with some TextFields:\npublic class FixedDecimalMain extends Application { private ObjectProperty\u0026lt;Double\u0026gt; valueProperty = new SimpleObjectProperty\u0026lt;\u0026gt;(0d); public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Scene scene = new Scene(new FixedDecimalMain.TestPane(), 300, 100); valueProperty.addListener(((observable, oldValue, newValue) -\u0026gt; { System.out.println(\u0026#34;Value changed -\u0026gt; Old Value: \u0026#34; + oldValue + \u0026#34;, New Value: \u0026#34; + newValue); })); primaryStage.setScene(scene); primaryStage.show(); } public class TestPane extends BorderPane { public TestPane() { TextField textField = new TextField(); TextFormatter\u0026lt;Double\u0026gt; textFormatter = new TextFormatter(new FixedDecimalConverter(2), 78, new FixedDecimalFilter()); textFormatter.valueProperty().bindBidirectional(valueProperty); textField.setTextFormatter(textFormatter); setCenter(new VBox(10, new HBox(6, new Text(\u0026#34;TextField 1\u0026#34;), textField), new HBox(6, new Text(\u0026#34;TextField 2\u0026#34;), new TextField()))); } } } Now, if you run this you\u0026rsquo;ll see that you can only enter characters which result in a valid decimal string. Otherwise, your keystrokes are swallowed up by the Filter.\nSelecting the Whole Number Part on Focus # The first thing you\u0026rsquo;ll notice if you run this is that the entire contents of the TextField are selected when the control gains focus. This isn\u0026rsquo;t what are requirements state, only the whole number part of the string should be selected. How to do this?\nIn order to investigate this, let\u0026rsquo;s add some console output to monitor what\u0026rsquo;s coming into the Filter:\npublic class FixedDecimalFilter implements UnaryOperator\u0026lt;TextFormatter.Change\u0026gt; { @Override public TextFormatter.Change apply(TextFormatter.Change change) { System.out.print(\u0026#34;Change: \u0026gt;\u0026#34; + change.getText() + \u0026#34;\u0026lt; Range: [\u0026#34; + change.getRangeStart() + \u0026#34;, \u0026#34; + change.getRangeEnd() + \u0026#34;]\u0026#34;); System.out.print(\u0026#34; Selection: [\u0026#34; + change.getSelection().getStart() + \u0026#34;, \u0026#34; + change.getSelection().getEnd() + \u0026#34;]\u0026#34;); System.out.println(\u0026#34; Anchor: \u0026#34; + change.getAnchor() + \u0026#34; Caret: \u0026#34; + change.getCaretPosition()); System.out.println(\u0026#34; TextField: \u0026gt;\u0026#34; + change.getControlText() + \u0026#34;\u0026lt;, L: \u0026#34; + change.getControlText().length()); if (change.getControlNewText().matches(\u0026#34;-?([0-9]*)?(\\\\.[0-9]*)?\u0026#34;)) { return change; } return null; } } No when you launch the application, you get the following console output:\nChange: \u0026gt;\u0026lt; Range: [0, 0] Selection: [0, 4] Anchor: 4 Caret: 0 TextField: \u0026gt;0.00\u0026lt;, L: 4 And if you hit Tab twice, to toggle the focus to the second TextField and back, you\u0026rsquo;ll end up with the following output:\nChange: \u0026gt;\u0026lt; Range: [0, 0] Selection: [0, 4] Anchor: 4 Caret: 0 TextField: \u0026gt;0.00\u0026lt;, L: 4 Change: \u0026gt;\u0026lt; Range: [0, 0] Selection: [0, 0] Anchor: 0 Caret: 0 TextField: \u0026gt;0.00\u0026lt;, L: 4 Change: \u0026gt;\u0026lt; Range: [0, 0] Selection: [0, 4] Anchor: 4 Caret: 0 TextField: \u0026gt;0.00\u0026lt;, L: 4 So it appears that when focus is gained on the TextField, it generates a Change with empty text and with the anchor set at the end of the string and the caret at the beginning. When focus is lost, it sends another Change with empty text and both the anchor and the caret set to the beginning of the string.\nIt\u0026rsquo;s also possible to select the contents of a TextField by double-clicking on it. This is the output that generates:\nChange: \u0026gt;\u0026lt; Range: [0, 0] Selection: [0, 0] Anchor: 0 Caret: 0 TextField: \u0026gt;0.00\u0026lt;, L: 4 Change: \u0026gt;\u0026lt; Range: [4, 4] Selection: [0, 4] Anchor: 0 Caret: 4 TextField: \u0026gt;0.00\u0026lt;, L: 4 The first line is when it responds to the first click, and the second when it processes it as a double click. It\u0026rsquo;s almost the same as Tab, but the caret and the anchor are reversed.\nIt looks like the best way to detect this is to check for a Change with empty text and a range which includes the entire contents of the TextField. Let\u0026rsquo;s put some code in the filter to catch that and change it to select just the whole number portion:\npublic class FixedDecimalFilter implements UnaryOperator\u0026lt;TextFormatter.Change\u0026gt; { @Override public TextFormatter.Change apply(TextFormatter.Change change) { System.out.print(\u0026#34;Change: \u0026gt;\u0026#34; + change.getText() + \u0026#34;\u0026lt; Range: [\u0026#34; + change.getRangeStart() + \u0026#34;, \u0026#34; + change.getRangeEnd() + \u0026#34;]\u0026#34;); System.out.print(\u0026#34; Selection: [\u0026#34; + change.getSelection().getStart() + \u0026#34;, \u0026#34; + change.getSelection().getEnd() + \u0026#34;]\u0026#34;); System.out.println(\u0026#34; Anchor: \u0026#34; + change.getAnchor() + \u0026#34; Caret: \u0026#34; + change.getCaretPosition()); System.out.println(\u0026#34; TextField: \u0026gt;\u0026#34; + change.getControlText() + \u0026#34;\u0026lt;, L: \u0026#34; + change.getControlText().length()); if (change.getText().isEmpty() \u0026amp;\u0026amp; isEverythingSelected(change)) { change.selectRange(0, change.getControlText().indexOf(\u0026#34;.\u0026#34;)); return change; } if (change.getControlNewText().matches(\u0026#34;-?([0-9]*)?(\\\\.[0-9]*)?\u0026#34;)) { return change; } return null; } private boolean isEverythingSelected(TextFormatter.Change change) { return (change.getSelection().getStart() == 0) \u0026amp;\u0026amp; (change.getSelection().getEnd() == change.getControlText().length()); } } This is our first example of manipulating the Change to alter the behaviour of the TextField. If any action attempts to select the entire string, it will intercept it and select the whole number portion instead. Also, since this can never alter the TextField string, there\u0026rsquo;s no point in running it through the regex to see if the Change is valid, so we return the altered Change immediately.\nHandling the \u0026ldquo;.\u0026rdquo; Key # Now we want the \u0026ldquo;.\u0026rdquo; key to toggle the input between the whole number and decimal portions of the string, selecting the entire part. So we\u0026rsquo;ll need to capture the decimal point Change and manipulate it. Here\u0026rsquo;s the logic that will do that:\nint decimalPos = change.getControlText().indexOf(\u0026#34;.\u0026#34;); if (change.getText().equals(\u0026#34;.\u0026#34;)) { change.setText(\u0026#34;\u0026#34;); change.setRange(0, 0); if (change.getControlCaretPosition() \u0026lt;= decimalPos) { change.setCaretPosition(decimalPos + 1); change.setAnchor(change.getControlText().length()); } else { change.setCaretPosition(decimalPos); change.setAnchor(0); } return change; } We\u0026rsquo;ve introduced decimalPos because it we\u0026rsquo;re going to be using it in two places now.\nFirst, we clear the text in the Change, since we don\u0026rsquo;t actually want to add a \u0026ldquo;.\u0026rdquo; to the string. Then we set the Change range to (0,0) since we don\u0026rsquo;t want to be removing any text either. So now the Change can\u0026rsquo;t actually change the TextField string.\nNext, we need to determine which portion of the number the caret is currently in, the whole number part or the decimal part. We do this by comparing it\u0026rsquo;s position to the position of the \u0026ldquo;.\u0026rdquo; in the string. Note that we care about where the caret was BEFORE the change would have been applied, so we\u0026rsquo;re going to get it\u0026rsquo;s position from getControlText().\nFinally, we set the caret just to either side of the \u0026ldquo;.\u0026rdquo;, and then set anchor to either the beginning or the end of the string. This will create a selection in the TextField.\nOnce again, since we\u0026rsquo;re not changing the contents of the TextField string, there\u0026rsquo;s no need to run it through the regex to see if it\u0026rsquo;s in the correct format.\nNow the Filter looks like this, with the new logic inserted and the console output removed:\npublic class FixedDecimalFilter implements UnaryOperator\u0026lt;TextFormatter.Change\u0026gt; { @Override public TextFormatter.Change apply(TextFormatter.Change change) { int decimalPos = change.getControlText().indexOf(\u0026#34;.\u0026#34;); if (change.getText().isEmpty() \u0026amp;\u0026amp; isEverythingSelected(change)) { change.selectRange(0, decimalPos); return change; } if (change.getText().equals(\u0026#34;.\u0026#34;)) { change.setText(\u0026#34;\u0026#34;); change.setRange(0, 0); if (change.getControlCaretPosition() \u0026lt;= decimalPos) { change.setCaretPosition(decimalPos + 1); change.setAnchor(change.getControlText().length()); } else { change.setCaretPosition(decimalPos); change.setAnchor(0); } return change; } if (change.getControlNewText().matches(\u0026#34;-?([0-9]*)?(\\\\.[0-9]*)?\u0026#34;)) { return change; } return null; } private boolean isEverythingSelected(TextFormatter.Change change) { return (change.getSelection().getStart() == 0) \u0026amp;\u0026amp; (change.getSelection().getEnd() == change.getControlText().length()); } } That\u0026rsquo;s all for this part. We still need to handle the scrolling of the decimal portion as digits are added or removed, and the \u0026ldquo;-\u0026rdquo; sign. We\u0026rsquo;ll do that in Part 3.\n","date":"11 May 2021","externalUrl":null,"permalink":"/javafx/elements/nodes/textformatter/part-2/","section":"JavaFX","summary":"A more in-depth look into how to build a TextFormatter that can be used to turn a TextField into sophisticated input field. We’ll look at designing a TextFormatter that is tailored to handle fixed place decimal input, using the decimal point itself as a control to toggle between the ordinal and mantissa of the number.","title":"TextFields and TextFormatter - Part 2","type":"javafx"},{"content":" The Articles: # ","externalUrl":null,"permalink":"/javafx/elements/observables/bindings/","section":"JavaFX","summary":"If you look at the JavaDocs for the interface, Property you’ll find that all of its methods are only concerned with binding. That’s how important Bindings are to JavaFX.\nBinding is a class, but it’s also an action. Bindings are how you connect two or more Properties together, so that you can have actual data stored inside a single place - a Property - and use it all over your application without having to write code to update all those places whenever it changes.\nSince the JavaFX screen Nodes are just chock full of Properties, this means that it’s easy to connect and control your GUI from Properties that you create in your Presentation Model.\n","title":"Bindings","type":"javafx"},{"content":" What You\u0026rsquo;ll Learn # Layout classes like HBox and VBox Some basic concepts about configuring layouts. We\u0026rsquo;ll cover spacing, alignment and padding in this part. The Pos and Insets classes. A little about TextField Adding Some Simple User Input # The screen from Part 1 is really simple, and doesn\u0026rsquo;t do much except display \u0026ldquo;Hello World\u0026rdquo; in a tiny little window. Let\u0026rsquo;s add some elements that will actually do something.\nThe Code So Far # Our code at the end of Part 1 looked like this:\npublic class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Scene scene = new Scene(createContents()); primaryStage.setScene(scene); primaryStage.show(); } private Region createContents() { return new Label(\u0026#34;Hello World\u0026#34;); } } With this output:\nThe Screen is Too Cramped # You can barely read the, \u0026ldquo;Hello World\u0026rdquo; since the screen is so tiny. We can fix that by setting the initial size of the Scene in it\u0026rsquo;s constructor. We\u0026rsquo;ll change the line to:\nScene scene = new Scene(createContents()); to:\nScene scene = new Scene(createContents(),400, 200); This sets the initial window size to 400 pixels wide, and 200 pixels tall. When you run this it looks like this:\nThat\u0026rsquo;s somewhat of an improvement, even though the \u0026ldquo;Hello World\u0026rdquo; is still tucked over way to the left. We\u0026rsquo;ll look at that kind of thing in a little bit.\nThat\u0026rsquo;s about all \u0026ldquo;Hello World\u0026rdquo; is good for. Let\u0026rsquo;s move on to something more interesting\u0026hellip;\nAdding User Input # Over the next few parts of this guide we\u0026rsquo;ll change this screen to let the user enter their name and click on a Button. When the Button is clicked, we\u0026rsquo;ll update the contents of the Label to be \u0026ldquo;Hello {name}\u0026rdquo;. The first step is to provide a way for the user to enter their name.\nAdding a Prompt and an Input Field # The prompt is just going to be another Label that says \u0026ldquo;Name:\u0026rdquo;, and the input field is a control called a \u0026ldquo;TextField\u0026rdquo;. TextField is the basic control for simple text input in a screen. We want the prompt and the input to be in a single row, so we are going to put them in an HBox. For now, we\u0026rsquo;ll scrap the output label:\npublic class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Scene scene = new Scene(createContents(), 400, 200); primaryStage.setScene(scene); primaryStage.show(); } private Region createContents() { return new HBox(new Label(\u0026#34;Name:\u0026#34;), new TextField(\u0026#34;\u0026#34;)); } } What\u0026rsquo;s an HBox? # HBox is an example of a \u0026ldquo;layout\u0026rdquo; class. It\u0026rsquo;s a Region subtype Node that holds other Nodes inside it. There are a number of different layout classes, like BorderPane, ScrollPane, GridPane, VBox and HBox. Each one has different characteristics and is designed for different methods of organizing their contents.\nHBox presents its contents in a horizontal row, with the first item at the far left and each new item added to the right. VBox is similar except that it shows it\u0026rsquo;s contents arranged vertically, from the top down. In this case, I\u0026rsquo;ve chosen to populate the contents of the HBox in its constructor.\nHBox and VBox are probably the most common, versatile and easy to use layout classes in JavaFX. Get used to seeing them because they are used a lot.\nMaking it Look Nicer # If you run this code, you\u0026rsquo;ll see it looks like this:\nWell, it\u0026rsquo;s all squished up in the top left corner, and the \u0026ldquo;Name:\u0026rdquo; prompt seems to be a little higher than the TextField. So let\u0026rsquo;s fix that.\nThis is done by configuring the layout of the containing HBox. The code ends up looking like this:\npublic class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Scene scene = new Scene(createContents(), 400, 200); primaryStage.setScene(scene); primaryStage.show(); } private Region createContents() { HBox results = new HBox(new Label(\u0026#34;Name:\u0026#34;), new TextField(\u0026#34;\u0026#34;)); results.setSpacing(6); results.setPadding(new Insets(0,0,0,50)); results.setAlignment(Pos.CENTER_LEFT); return results; } } The first thing that you\u0026rsquo;ll notice is that we\u0026rsquo;re now instantiating the HBox as a variable so that we can configure it through some of its configuration methods. Let\u0026rsquo;s look at what we\u0026rsquo;ve done:\nsetSpacing() : This defines the space between the Nodes contained inside the HBox\nsetPadding() : This defines the space between the edges of the HBox and its contents.\nInsets : This class is used to define space around or inside a Node. Insets has two constructors. One takes a single number which is applied to all sides of the Node. In this case, we\u0026rsquo;ve used the second constructor, which takes a separate number for each side in the order: Top, Right, Bottom and Left.\nsetAlignment() and Pos : This defines how the Nodes contained in the HBox will be oriented in relation to the HBox. In this case, they are going to be left aligned and vertically centred. The default is Pos.TOP_LEFT, which explains why the Label looked higher than the TextField. TextField is slightly taller than Label in its default styling.\nWhen you run this, you\u0026rsquo;ll see it looks almost like this:\nI\u0026rsquo;ve added an extra line to put a red border around the HBox. It\u0026rsquo;s not included in the code above because it uses concepts we haven\u0026rsquo;t looked at yet. But since HBox is usually invisible, it helps to have a border to understand how it works. We\u0026rsquo;ll get to the border later on.\nWhen you see the red border it becomes clear that the HBox is occupying all of the space available to it, not just the amount of space required to hold its contents. This explains why everything was originally up in the top left corner with the default Pos.TOP_LEFT alignment, and why it\u0026rsquo;s now moved to the middle of the window. It\u0026rsquo;s not all the way over at the left because we set a 50 pixel padding on the left side of the HBox.\nUnderstanding how and when certain of the layout classes will expand in one direction or another to fill all of the space available is one of the more advanced aspects of JavaFX that you\u0026rsquo;ll eventually have to be comfortable with. In the meantime, you can be assured that all of the layout classes act consistently and in predictable ways.\n","externalUrl":null,"permalink":"/beginners/part2/","section":"JavaFX","summary":"Some basics about building layouts and making your screens look the way you want.","title":"Part 2 - Configuring Layouts","type":"javafx"},{"content":" Introduction # This is the third part of an article describing how to set up whole-home audio using Mopidy and SnapCast. If you haven\u0026rsquo;t already read them should should probably read Part I and Part II first.\nSo far, we\u0026rsquo;ve installed and configured the SnapCast server, which will broadcast the music to the SnapCast clients. We\u0026rsquo;ve looked at how to set up a RasberryPi Zero (RPi0), install a Digital to Analog Converter (DAC) on it, install the SnapCast client service, and then configure the whole thing so that it can receive a stream from the SnapCast server and play it through the DAC.\nWhat we need now is a way to pull an audio stream from the Internet, and transfer it over to our SnapCast server to broadcast internally. For this we\u0026rsquo;ll use an application called Mopidy.\nMopidy is a streaming client designed to be used on a \u0026ldquo;headless\u0026rdquo; server. It has a variety of plugins to provide a browser interface and to allow it to connect to diffent kinds of streaming services. It also has a REST API that we will be using to provide some simple automation for the whole-home audio.\nIn my house we are mostly streaming from SomaFM.com, which is an excellent service and has a variety of stations to choose from. Personally, we like the one called \u0026ldquo;Groove Salad\u0026rdquo;. That\u0026rsquo;s what I\u0026rsquo;ll be showing how to set up. However, if you are interested in streaming from any other service, this guide should get you going on that too.\nMopidy Server # Mopidy is pretty easy to install, but there are a few pain points along the way\u0026hellip;\nStart by using apt to install Mopidy. This seems to give the latest version:\n$ sudo apt update $ sudo apt install mopidy You can check the version that you get:\n$ mopidy --version Mopidy 3.4.2 Which, at the time of writing, was the latest version, although from October 2023.\nYou can use apt to see all of the Mopidy extensions that are available that way:\n$ sudo apt search mopidy We are going to need a couple of extensions, otherwise we won\u0026rsquo;t be able to do much with Mopidy. The first we will install is the extension for SomaFM. If you are going to be using some other service for a source, you\u0026rsquo;ll need to install the extension for it. However, I would still recommend that you install the SomaFM add-on because then you\u0026rsquo;ll have something that you can test before you go trying your own thing.\n$ sudo apt install mopidy-somafm Next, we\u0026rsquo;ll need a web interface so that we can control everything. I prefer \u0026ldquo;Muse\u0026rdquo; because it integrates some of the controls for SnapCast as well.\nMany of the extensions, like Muse, cannot be installed via apt. You\u0026rsquo;ll need to use a Python utility to do it, and the first thing you\u0026rsquo;ll need to do is install that utility. Here\u0026rsquo;s how you get Muse installed:\n$ sudo apt install python3-pip $ sudo python3 -m pip install Mopidy-Muse $ sudo python3 -m pip install --break-system-packages Mopidy-Muse $ sudo python3 -m pip install --break-system-packages Mopidy-Jellyfin You can see that I\u0026rsquo;ve also installed the Jellyfin add-on as well.\nThat --break-system-packages option is going to generate some ugly warning messages. If you leave it out, you\u0026rsquo;ll get this:\n$ sudo python3 -m pip install Mopidy-Muse error: externally-managed-environment × This environment is externally managed ╰─\u0026gt; To install Python packages system-wide, try apt install python3-xyz, where xyz is the package you are trying to install. If you wish to install a non-Debian-packaged Python package, create a virtual environment using python3 -m venv path/to/venv. Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make sure you have python3-full installed. If you wish to install a non-Debian packaged Python application, it may be easiest to use pipx install xyz, which will manage a virtual environment for you. Make sure you have pipx installed. See /usr/share/doc/python3.13/README.venv for more information. note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages. hint: See PEP 668 for the detailed specification. I\u0026rsquo;m not a Python programmer, and I have absolutely no knowledge of or experience with \u0026ldquo;Python virtual environments\u0026rdquo;. When I first encountered this, I tried to figure it out. I really tried. In the end, I gave up and just went with the --break-system-packages, even though it made me feel uneasy. It hasn\u0026rsquo;t caused any problems, though.\nThe last bit of installation that you\u0026rsquo;ll need to do is to install some decoders for the streaming engine. Mopidy uses something called GStreamer1.0 to do the work. You\u0026rsquo;ll already have GStreamer1.0 installed, but it will need some additional packages to make it work. There\u0026rsquo;s three of them; \u0026ldquo;Good\u0026rdquo;, \u0026ldquo;Bad\u0026rdquo;, and \u0026ldquo;Ugly\u0026rdquo;. I don\u0026rsquo;t know which ones you absolutely need, so I installed all of them:\n$ sudo apt install gstreamer1.0-plugins-good $ sudo apt install gstreamer1.0-plugins-bad $ sudo apt install gstreamer1.0-plugins-ugly That\u0026rsquo;s it for the installation. The last thing you need is the configuration file to make everything work. It\u0026rsquo;s located at /etc/mopidy/mopidy.conf, and when you are done it should look something like this:\n# For information about configuration values that can be set in this file see: # # https://docs.mopidy.com/en/latest/config/ # # Run `sudo mopidyctl config` to see the current effective config, based on # both defaults and this configuration file. [core] restore_state = true [http] hostname=10.93.50.208 [audio] output = audioresample ! audioconvert ! audio/x-raw,rate=48000,channels=2,format=S16LE ! wavenc ! filesink location=/var/local/snapserver/snapfifo mixer_volume = 22 buffer_time = 4000 [somafm] encoding = aac quality = highest [muse] enabled = true mopidy_host = 10.93.50.208 mopidy_port = 6680 mopidy_ssl = false snapcast_host = 10.93.50.208 snapcast_port = 1780 snapcast_ssl = false [jellyfin] hostname = 192.168.1.211 username = mopidy password = PYo78u.p%.MChCTMROE83M\u0026amp;#867F max_bitrate = 100 Obviously, you\u0026rsquo;ll have to change the IP addresses to match you\u0026rsquo;re own setup. The restore_state flag just tells Mopidy to resume doing whatever it was doing before it was restarted.\nThe output tag is every important. Make sure that the \u0026ldquo;filesink location\u0026rdquo; matches the fifo pipe that you created for SnapCast. If you followed my instructions, it should be /var/local/snapserver/snapfifo.\nIf you added any other extensions, you\u0026rsquo;ll probably have to add a section in this file for that extension. Generally, if you follow a link from the Mopidy extension listing, you\u0026rsquo;ll get to a page that has a GitHub link listed just above the section called \u0026ldquo;Installation\u0026rdquo;. Follow that link and the GitHub page should have a sample configuration block on it.\nAt this point, you can restart the Mopidy service to get it to load all of the extensions and read the configuration.\nYou should use systemctl status mopidy -n60 to make sure that it\u0026rsquo;s working properly. If you\u0026rsquo;ve muddled up the configuration, you should get some messages in that display that will tell you that it had problems.\nTesting It All # I\u0026rsquo;d suggest opening up two browser tabs: one with the SnapCast page at port 1780, and the other at the Mopidy page at port 6680, then click into the \u0026ldquo;Muse\u0026rdquo; page.\nOn the SnapCast tab, click on the \u0026ldquo;Play\u0026rdquo; button. Then go to the Muse page, pick \u0026ldquo;Browse\u0026rdquo;, then select \u0026ldquo;SomaFM\u0026rdquo; which will expand the station list. Click on one of the stations, then \u0026ldquo;Play\u0026rdquo;. If all goes well, you should hear music through your browser.\nAutomation # In our house, we want the whole home audio streaming SomaFM\u0026rsquo;s \u0026ldquo;Groove Salad\u0026rdquo; from the time we wake up until we go to bed. When our current dog was just a puppy and we had her spending the night in a crate beside the bed, we needed some background noise so that she wouldn\u0026rsquo;t wake up and start whining whenever someone rolled over or made some small sound. So we started using a Google Home device to play \u0026ldquo;Forest Sounds\u0026rdquo; at night. Over time, we became used to this, and found it filters out street noises and other nighttime distractions.\nWith this new setup, I was looking for a way to have a completely automated, \u0026ldquo;hands-free\u0026rdquo;, solution that would turn on Groove Salad in the morning, and turn on some kind of forest sounds at night. Something that just worked, and that I didn\u0026rsquo;t have to even think about.\nMopidy has a REST API that you can use to control it remotely via HTML POST transactions. It\u0026rsquo;s not particularly well documented. At least, it looks like documentation was started, but never completed. However, there is enough there to figure out how to do some interesting and useful things.\nIn order to investigate how to use the API, on my workstation I downloaded an application called, \u0026ldquo;Bruno\u0026rdquo;, which is an open source alternative to \u0026ldquo;Postman\u0026rdquo;. This application allows you to create REST API calls that you can test and see the results in real time.\nThat\u0026rsquo;s what I did with Mopidy. I poked around trying various API calls to see what would happen, and to figure out the correct syntax and structure. Eventually, I had a number of tests written that would control the Mopidy server, and that would start and stop both GrooveSalad and my forest sounds MP3.\nOne of the nice things about Bruno is that you can export the tests as stand-alone Bash scripts (as well as code in a few programming languages). I copied them out and then put them in /usr/local/bin/mopidy on the Mopidy/Snapcast LXC. Let\u0026rsquo;s have a look at a couple of them:\n$ cat clearTrackList curl --request POST \\ --url http://mopidy.mydomain:6680/mopidy/rpc \\ --header \u0026#39;content-type: application/json\u0026#39; \\ --data \u0026#39;{ \u0026#34;jsonrpc\u0026#34;: \u0026#34;2.0\u0026#34;, \u0026#34;id\u0026#34;: 1, \u0026#34;method\u0026#34;: \u0026#34;core.tracklist.clear\u0026#34; }\u0026#39; This script clears the tracklist out of Mopidy.\n$ cat addGrooveSalad curl --request POST \\ --url http://mopidy.mydomain:6680/mopidy/rpc \\ --header \u0026#39;content-type: application/json\u0026#39; \\ --data \u0026#39;{ \u0026#34;jsonrpc\u0026#34;: \u0026#34;2.0\u0026#34;, \u0026#34;id\u0026#34;: 1, \u0026#34;method\u0026#34;: \u0026#34;core.tracklist.add\u0026#34;, \u0026#34;params\u0026#34;: { \u0026#34;uris\u0026#34;: [ \u0026#34;somafm:channel:/groovesalad\u0026#34; ] } }\u0026#39; This puts the GrooveSalad channel from SomaFM into the playlist.\n$ cat startPlayback curl --request POST \\ --url http://mopidy.mydomain:6680/mopidy/rpc \\ --header \u0026#39;content-type: application/json\u0026#39; \\ --data \u0026#39;{ \u0026#34;jsonrpc\u0026#34;: \u0026#34;2.0\u0026#34;, \u0026#34;id\u0026#34;: 1, \u0026#34;method\u0026#34;: \u0026#34;core.playback.play\u0026#34; }\u0026#39; This script starts the playback.\n$ cat startGrooveSalad /usr/local/bin/mopidy/clearTrackList /usr/local/bin/mopidy/addGrooveSalad /usr/local/bin/mopidy/startPlayback Finally, this one calls the other three back-to-back to start GrooveSalad playing.\nHow to run these? Well, cron of course! [Note: On a later project that I used ChatGPT to troubleshoot, it told me that cron is old-fashioned and that there is a more modern approach using systemd. You may want to pursue that, but I find that cron works just fine. What can I say? I\u0026rsquo;m a dinosaur.]\nI\u0026rsquo;m not going to go into cron in depth, but for those of you who have never heard of it, cron is a service that will run whatever you want on a schedule that you set up. You can specify certain days of the week or of the month, certain minutes of the hour or particular hours that you want your action to run.\nI needed two actions, one to start up GrooveSalad in the morning, and one to start up the forest sounds at night:\n$ sudo crontab -l # Edit this file to introduce tasks to be run by cron. # # Each task to run has to be defined through a single line # indicating with different fields when the task will be run # and what command to run for the task # # To define the time you can provide concrete values for # minute (m), hour (h), day of month (dom), month (mon), # and day of week (dow) or use \u0026#39;*\u0026#39; in these fields (for \u0026#39;any\u0026#39;). # # Notice that tasks will be started based on the cron\u0026#39;s system # daemon\u0026#39;s notion of time and timezones. # # Output of the crontab jobs (including errors) is sent through # email to the user the crontab file belongs to (unless redirected). # # For example, you can run a backup of all your user accounts # at 5 a.m every week with: # 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/ # # For more information see the manual pages of crontab(5) and cron(8) # # m h dom mon dow command 30 7 * * * /usr/local/bin/mopidy/startGrooveSalad 0 21 * * * /usr/local/bin/mopidy/startNatureSounds I\u0026rsquo;ve left in the default comments so that it will look like what you\u0026rsquo;ll see if you do this on your own system.\nThe easiest way to edit /etc/crontab is to just type sudo crontab -e and it\u0026rsquo;ll get you there. We need this to run as root, so sudo is used here. The command crontab -l just lists the crontab file. These two commands run every day, the first one at 7:30 AM, and the second at 9:00 PM.\nHandling Stream Interuptions # The last issue that I had was due to connection issues from SomaFM which caused the stream to stop momentarily. This caused the Mopidy server to throw an error and stop:\nMar 31 15:24:31 snapcast mopidy[25889]: ERROR [MainThread] mopidy.audio.gst GStreamer error: Internal data stream error. or:\nMar 30 07:30:18 snapcast mopidy[25889]: ERROR [MainThread] mopidy.audio.gst GStreamer error: Server does not support seeking. I wish that Mopidy or its SomaFM plugin would be more robust to these disruptions, or have some self-recovery feature built in, but it does not. That meant it was up to me to figure out how to detect these stoppages and restart the stream.\nThe first thing I did was to look for something in the API that would tell me that the stream had stopped. I was hopeful that the \u0026ldquo;status\u0026rdquo; command would be the answer. Unfortunately, when I tested this I found that the status was still listed as \u0026ldquo;playing\u0026rdquo;, from which I assume that the error that stopped stream wasn\u0026rsquo;t causing it to go into some elegant shutdown that updated the status.\nLuckily, I had decided to connect Mopidy to SnapCast via a named pipe. Perhaps if I checked the last update time of that pipe I could see if the stream had stopped?\nThis turned out easy to check. The pipe was at /var/local/snapfifo and a listing shows that it\u0026rsquo;s date and time is right now:\n$ ll snapfifo prw-rw-rw- 1 snapserver snapserver 0 Apr 3 14:54 snapfifo If I stop the stream, then the date/time stops updating. So it looks like this is going to be an answer. I came up with this script:\n$ cat checkPlaybackState #!/bin/bash if (((`date +%s` - `date +%s -r /var/local/snapfifo`) \u0026lt; \u0026#34;5\u0026#34;)) then echo \u0026#34;`date` Still going\u0026#34; \u0026gt;\u0026gt; /var/local/playbackcheck else echo \u0026#34;`date` Not running\u0026#34; \u0026gt;\u0026gt; /var/local/playbackcheck /usr/local/bin/mopidy/startGrooveSalad fi Eventually, I removed the echo commands, since they really aren\u0026rsquo;t needed. In my running version, I have a line that submits a message to my Gotify server in the event that it detects that the stream has stopped.\nI need to run it throughout the day, which means more cron to the rescue:\n*/3 8-20 * * * /usr/local/bin/mopidy/checkPlaybackState The */3 means every 3 minutes, and the 8-20 means between 8:00 AM and 8:00 PM. These hours are inclusive, so the last run will be at 20:57, or 8:57 PM. Then, at 9:00 PM the forest sounds start and that is entirely local, so there\u0026rsquo;s no need to check for stream issues.\nConclusion # Success in these kinds of projects is ususally evaluated by how happy my wife is with how it works. She was getting quite fed up with the Google issues that we were experiencing, and frustrated with the constant drops in the service and times when it simply wouldn\u0026rsquo;t start up again.\nShe considers this to be one the biggest successes of my self-hosting project so far! Hurray!\nIt\u0026rsquo;s a success because it just works, and it never forces its way into the foreground by making you fiddle with it to get it to work. It starts up the music in the morning and the forest sounds at night and you don\u0026rsquo;t have to even think about it. It\u0026rsquo;s just there.\nTo be sure, there were some hiccoughs to start with\u0026hellip;\nThe Raspberry Pi Zero has a woefully weak WiFi tranceiver in it. This caused grief until I sorted out procurement and placement of WiFi access points around the house. Symptoms of this problem were sporadic pauses in the audio of up to 2 seconds.\nThe SnapCast system seems to be dependent on stable connectivity between the server and the clients. At first, I had one of my WiFi access points connected to the wired network through a powerline AV connector. These are notoriously quirky, and can suffer interruptions if someone runs a heavy appliance like a clothes washer or dryer in the house. Once again, the symptoms were sporadic pauses in the audio.\nWhen I shifted AP connectivity over to MoCa (Ethernet over co-ax), all of the issues with sporadic audio pauses largely vanished.\nNow it just runs. If something goes wrong, it fixes itself. Most of the time, the only reason I\u0026rsquo;m even aware that it had a problem is because I get a Gotify alert for it. But even this only happens rarely.\n","date":"22 June 2026","externalUrl":null,"permalink":"/homelab/snapcast2/","section":"Homelabs","summary":"The final installment of a 3 part series describing how to set up whole-home audio using SnapCast, Mopidy and Rasberry Pi’s.  In this article, we look at how to install and configure Mopidy as a streaming service, and how to connect it to a SnapCast server.","title":"Whole Home Audio With SnapCast - Part III","type":"homelab"},{"content":" Introduction # The ComobBox! Everybody\u0026rsquo;s go-to control for multiple choice input. It\u0026rsquo;s one of those elements that\u0026rsquo;s dead simple to get started with, but has a lot of power that many people don\u0026rsquo;t use.\nIn this article we are going to cover the absolute basics, and then take a look at some slightly more involved situations that seem to come up all the time. We\u0026rsquo;ll also see how easy it is to massage the presentation of the selectable items in your ComboBox to make it more user-friendly.\nSimple Implementation # The easiest way to use a ComboBox is to just stuff some values in a list, bind the ValueProperty and stick it in a layout:\nclass ComboBoxExample : Application() { private val comboBoxValue: StringProperty = SimpleStringProperty(\u0026#34;\u0026#34;) override fun start(stage: Stage) { val scene = Scene(createContent()).apply { addWidgetStyles() } stage.scene = scene stage.show() } private fun createContent(): Region = VBox(20.0).apply { children += Label().apply { textProperty().bind(comboBoxValue.map { \u0026#34;The selected value is: $it\u0026#34; }.orElse(\u0026#34;No value has been selected\u0026#34;)) } children += ComboBox\u0026lt;String\u0026gt;().apply { items += listOf(\u0026#34;Fred\u0026#34;, \u0026#34;George\u0026#34;, \u0026#34;Mary\u0026#34;, \u0026#34;Jane\u0026#34;) comboBoxValue.bind(valueProperty()) } padding = Insets(40.0) } } fun main() = Application.launch(ComboBoxExample::class.java) Note the use of ObservableValue.map() and ObservableValue.orElse() to translate the raw value from the ComboBox to the text for the Label. This is new since JavaFX 19, and it greatly simplifies a lot of bindings.\nIt starts out with a Null value, so the ComboBox just looks like an empty Button with an arrow. Click anywhere on it and it opens up with a list of choices for the user to click on:\nPicking one then populates the Button, and closes the pop-up list:\nStandard Configuration Options # Editable ComboBoxes work in two modes, editable and non-editable. \u0026ldquo;Editable\u0026rdquo; means that the user can type into the area in the ComboBox that looks like a TextField and enter values that aren\u0026rsquo;t in the pop-up list. The default is non-editable. Items The contents of of the pop-up list are controlled by an ObservableList called items. VisibleRowCount This is the maximum size of the pop-up list, in rows. The default is 10, but you can make it bigger or smaller, depending on your layout. Value The currently selected value in the ComboBox. This can be set programmatically if, for instance, you want to set a default value. Note that if you do change the value programmatically, it will automatically select the corresponding value in the pop-up list. The value does NOT have to be one of the values in the pop-up list, however. OnAction You can trigger an EventHandler whenever the ValueProperty of the ComboBox is changed. This is the OnAction event. The pop-up list works very much like a ListView and does, therefore, have a SelectionModel. For some reason, and I don\u0026rsquo;t know why (probably some copypasta that ended up in every on-line tutorial), people seem to think that the way to get a ComboBox's value is to go through the pop-up list SelectionModel. In reality, there\u0026rsquo;s almost never any reason to go through this round-about route - just use the Value property.\nComboBox\u0026lt;Object\u0026gt; # So what if you don\u0026rsquo;t want to have your ComboBox return a primitive like int, or a String? How do you do that? ComboBox is a generic class, so to use one you\u0026rsquo;ll need to specify the type of data values that it will return, and that will be held in items.\nFirst off, though, you need to remember that the items that populate the list in a ComboBox should be part of your Presentation Model, and they should be designed with the intent that they are going to be used to support your view. This means that the idea that you\u0026rsquo;re just going to rip some customer records out of a database and dump them into a ComboBox as the choices is probably a bad design decision.\nThat being said, there are times when you might have a more complex object used to populate your ComboBox. It\u0026rsquo;s generally considered bad form to rely on toString() to create the display for your ComboBox, so you\u0026rsquo;ll need to customize it in some way to make it work properly. This means that you\u0026rsquo;re going to have to treat the popup as a ListView (which it is) and customize the cells.\nIn this example, we\u0026rsquo;re going to look at using an object with two fields. One of the easiest ways to do that is to use an Enum class, so that\u0026rsquo;s what we\u0026rsquo;ll do.\nNo surprise, we\u0026rsquo;re going to use the same Enum that we used in the ListView article, because the popup is pretty much just a ListView:\nclass ComboBoxExample1 : Application() { private val comboBoxValue: ObjectProperty\u0026lt;Animal\u0026gt; = SimpleObjectProperty() override fun start(stage: Stage) { val scene = Scene(createContent()).apply { addWidgetStyles() } stage.scene = scene stage.show() } private fun createContent(): Region = VBox(20.0).apply { children += Label().apply { textProperty().bind(comboBoxValue.map { \u0026#34;The selected value is: $it\u0026#34; }.orElse(\u0026#34;No value has been selected\u0026#34;)) } children += ComboBox\u0026lt;Animal\u0026gt;().apply { items += listOf(Animal.BEAR, Animal.OWL, Animal.BIRD, Animal.SHARK, Animal.HEDGEHOG, Animal.LOBSTER) comboBoxValue.bind(valueProperty()) } padding = Insets(40.0) } } enum class Animal(val animalName: String, imageName: String) { BEAR(\u0026#34;Bear\u0026#34;, \u0026#34;bear.png\u0026#34;), BIRD(\u0026#34;Bird\u0026#34;, \u0026#34;bird.png\u0026#34;), HEDGEHOG(\u0026#34;Hedgehog\u0026#34;, \u0026#34;hedgehog.png\u0026#34;), LOBSTER(\u0026#34;Lobster\u0026#34;, \u0026#34;lobster.png\u0026#34;), OWL(\u0026#34;Owl\u0026#34;, \u0026#34;owl.png\u0026#34;), SHARK(\u0026#34;Shark\u0026#34;, \u0026#34;shark.png\u0026#34;); val imageView: ImageView = ImageView(this::class.java.getResource(imageName)?.toExternalForm()) override fun toString(): String { return animalName } } fun main() = Application.launch(ComboBoxExample1::class.java) Which looks like this:\nHere, we are just letting the Animal.toString() do the job of populating the pop-up contents. But, what if we want to have the image associated with the Animal appear in the ComboBox? We have to create a custom CellFactory for this:\nchildren += ComboBox\u0026lt;Animal\u0026gt;().apply { items += listOf(Animal.BEAR, Animal.OWL, Animal.BIRD, Animal.SHARK, Animal.HEDGEHOG, Animal.LOBSTER) comboBoxValue.bind(valueProperty()) setCellFactory { ListCell\u0026lt;Animal\u0026gt;().apply { graphicProperty().bind(itemProperty().map { it.imageView }) textProperty().bind(itemProperty().map { it.animalName }) } } } This is an approach you don\u0026rsquo;t see in most tutorials. There\u0026rsquo;s no custom class extending ListCell here.\nRemember that ListCell is a subclass of Labeled, which means that its presentation can work very much like a standard Label. Specifically, we have both a Graphic and a Text element, and we have a graphicProperty() and a textProperty() method. If we bind those properties to data in item, then they\u0026rsquo;ll just show up on the screen and it will work.\nSince we\u0026rsquo;re not going to override updateItem(), nor are we going to customize the layout of the ListCells, we don\u0026rsquo;t have to create a custom ListCell class, not even an anonymous inner class. We can just instantiate a plain vanilla ListCell and configure it from outside, using the methods already available from ListCell.\nAnd it looks like this:\nBut, you can see that there is a bit of a problem. The main part of the CheckBox doesn\u0026rsquo;t have the image. This area is called the Button and it has a ListCell in it, just like the pop-up ListView does. We can set it, too, although it doesn\u0026rsquo;t need a factory but just an instance of the ListCell:\nchildren += ComboBox\u0026lt;Animal\u0026gt;().apply { items += listOf(Animal.BEAR, Animal.OWL, Animal.BIRD, Animal.SHARK, Animal.HEDGEHOG, Animal.LOBSTER) comboBoxValue.bind(valueProperty()) setCellFactory { createCell() } buttonCell = createCell() } . . . private fun createCell() = ListCell\u0026lt;Animal\u0026gt;().apply { itemProperty().subscribe(Consumer { graphic = it?.imageView text = it?.animalName }) } In order to keep with DRY (Don\u0026rsquo;t Repeat Yourself), we\u0026rsquo;ve pulled the ListCell instantiation into its own method so that we can call if from the factory, and then directly to set the Button. However, there was a problem when trying to bind the Graphic and Text properties to Item fields. It turns out that the inner workings of the ComboBox skin actually set the values of Graphic and Text in the Button component directly in some circumstances, and the binding causes grief with that. So the bindings have been turned into a Subscription that can co-exist nicely with ComboBox skin code.\nNow, it looks like this:\nUh oh! Where did the image go for the shark in the list? Since the Enum contains an actual ImageView which is a Node, and each Node can only appear on the SceneGraph once, it gets removed from the pop-up and put into the Button. To fix that, we\u0026rsquo;ll need to change the design of Animal (honestly, having ImageView in Animal was probably a bad idea to start with):\nclass ComboBoxExample1 : Application() { private val comboBoxValue: ObjectProperty\u0026lt;Animal\u0026gt; = SimpleObjectProperty() override fun start(stage: Stage) { val scene = Scene(createContent(), 280.0, 300.0).apply { addWidgetStyles() } stage.scene = scene stage.show() } private fun createContent(): Region = VBox(20.0).apply { children += Label().apply { textProperty().bind(comboBoxValue.map { \u0026#34;The selected value is: $it\u0026#34; }.orElse(\u0026#34;No value has been selected\u0026#34;)) } children += ComboBox\u0026lt;Animal\u0026gt;().apply { items += listOf(Animal.BEAR, Animal.OWL, Animal.BIRD, Animal.SHARK, Animal.HEDGEHOG, Animal.LOBSTER) comboBoxValue.bind(valueProperty()) setCellFactory { createCell() } buttonCell = createCell() } padding = Insets(40.0) } private fun createCell() = ListCell\u0026lt;Animal\u0026gt;().apply { val imageView = ImageView() itemProperty().subscribe(Consumer { imageView.image = it?.image graphic = imageView text = it?.animalName }) } } enum class Animal(val animalName: String, imageName: String) { BEAR(\u0026#34;Bear\u0026#34;, \u0026#34;bear.png\u0026#34;), BIRD(\u0026#34;Bird\u0026#34;, \u0026#34;bird.png\u0026#34;), HEDGEHOG(\u0026#34;Hedgehog\u0026#34;, \u0026#34;hedgehog.png\u0026#34;), LOBSTER(\u0026#34;Lobster\u0026#34;, \u0026#34;lobster.png\u0026#34;), OWL(\u0026#34;Owl\u0026#34;, \u0026#34;owl.png\u0026#34;), SHARK(\u0026#34;Shark\u0026#34;, \u0026#34;shark.png\u0026#34;); val image: Image = Image(this::class.java.getResource(imageName)?.toExternalForm()) override fun toString(): String { return animalName } } fun main() = Application.launch(ComboBoxExample1::class.java) Now we have the Animal holding an Image, which is not a Node, and then loading that Image into an ImageView that is associated with each ListCell.\nAnd it looks like this:\nCodes and Descriptions # One situation that pops up all the time is where you have some sort of list of codes, and you want to display some descriptive text about the code instead of the code in your ComboBox. But you still want to return the code from the ComboBox selection. How do you do this?\nA lot of programmers try using a Map\u0026lt;Code, Description\u0026gt; to establish the relationship between the code and its description. But then it\u0026rsquo;s hard to go on from there.\nHonestly, using a Map instead of creating a class to hold a value pair is really not a great strategy to begin with. JavaFX has a general purpose class called Pair that you can use for this purpose. It has two fields, key and value and all the methods you might need with it. So you could create a List\u0026lt;Pair\u0026lt;Code, Description\u0026gt;\u0026gt; just as easily as you could a Map.\nSo let\u0026rsquo;s use ObservableList\u0026lt;Pair\u0026gt;\u0026hellip;\nclass ComboBoxExample3 : Application() { private val comboBoxValue: ObjectProperty\u0026lt;Pair\u0026lt;String, String\u0026gt;\u0026gt; = SimpleObjectProperty() override fun start(stage: Stage) { val scene = Scene(createContent(), 280.0, 300.0).apply { addWidgetStyles() } stage.scene = scene stage.show() } private fun createContent(): Region = VBox(20.0).apply { children += Label().apply { textProperty().bind(comboBoxValue.map { \u0026#34;The selected value is: ${it.key}\u0026#34; } .orElse(\u0026#34;No value has been selected\u0026#34;)) } children += ComboBox\u0026lt;Pair\u0026lt;String, String\u0026gt;\u0026gt;().apply { items += listOf( Pair(\u0026#34;GB\u0026#34;, \u0026#34;Great Britain\u0026#34;), Pair(\u0026#34;FR\u0026#34;, \u0026#34;France\u0026#34;), Pair(\u0026#34;AU\u0026#34;, \u0026#34;Australia\u0026#34;), Pair(\u0026#34;ES\u0026#34;, \u0026#34;Spain\u0026#34;) ) comboBoxValue.bind(valueProperty()) setCellFactory { ListCell\u0026lt;Pair\u0026lt;String, String\u0026gt;\u0026gt;().apply { textProperty().bind(itemProperty().map { it.value }) } } buttonCell = ListCell\u0026lt;Pair\u0026lt;String, String\u0026gt;\u0026gt;().apply { itemProperty().subscribe(Consumer { text = it?.value }) } } padding = Insets(40.0) } } fun main() = Application.launch(ComboBoxExample3::class.java) Which looks like this:\nThe key point here is that it doesn\u0026rsquo;t take a mountain of code to populate your ComboBox values with just the description, while still retaining the code in ComboBox.value. All you need is a standard ListCell and binding its textProperty() to a Binding derived from its itemProperty() extracting the Pair.value field does the trick.\nThis technique will work with much more complex objects, too. Just set up a Binding that composes a String made up from whatever class you are putting into the ComboBox.\nAt the same time, you can still use Map, in which case you\u0026rsquo;ll need to put the Map.entrySet() into an ObservableList. Map.Entry has key and value as well.\nLinking ComboBoxes # Something that comes up fairly often is the idea of having two ComboBoxes that are somehow connected to each other. Changing the selection in one ComboBox changes the list of items available in the second ComboBox.\nWe\u0026rsquo;ll go back to our Animal based ComboBox and then add another that has names of individual animals making the screen a \u0026ldquo;Pick your favourite animal at the zoo\u0026rdquo; application. Let\u0026rsquo;s look at the code first, then see how it works:\nclass ComboBoxExample2 : Application() { private val animalValue: ObjectProperty\u0026lt;Animal\u0026gt; = SimpleObjectProperty() private val nameValue: ObjectProperty\u0026lt;String\u0026gt; = SimpleObjectProperty() override fun start(stage: Stage) { val scene = Scene(createContent(), 400.0, 300.0).apply { addWidgetStyles() } stage.scene = scene stage.show() } private fun createContent(): Region = VBox(20.0).apply { children += Label().apply { textProperty().bind(FavouriteAnimalBinding(animalValue, nameValue)) } children += HBox(14.0).apply { children += ComboBox\u0026lt;Animal\u0026gt;().apply { items += listOf(Animal.BEAR, Animal.OWL, Animal.BIRD, Animal.SHARK, Animal.HEDGEHOG, Animal.LOBSTER) animalValue.bind(valueProperty()) setCellFactory { createCell() } buttonCell = createCell() } children += ComboBox\u0026lt;String\u0026gt;().apply { val zooAnimals = populateZooAnimals() itemsProperty().bind(animalValue.map { zooAnimals[it] }) nameValue.bind(valueProperty()) placeholder = Label(\u0026#34; Pick an Animal first \u0026#34;) } } padding = Insets(40.0) } private fun populateZooAnimals(): Map\u0026lt;Animal, ObservableList\u0026lt;String\u0026gt;\u0026gt; = mapOf\u0026lt;Animal, ObservableList\u0026lt;String\u0026gt;\u0026gt;( Animal.OWL to FXCollections.observableArrayList(\u0026#34;Hoodini\u0026#34;, \u0026#34;Olive Owl\u0026#34;, \u0026#34;Whoolio\u0026#34;, \u0026#34;Barney\u0026#34;), Animal.SHARK to FXCollections.observableArrayList(\u0026#34;Striper\u0026#34;, \u0026#34;Steve\u0026#34;, \u0026#34;Freddy\u0026#34;, \u0026#34;Sushi\u0026#34;), Animal.LOBSTER to FXCollections.observableArrayList(\u0026#34;Clawdia\u0026#34;, \u0026#34;Kevin\u0026#34;, \u0026#34;Butter\u0026#34;), Animal.BEAR to FXCollections.observableArrayList(\u0026#34;Gummi\u0026#34;, \u0026#34;Paddington\u0026#34;, \u0026#34;Baloo\u0026#34;, \u0026#34;Ted\u0026#34;), Animal.BIRD to FXCollections.observableArrayList(\u0026#34;Tweety\u0026#34;, \u0026#34;Comet\u0026#34;, \u0026#34;Chickpea\u0026#34;, \u0026#34;Cracker\u0026#34;), Animal.HEDGEHOG to FXCollections.observableArrayList(\u0026#34;Sonic\u0026#34;, \u0026#34;Spike\u0026#34;, \u0026#34;Whoolio\u0026#34;, \u0026#34;Herbert\u0026#34;) ) private fun createCell() = ListCell\u0026lt;Animal\u0026gt;().apply { val imageView = ImageView() itemProperty().subscribe(Consumer { imageView.image = it?.image graphic = imageView text = it?.animalName }) } } class FavouriteAnimalBinding(private val animal: ObservableValue\u0026lt;Animal?\u0026gt;, val name: ObservableValue\u0026lt;String?\u0026gt;) : StringBinding() { init { super.bind(animal, name) } override fun computeValue(): String { val favString = \u0026#34;My favourite animal at the zoo is\u0026#34; return animal.value?.let { animalType -\u0026gt; name.value?.let { animalName -\u0026gt; \u0026#34;$favString $animalName the $animalType\u0026#34; } ?: \u0026#34;$favString a $animalType\u0026#34; } ?: \u0026#34;I haven\u0026#39;t picked a favourite yet\u0026#34; } } fun main() = Application.launch(ComboBoxExample2::class.java) Here\u0026rsquo;s what it looks like at launch:\nAs you pick an Animal:\nAs you select a name:\nAnd once a name has been selected:\nThe trick with this is to treat the ObservableList of the second ComboBox as a Property itself, which ComboBox gives us as itemsProperty(). This is an ObjectProperty\u0026lt;ObservableList\u0026lt;String\u0026gt;\u0026gt;, which means that we can bind it. Do do that, we use ObservableValue.map() to extract an ObservableList from a Map\u0026lt;Animal,ObservableList\u0026gt;.\nEach entry in our Map is keyed with an Animal, and the value is an ObservableList\u0026lt;String\u0026gt; of names of particular animals of that type. The ObservableValue.map() function here just pulls the List from the Map using the current Animal value selected in the first ComboBox. An equivalent way to do this in a version of JavaFX earlier than 19 would be Bindings.valueAt().\nOne last feature you should probably include is a Placeholder on the second ComboBox. This is what will be shown if the ListView in the pop-up is empty - as it will be when an Animal has not yet been selected. In this case, we\u0026rsquo;re just showing a Label that instructs the user to pick an animal first:\nThis is done with this line of code:\nplaceholder = Label(\u0026#34; Pick an Animal first \u0026#34;) Editable ComboBoxes # Making a ComboBox editable means that the user can type in the Button cell and enter a value that potentially isn\u0026rsquo;t in the drop-down list. Here\u0026rsquo;s what happens when we make the ComboBox for name editable:\nThat ComboBox now looks like a TextField with a twisty Button beside it. When we type in something and hit \u0026lt;Enter\u0026gt; this is what happens:\nAnd you can see that none of the items in the drop-down are selected:\nIf we change it to one of the items in the drop-down, then the ComboBox makes the connection and highlights it for us:\nThe connection isn\u0026rsquo;t made until you commit the change by hitting \u0026lt;Enter\u0026gt; or taking the focus away from the ComboBox. You\u0026rsquo;re going to have to trust me on this, as I can\u0026rsquo;t get a screenshot without taking focus away from ComboBox.\nConclusion # And that\u0026rsquo;s it for our tour of the basics of ComboBoxes. With the information here you can probably handle the vast majority of ComboBox situations that come up when designing a JavaFX application.\nThe main things to keep in mind:\nGrab the value from ComboBox.valueProperty(), don\u0026rsquo;t go digging into the SelectionModel to get the selected item. This is especially true if your ComboBox is editable.\nYou can use the Labeled nature of ListCell to do a fair bit of manipulation of the presentation of your items without having to extend ListCell into a custom class.\nYou can use a generic class like Pair to display human-friendly names for items that are codes. Also, Enum can be really useful for this too.\n","date":"30 August 2024","externalUrl":null,"permalink":"/javafx/elements/comboboxes/","section":"JavaFX","summary":"The ComboBox is the classic “choose from a list” screen widget that you see everywhere. In JavaFX, the ComboBox is a fairly complex structure that involves a TextBox and a ListView tied together. In this article you’ll learn how to configure the ComboBox to fit a variety of uses.","title":"Understanding the ComboBox","type":"javafx"},{"content":" Introduction # In Part 1 of this series, we looked at the Observable classes designed to wrap aribitrary Object values, and in Part II we looked at the typed Observable classes. In this article we are going to look at the Observables designed to hold ObservableLists.\nThis is a subject that always confused me. Aren\u0026rsquo;t ObserableLists already Observable? And doesn\u0026rsquo;t ObseravbleList already implement the Obseravble interface? So why wrap an ObservableList inside an Observable wrapper?\nThe whole thing, at first glance, seems very circular. We have an Observable that wraps an ObservableList and supports these same methods as ObservableList. What\u0026rsquo;s the point?\nThe point is that this turns out to be awesomely cool! And not just cool, but super useful, too. In this article we\u0026rsquo;ll first look at how ListProperty behaves, then we\u0026rsquo;ll look at how the various interfaces and classes go together, and then we\u0026rsquo;ll take a look at some practical examples.\nAnd, by the way, everything about ListProperty is also true for MapProperty and SetProperty too.\nObservableList Observables in Action # The reality is, you\u0026rsquo;ve already used a Property that wraps an ObseravbleList if you have ever used TableView. That\u0026rsquo;s because the items field in TableView is an ObjectProperty\u0026lt;ObservableList\u0026lt;S\u0026gt;\u0026gt;. But it\u0026rsquo;s highly unlikely that you\u0026rsquo;ve ever used this as a Property, meaning that you\u0026rsquo;ve probably never put a Listener on it or bound it to or from anything. And even then, ObjectProperty\u0026lt;ObservableListe\u0026lt;S\u0026gt;\u0026gt; is still not ListProperty\u0026lt;S\u0026gt;, which is where the real coolness lies.\nWe should have a closer look at what this does.\nThe best way to figure out the value of List Obseravbles is to see one in action\u0026hellip;\nThe Basic Scenario # Here\u0026rsquo;s a small program that can test how ObjectProperty\u0026lt;ObservableList\u0026lt;String\u0026gt;\u0026gt; works:\nclass Example1 : Application() { @Throws(Exception::class) override fun start(primaryStage: Stage) { val mainScene = Scene(createContent(), 840.0, 700.0) primaryStage.scene = mainScene primaryStage.show() } private fun createContent(): Region = VBox(20.0).apply { val list1 = FXCollections.observableArrayList(\u0026#34;abc\u0026#34;, \u0026#34;def\u0026#34;, \u0026#34;ghi\u0026#34;) val list2 = FXCollections.observableArrayList(\u0026#34;jkl\u0026#34;, \u0026#34;mno\u0026#34;) val messages = SimpleStringProperty(\u0026#34;\u0026#34;) val listProperty = SimpleListProperty(list1) listProperty.subscribe { oldVal, newVal -\u0026gt; messages.value += \u0026#34;Change -\u0026gt; Old: $oldVal New: $newVal\\n\u0026#34; } list1.subscribe { messages.value += \u0026#34;List1 Invalidated\\n\u0026#34; } children += Label().apply { textProperty().bind(listProperty.sizeProperty().map { \u0026#34;List Size: $it\u0026#34; }) } children += TextArea().apply { textProperty().bind(messages) prefHeight = 300.0 } children += Button(\u0026#34;Add Element to List1\u0026#34;).apply { setOnAction { messages.value += \u0026#34;Add Element to List1 clicked\\n\u0026#34; list1.add(\u0026#34;xyz\u0026#34;) } } children += Button(\u0026#34;Swap to List2\u0026#34;).apply { setOnAction { messages.value += \u0026#34;Swap List clicked\\n\u0026#34; listProperty.set(list2) } } padding = Insets(30.0) } } fun main() = Application.launch(Example1::class.java) Data-wise, we have two ObservableArrayLists\u0026lt;String\u0026gt; called list1 and list2, and a SimpleListProperty called listProperty. We start off with list1 loaded into listProperty.\nOn the screen, we have a Label() with its textProperty bound to listProperty.sizeProperty(). Then we have a TextArea with its textProperty bound to a StringProperty called messages.\nWe\u0026rsquo;ve added a Subscription to the listProperty to add a line to messages whenever its value changes. Then we\u0026rsquo;ve added a Subscription to list1 to tell us whenever it becomes invalidated.\nFinally, we have two Buttons. The first adds a line to messages and adds a new element to list1. The second swaps list2 into listProperty after adding a line to messages so that we can tell when it was clicked.\nLet\u0026rsquo;s see what happens. First we\u0026rsquo;ll click the first Button a couple of times:\nYou can see that the list size of 5 matches the size of list1 which is still loaded into listProperty. You can see the two messages from clicking the first Button twice, and you can see the two \u0026ldquo;Invalidation\u0026rdquo; messages from the Listener on list1.\nThe really interesting part is that you can see the messages from the Subscription on listProperty. But we didn\u0026rsquo;t change the value of listProperty!\nThis tells us the changes to the ObseravbleList in a ListProperty trigger a change in the ListProperty itself! But you can see that both oldVal and newVal are still the same thing. So it\u0026rsquo;s not like we get a snapshot of what the ObservableList looked like before and after the change.\nThen we\u0026rsquo;ll click on the second Button and then the first Button a few more times:\nYou can see the message where the second Button was clicked, and then the Subscription message. Now we have different values for oldVal and newVal. You can also see that the listProperty.sizeProperty() has changed from the Label at the top.\nNext the first Button was clicked twice more, and you can see the messages from the Invalidation Subscription on list1. Interestingly, even though we haven\u0026rsquo;t done any getValue() operations on list1 it seems to be revalidating itself, because the second click also triggered the Subscription.\nAdding a ListChangeListener # There\u0026rsquo;s another thing we can try. ListProperty supports ObseravbleList.addListener(ListChangeListener) which will show us actual changes.\nLet\u0026rsquo;s see if we swap out the Subscription on listProperty with one of those:\nlistProperty.addListener(ListChangeListener { change -\u0026gt; messages.value += \u0026#34;listProperty change: $change \\n\u0026#34; }) And we\u0026rsquo;ll add another Button to add an element to list2:\nchildren += Button(\u0026#34;Add Element to List2\u0026#34;).apply { setOnAction { messages.value += \u0026#34;Add Element to List2 clicked\\n\u0026#34; list2.add(\u0026#34;xyz\u0026#34;) } } And this is what happens:\nWe start off by clicking on the first Button and we see list1 invalidated and the ListChangeListener on listProperty fires and adds its message. Then we click on the Button to add an element to list2, which generates no new messages other than the Button click message. We click the first Button again, and see all the messages that we expect to see.\nThen we click the \u0026ldquo;Swap\u0026rdquo; Button and we see the ListChangeListener on listProperty fire again! It tells us that the entire ObseravbleList has been changed by showing us the elements (all of them) being replaced. Notice that this is fundamentally different from the way that the old Subscription worked by showing us the change in the wrapper value.\nNext, the first Button was clicked again. This generated the click message, and the list1 invalidation message, but no messages from the ListChangeListener. So we can see that the ListChangeListener didn\u0026rsquo;t just get passed on down to list1 through listProperty.\nFinally, the Button to add an element to list2 was clicked. We see the click message, and then a message from the ListChangeListener on listProperty, telling us that a new element was added to listProperty.\nAdding Elements to the Wrapper Observable # ListProperty also supports the List.add() method. What happens if we try that.\nWe\u0026rsquo;ll change the \u0026ldquo;Add to List1\u0026rdquo; Button to add to listProperty instead. Then we\u0026rsquo;ll take away \u0026ldquo;Add to List2\u0026rdquo; and put an Invalidation Subscription on list2:\nchildren += Button(\u0026#34;Add Element to listProperty\u0026#34;).apply { setOnAction { messages.value += \u0026#34;Add Element to listProperty clicked\\n\u0026#34; listProperty.add(\u0026#34;xyz\u0026#34;) } } Let\u0026rsquo;s see what happens:\nYou can see the \u0026ldquo;Add\u0026rdquo; Button is clicked twice, and we get the messages that we would expect; list1 is invalidated and the change on listProperty is shown both times.\nThen the \u0026ldquo;Swap\u0026rdquo; Button is clicked and we see the click message and the change of all of the elements in listProperty displays. No InvalidationListeners fire.\nFinally the \u0026ldquo;Add\u0026rdquo; Button is clicked twice more and we see that list2 is invalidated and the change on listProperty displays twice.\nWhat This Means # In our examples, we see how a ListProperty acts exactly like a Property\u0026lt;ObseravbleList\u0026gt; but also exactly like the ObservableList that is currently in its value.\nNow let\u0026rsquo;s take a look at how all of the classes and elements hook up together\u0026hellip;\nThe List Classes and Interfaces # We\u0026rsquo;ll start off with the chart, which looks quite a bit like the charts from the first two parts of this series:\nHere you can see that all of the generic \u0026lt;T\u0026gt;\u0026rsquo;s have been replaced with \u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; and the typed entries are of \u0026lt;E\u0026gt;. I\u0026rsquo;ve used ObL as a shorthand for ObservableList\u0026lt;E\u0026gt; to avoid having to use a tiny, tiny font.\nUnderstand, though, that there is no interface called ObservableValue\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt;, it\u0026rsquo;s just ObservableValue\u0026lt;T\u0026gt; with the \u0026lt;T\u0026gt; resolved as a \u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt;. The same holds true for all of the other entries on the chart defined with \u0026lt;ObL\u0026gt;.\nThis chart is a little bit more complicated in its relationships because of the dual base interfaces; ObservableValue and ObservableList. However, you can see that every entry with \u0026ldquo;List\u0026rdquo; in its name implements ObservableList, while those without it do not. For instance, WritebleListValue implements ObservableList, while Property does not.\nThe Interfaces # We\u0026rsquo;ll start with the interfaces. Some of the descriptions are a little bit stripped down from what you\u0026rsquo;ll see in Part 1. I want this article to be complete within itself, but I don\u0026rsquo;t want to repeat a lot of information that you can find in Part 1.\nObservable \u0026amp; ObservableValue\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; # At the top of the chart, you\u0026rsquo;ll find the interface, Observable. You\u0026rsquo;ll notice that it is not typed at all, it\u0026rsquo;s not even a generic. That\u0026rsquo;s because all of its methods do not deal with data.\nThis is the root for all of the other classes and interfaces, but it\u0026rsquo;s really quite simple and only defines three methods: addListener(), removeListener() and subscribe(). These are all related to the process called \u0026ldquo;Invalidation\u0026rdquo;.\nThe next interface down is ObservableValue\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt;, which extends Observable. The key method in this interface is ObservableValue.getValue(), which allows us to actually read the value and will re-validate the Observable.\nNow that we can read the value we can have the methods related to ChangeListeners, including those related to Subscriptions. The other methods it adds are the map() and flatMap() methods used for transforming an ObservableValue into a different ObservableValue.\nThere is also the ObservableObjectValue\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; interface, which introduces the get() method, which at this point is effectively identical to getValue().\nObservableListValue\u0026lt;E\u0026gt; # ObservableListValue is where the List Observables start to get very different from the other typed Observables. This interface extends ObservableObjectValue\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt;, as expected, but it also implements ObservableList\u0026lt;E\u0026gt;. Unlike, for instance, ObservableStringValue, it\u0026rsquo;s not just a placeholder entry that you can treat the same as ObservableObjectValue\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt;.\nThis is the place on the chart where ObservableValue takes on the characteristics of ObservableList. In addition to the getValue and the two types of Listener methods we also ListChangeListener methods along with all of the methods in List, Collection, and Iterable. There are some extra methods for manipulating lists as well; setAll(), addAll(), and removeAll().\nFinally, we get filtered() and sorted() which generate FilteredList and SortedList.\nReadOnlyProperty\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; \u0026amp; WritableValue\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; # These are the two parent interfaces for all of the Property classes.\nReadOnlyProperty\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; simply introduces the getBean() and getName() methods. These are presumably meaningful methods if you are serializing your Properties.\nThe interface WritableValue\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; gives us the setValue() method. Now we can actually put something into an Observable!\nWritableObjectValue\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; and WritableListValue\u0026lt;E\u0026gt; # WritableObjectValue\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; adds the set() method, which, for String types is identical to setValue().\nWritabaleListValue\u0026lt;E\u0026gt; is an extension of WritableObjectValue\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; that adds no methods but does also implement ObseravbleList\u0026lt;E\u0026gt; (even though it has no actual implementations of its methods). So an object passed as WritebleListValue\u0026lt;E\u0026gt; can be treated as if it was an ObseravbleList\u0026lt;E\u0026gt;.\nProperty\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; # The Property\u0026lt;ObseravbleList\u0026lt;E\u0026gt;\u0026gt; interface extends WritableObjectValue\u0026lt;ObseravbleList\u0026lt;E\u0026gt;\u0026gt; and adds the ability to bind to other observables. We get bind() and bindBidirectional(), and their corresponding unbinding methods. Also there is the isBound() method that will tell us if the Property has been bound to something.\nNotice, though, that it does NOT implement or extend WritebleListValue\u0026lt;E\u0026gt; so it cannot be treated like an ObseravbleList\u0026lt;E\u0026gt;.\nBinding\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; # Binding\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; is the last interface on the chart. It extendsObservableValue\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; adding a method to force the Binding to invalidate, a method to check if the Binding is valid and a method to get the dependencies of the Binding.\nThe Classes # Everything else on the chart is a class, let\u0026rsquo;s take a look at them.\nListExpression\u0026lt;E\u0026gt; # This is the root class for all of the Property\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; classes, even though the diagram goes diagonally down to ObjectProperty\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt;. Note that ListExpression\u0026lt;E\u0026gt; is a drop-in replacement for ObjectExpression and does not extend or implement ObjectExpression\nThis is the only class in the chart that introduces new concrete methods that don\u0026rsquo;t implement methods already defined by any of the interfaces in the chart.\nNote ListExpression is where the implementation of the methods for ObservableList happens.\nThis class provides the core functionality for the \u0026ldquo;Fluent API\u0026rdquo; for creating List bindings. In truth, this isn\u0026rsquo;t much. There\u0026rsquo;s the expected isNull() and isNotNull(), along with isEqualTo() and isNotEqualTo(). There are also two versions of valueAt() which returns an ObjectBinding\u0026lt;E\u0026gt;, one of these takes as static index value, the other an ObservableIntegerValue.\nBeyond that there are emptyProperty() and sizeProperty(). We saw sizeProperty() used in our test example above. This alone could be a reason to use a ListProperty.\nBut more importantly, ListExpression\u0026lt;E\u0026gt; is an abstract class that implements all of the List and ObservableList methods, essentially turning ListExpression into an implementation of ObservableList.\nReadOnlyListProperty\u0026lt;E\u0026gt; # This class extends ListExpression and implements ReadOnlyProperty. This class can also be thought of as very much like ReadOnlyProperty but with the ability to use the Fluent API with it to create bindings.\nListProperty\u0026lt;E\u0026gt; # The best way to think about ListProperty\u0026lt;E\u0026gt; is an implementation of Property\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt; but, since it inherits from ListExpression\u0026lt;E\u0026gt; through ReadOnlyListProperty\u0026lt;E\u0026gt;, it also has the methods for creating bindings via the Fluent API, and the methods to implement ObservableList\u0026lt;E\u0026gt;. However, it only has implementations for bidirectional binding and setting the value.\nThis is, however, a great class to declare your instantiated Properties as. Like this:\nval someProperty : ListProperty\u0026lt;String\u0026gt;= SimpleListProperty(FXCollections.observableArrayList(\u0026#34;abc\u0026#34;, \u0026#34;def\u0026#34;)) ListPropertyBase\u0026lt;E\u0026gt; # This class adds almost all the remaining methods defined in the various interfaces up hierarchy. We get methods to bind and unbind, add and remove Listeners and a get() method.\nThe methods that are missing are the two silly Java Bean related methods. This is the class that you probably want to extend from if you want to create your own Property classes, especially if you want to ignore Java Bean stuff as much you can.\nSimpleListProperty\u0026lt;E\u0026gt; # This is the only class in the chart that you can directly instantiate, so everybody is familiar with it. It adds the getBean() and getName() methods and the infrastructure and constructors to set their values.\nReadOnlyListPropertyBase\u0026lt;E\u0026gt; and ReadOnlyListWrapper\u0026lt;E\u0026gt; # ReadOnlyListPropertyBase\u0026lt;E\u0026gt; is very similar to ListPropertyBase\u0026lt;E\u0026gt; in that it is an abstract class that has almost all of the interface methods implemented except for the \u0026ldquo;Bean\u0026rdquo; methods. It\u0026rsquo;s also missing the get() method.\nThe only \u0026ldquo;only out of the box\u0026rdquo; implementation of ReadOnlyListPropertyBase\u0026lt;E\u0026gt; is contained in ReadOnlyListWrapper\u0026lt;E\u0026gt;. This is a fairly involved topic that was covered in depth here in Part I.\nListBinding\u0026lt;E\u0026gt; # This is the sole class on the Binding side of the chart, and it\u0026rsquo;s abstract and it extends ListExpression\u0026lt;E\u0026gt; and implements Binding\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt;. Any utility (including the Fluent API) that creates List Bindings will do so by extending from this class.\nIt doesn\u0026rsquo;t add any new public methods but there are a few protected methods that are the framework for custom Binding classes. We get bind() and unbind() to start with. We also get allowValidation(), onInvalidating() and isObserved(). There is one abstract method, and that\u0026rsquo;s computeValue() which is also protected.\nThe intention is clear about the standard use case for custom classes extended from ListBinding\u0026lt;E\u0026gt;. It\u0026rsquo;s that the Observables to be bound are defined when the class is instantiated and that you\u0026rsquo;ll also define a computeValue() method that will use those bound values to determine the return value of any calls to get() or getValue().\nTip Note that computeValue() is only called when a call is made to get() or getValue() when the Binding has been invalidated.\nSince ListBinding extends from ListExpression you can use the Fluent API with it, and you can treat it just as though it was an ObservableList.\nUsing this Information # At some point during this article, you should have had an, \u0026ldquo;Oh! Wow!\u0026rdquo; moment. I know I did when I was writing it.\nIf you didn\u0026rsquo;t, then consider this: ListProperty\u0026lt;E\u0026gt; can be used any place that you can use ObservableList\u0026lt;E\u0026gt;.\nAny place.\nLet\u0026rsquo;s look at the ramifications of this\u0026hellip;\nIn your Presentation Model # For years I\u0026rsquo;ve been saying that if you are using the \u0026ldquo;set\u0026rdquo; methods for Nodes than have values, then you\u0026rsquo;re doing it wrong. You shouldn\u0026rsquo;t be calling setText() or setValue() or setSelected(). Use should be using textProperty().bind(), valueProperty().bind() and selectedProperty().bind().\nYet, I\u0026rsquo;ll still call ListView.setItems() or TableView.setItems(). It never even occurred to me that there was another way. But if I declare my ObservableLists as Properties in my Model, then I can do the same binding operation:\nclass Model { val someStuff : ListProperty\u0026lt;String\u0026gt; = SimpleListProperty\u0026lt;String\u0026gt;(FXCollections.observableArrayList(\u0026#34;abc\u0026#34;, \u0026#34;def\u0026#34;)) } and then I can do this:\nlistView.itemsProperty().bind(model.someStuff) But here\u0026rsquo;s the cool part, I can ignore the Property nature of model.someStuff if I want. Like this:\nmodel.someStuff.add(\u0026#34;hjk\u0026#34;) listView.setItems(model.someStuff) And it will still work.\nBut what\u0026rsquo;s the big deal? What\u0026rsquo;s the difference?\nIf I declare model.someStuff as an ObservableList\u0026lt;String\u0026gt;, then I have to use listView.setItems(model.someStuff). And then, if I want to change the list in the business logic, I have to clear out model.someStuff and replace its contents with new items. But, if I declare it as a ListProperty\u0026lt;String\u0026gt;, I not only have the ObservableList methods, but I also have Property methods. So now I can do this:\nmodel.someStuff.set(someOtherObseravbleList) and the ListView will automatically change.\nBut wait!! There\u0026rsquo;s more!\nThis will still work, even if I used listView.setItems(model.someStuff)! Wrap your brain around that.\nAs a Binding # Let\u0026rsquo;s look at this some more. But now we\u0026rsquo;ll use a ListBinding. Here\u0026rsquo;s something close to the examples we started out with:\nclass Example1 : Application() { @Throws(Exception::class) override fun start(primaryStage: Stage) { val mainScene = Scene(createContent(), 600.0, 500.0) primaryStage.scene = mainScene primaryStage.show() } private fun createContent(): Region = VBox(20.0).apply { val list1 = FXCollections.observableArrayList(\u0026#34;abc\u0026#34;, \u0026#34;def\u0026#34;, \u0026#34;ghi\u0026#34;, \u0026#34;123\u0026#34;) val list2 = FXCollections.observableArrayList(\u0026#34;jkl\u0026#34;, \u0026#34;mno\u0026#34;) val listProperty1 = SimpleListProperty(list1) val listProperty2 = SimpleListProperty(list2) val listBinding = object : ListBinding\u0026lt;String\u0026gt;() { init { super.bind(listProperty1, listProperty2) } override fun computeValue(): ObservableList\u0026lt;String\u0026gt; = if (listProperty1.size \u0026gt;= listProperty2.size) listProperty1 else listProperty2 } children += ListView\u0026lt;String\u0026gt;().apply { itemsProperty().bind(listBinding) } children += Button(\u0026#34;Add Element to list2\u0026#34;).apply { setOnAction { listProperty2.add(\u0026#34;xyz\u0026#34;) } } padding = Insets(30.0) } } fun main() = Application.launch(Example1::class.java) Now, we\u0026rsquo;ve added a ListView with its itemsProperty() bound to a ListBinding. The Binding is dependent on two ListProperties and will return whichever one is the biggest. At the beginning, list1 has 4 elements and list2 has 2 elements. The Button now adds an element to list2 with each click. Here\u0026rsquo;s how it starts out:\nAnd here\u0026rsquo;s what it looks like after 3 clicks:\nAnd it works exactly the same if we use setItems(listBinding) instead of itemsProperty().bind(listBinding).\nBut Wait!!! There\u0026rsquo;s more!\nRemember that ObservableList implements Observable. And what does Observable define? Invalidation!! And, of course, invalidation is the core element of binding. Which means that we can do this:\nclass Example1 : Application() { @Throws(Exception::class) override fun start(primaryStage: Stage) { val mainScene = Scene(createContent(), 600.0, 500.0) primaryStage.scene = mainScene primaryStage.show() } private fun createContent(): Region = VBox(20.0).apply { val list1 = FXCollections.observableArrayList(\u0026#34;abc\u0026#34;, \u0026#34;def\u0026#34;, \u0026#34;ghi\u0026#34;, \u0026#34;123\u0026#34;) val list2 = FXCollections.observableArrayList(\u0026#34;jkl\u0026#34;, \u0026#34;mno\u0026#34;) val listBinding = object : ListBinding\u0026lt;String\u0026gt;() { init { super.bind(list1, list2) } override fun computeValue(): ObservableList\u0026lt;String\u0026gt; = if (list1.size \u0026gt;= list2.size) list1 else list2 } children += ListView\u0026lt;String\u0026gt;().apply { itemsProperty().bind(listBinding) } children += Button(\u0026#34;Add Element to list2\u0026#34;).apply { setOnAction { list2.add(\u0026#34;xyz\u0026#34;) } } padding = Insets(30.0) } } fun main() = Application.launch(Example1::class.java) Which will work exactly the same as the previous example. What happens is that each time we add an element to list2 it invalidates. This causes listBinding to invalidate, and then computeValue() will eventually get called, revalidating both listBinding and list2.\nLinked ComboBoxes # One last example of this, because it does come up quite often\u0026hellip;\nWhat if you have two ComboBoxes and the selection list in the second one depends on the selection in the first? Here\u0026rsquo;s how I\u0026rsquo;d do this now\u0026hellip;\nclass Example2 : Application() { @Throws(Exception::class) override fun start(primaryStage: Stage) { val mainScene = Scene(createContent(), 320.0, 300.0) primaryStage.scene = mainScene primaryStage.show() } private fun createContent(): Region = HBox(20.0).apply { val birds = FXCollections.observableArrayList(\u0026#34;Robin\u0026#34;, \u0026#34;Starling\u0026#34;, \u0026#34;Woodpecker\u0026#34;, \u0026#34;Blue Jay\u0026#34;) val fish = FXCollections.observableArrayList(\u0026#34;Goldfish\u0026#34;, \u0026#34;Carp\u0026#34;, \u0026#34;Bass\u0026#34;, \u0026#34;Perch\u0026#34;) val rodents = FXCollections.observableArrayList(\u0026#34;Rat\u0026#34;, \u0026#34;Mouse\u0026#34;, \u0026#34;Capybara\u0026#34;, \u0026#34;Chipmunk\u0026#34;) val selectionList = FXCollections.observableArrayList(\u0026#34;Birds\u0026#34;, \u0026#34;Fish\u0026#34;, \u0026#34;Rodents\u0026#34;) val selectedType = SimpleStringProperty(\u0026#34;\u0026#34;) val listBinding = object : ListBinding\u0026lt;String\u0026gt;() { init { super.bind(selectedType) } override fun computeValue(): ObservableList\u0026lt;String\u0026gt;? = when (selectedType.value) { \u0026#34;Birds\u0026#34; -\u0026gt; birds \u0026#34;Fish\u0026#34; -\u0026gt; fish \u0026#34;Rodents\u0026#34; -\u0026gt; rodents else -\u0026gt; null } } children += ComboBox\u0026lt;String\u0026gt;().apply { items = selectionList selectedType.bindBidirectional(valueProperty()) } children += ComboBox\u0026lt;String\u0026gt;().apply { itemsProperty().bind(listBinding) } padding = Insets(30.0) } } fun main() = Application.launch(Example2::class.java) We have three lists; birds, fish and rodents which might be loaded into the items of the second ComboBox. The first ComboBox basically has a list of the lists, and its valueProperty() is bound to selectedType. There is a ListBinding which is dependent only on selectedType and its computeValue() method will return one of the three other lists based on the value in selectedType. The Kotlin when() statement is equivalent to a variant of the Java switch statement.\nUnfortunately, I can\u0026rsquo;t get screenshots with ComboBoxes open, but the screen starts looking like this:\nWhere you can see that the second ComboBox is very small, as it has an empty list of items. And then after picking \u0026ldquo;Rodents\u0026rdquo;:\nWhere it\u0026rsquo;s now bigger because it has items in its list.\nHere\u0026rsquo;s the thing. You can search the Web and StackOverflow.com and you\u0026rsquo;ll find variations on this situation over and over. But I don\u0026rsquo;t think you\u0026rsquo;re going to find anything like this approach, no matter how hard you look. Because nobody seems to know about it.\nWhy not?\nI had a look through the JavaDocs for the Bindings class. There are exactly zero methods in that class that return a ListBinding. The only way to do this is to write a custom ListBinding as I did.\nNow, you could change listBinding so that it was ObjectBinding\u0026lt;ObservableList\u0026lt;String\u0026gt;\u0026gt; and the rest of the code in this example will work perfectly. That would let you use Bindings or the Fluent API and might be something that someone would stumble across. However, if you did that, it will NOT work with ComboBox.setItems(listBinding).\nThe fact that ComboBox.items is an ObjectProperty\u0026lt;ObservableList\u0026lt;E\u0026gt;\u0026gt;, isn\u0026rsquo;t particularly hidden, but the idea of ObservableLists wrapped in Properties pushes it into a different level of functionality.\nConclusion # I think that there are two really important take-aways from this article:\nListProperty can be used anywhere ObservableList can be used.\nThere\u0026rsquo;s problably some very minor memory and performance hit to using ListProperty, as you need storage for things like sizeProperty() and all of the methods are delegated to the enclosed ObseravbleList. But I doubt that any ordinary application is going to have any noticable impact. On the upside, you do get a lot of versatility that you didn\u0026rsquo;t have before.\nAs you can see from the ComboBox example, using ListBinding or ListProperty allows you to do cool stuff while at the same time not creating dependencies in your client code. With ListBinding the ComboBox example worked whether the client ComboBox was aware it was getting an observable wrapper and using itemProperty().bind(), or if it wasn\u0026rsquo;t and just used setItems(). This is a strong case for just implementing all of your ObseravbleLists as ListProperties as a standard practice.\nObservableList can be treated just like Observable.\nThis is something that seems to get missed all the time. When you think about using an ObseravbleList you immediately think about ListChangeListeners and then dealing with the resulting ListChangeListener.Change objects that it passes. But if you treat it as Obseravble then you can create a Binding with it as a dependancy. If nothing else, this makes it trivial to create Bindings that return the maximum, minimum or average value of an ObservableList of numbers - something that appears incredibly hard otherwise.\nWhen I started this series, I figured this Part III would be something I wrote just for the sake of completeness. But when I dug into the subject I discovered that this is one of those \u0026ldquo;hidden features\u0026rdquo; of JavaFX that everyone should know about and understand, but few people seem to - like TextFormatter.\nIs it better/cooler/more useful that TextFormatter???\nI think it just might be.\n","date":"28 August 2024","externalUrl":null,"permalink":"/javafx/elements/observable-classes-lists/","section":"JavaFX","summary":"You can work with a JavaFX for a long time before you realize that ListProperty is even a thing. And when you do, you’ll probably be extremely confused by it.\nThat’s because ListProperty (and all of the other types that descend from ObservableValue\u003cList\u003e) implements all of the methods of ObservableList as well as everything implied by ObservableValue and its descendants. This is a little mind boggling, because ObservableList\u003cT\u003e is generic itself, and implements everything in List\u003cT\u003e, and this means that ListProperty\u003cT\u003e does everything that Property\u003cT\u003e does, as well as everything that ObservableList\u003cT\u003e and, therefore, List\u003cT\u003e does.\nBut don’t worry, read this and it will all make sense - and actually be really useful.\n","title":"Guide To the Observable Classes - Part III","type":"javafx"},{"content":"ChangeListeners are way to manually monitor a Property for changes in its value, and then to trigger some code when a change happens. Generally speaking, Bindings are much preferred over listeners, but there are times when only a listener will work. The reason for this is that Bindings link \u0026ldquo;State\u0026rdquo;, while ChangeListeners transform changes in \u0026ldquo;State\u0026rdquo; to actions. Programming to State is usually better, but events work better in some circumstances, such as when you need to deal with some part of your application outside the JavaFX domain that doesn\u0026rsquo;t understand JavaFX observables.\nThe Articles # ","externalUrl":null,"permalink":"/javafx/elements/observables/listeners/","section":"JavaFX","summary":"ChangeListeners are way to manually monitor a Property for changes in its value, and then to trigger some code when a change happens.\n90%+ of the functionality that you need to build in a Reactive application can be achieved via Bindings, which allow the “State” of your application to be share back and forth with the Nodes that make up your GUI. But, sometimes you need an “Action” to take place in response to some change in your Presentation Model, or in one of the Properties inside a screen Node. In those cases you’ll need to use a Listener to listen for the change to take place and trigger your code.\n","title":"Listeners","type":"javafx"},{"content":" What You\u0026rsquo;ll Learn # How to use the Button class. Some principles of good programming The Single Responsibility Principle Avoiding excess coupling How to organize your layout code to make it easy to understand Events How to use Bindings Adding User Interaction # Two of the most common tools for user interaction and input are TextFields and Buttons. We\u0026rsquo;re going to add both of these two our application to make it actually do something.\nThe Code So Far # public class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Scene scene = new Scene(createContent(), 400, 200); primaryStage.setScene(scene); primaryStage.show(); } private Region createContent() { HBox results = new HBox(new Label(\u0026#34;Name:\u0026#34;), new TextField(\u0026#34;\u0026#34;)); results.setSpacing(6); results.setPadding(new Insets(0,0,0,50)); results.setAlignment(Pos.CENTER_LEFT); return results; } } Adding the Output and a Button # Now we are going to add some interaction to the application. We\u0026rsquo;ll add a Button and put the output Label back on the screen. We\u0026rsquo;ll set up the Button so that when it\u0026rsquo;s clicked, it loads \u0026ldquo;Hello \u0026quot; plus the name into the output Label.\nThe code now looks like this:\npublic class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Scene scene = new Scene(createContent(), 400, 200); primaryStage.setScene(scene); primaryStage.show(); } private Region createContent() { TextField inputTextField = new TextField(\u0026#34;\u0026#34;); HBox inputRow = new HBox(new Label(\u0026#34;Name:\u0026#34;), inputTextField); inputRow.setSpacing(6); inputRow.setAlignment(Pos.CENTER); Label outputLabel = new Label(\u0026#34;\u0026#34;); Button actionButton = new Button(\u0026#34;Hello\u0026#34;); actionButton.setOnAction(evt -\u0026gt; outputLabel.setText(\u0026#34;Hello \u0026#34; + inputTextField.getText())); VBox results = new VBox(20, inputRow, outputLabel, actionButton); results.setAlignment(Pos.CENTER); return results; } } I\u0026rsquo;ve wrapped the entire content in a VBox, which presents the contained Nodes vertically stacked on the screen. Then I\u0026rsquo;ve added the Label for the output (called outputLabel), and the Button to trigger the work (called actionButton). An EventHandler was added to the actionButton to take an ActionEvent which will trigger an update on the contents of the outputLabel.\nAnd the output looks almost like this:\nI\u0026rsquo;ve put a green border around the outer VBox and the red border remains around the HBox.\nButton Actions # There\u0026rsquo;s something new in this code:\nButton actionButton = new Button(\u0026#34;Hello\u0026#34;); actionButton.setOnAction(evt -\u0026gt; outputLabel.setText(\u0026#34;Hello \u0026#34; + inputTextField.getText())); What\u0026rsquo;s this all about?\nIt\u0026rsquo;s best to think of a Button as a nothing more than a trigger for an action. You can do other things with them, but for now let\u0026rsquo;s just stick to this simplest use.\nMost of the JavaFX classes can trigger \u0026ldquo;Events\u0026rdquo;. Events are a way for these classes to signal that something significant has happened to them. Maybe they\u0026rsquo;ve changed some kind of state, or received user input - these are things that cause Events to be fired. For every Event that can be triggered, you can define an EventHandler, which is a piece of code that will be executed when that Event is fired.\nThere are lots and lots of Events that are constantly fired in JavaFX and the vast majority of them have no associated EventHandler and won\u0026rsquo;t cause anything to happen. For instance, if you move the cursor around a JavaFX window, you\u0026rsquo;ll cause hundreds of mouse events to be fired, and virtually none of them will result in any EventHandlers being invoked.\nWhen you think of a Button as a trigger, then there is one type of Event that is supremely important, the \u0026ldquo;OnAction\u0026rdquo; Event. OnAction is fired when the user clicks and releases the mouse over the Button, or when the user hits the \u0026ldquo;Enter\u0026rdquo; key when the Button is \u0026ldquo;default\u0026rdquo; or has focus. In other words, when the Button is activated.\nThe way to set up an EventHandler for the OnAction event is to call Button.setOnAction(). The single parameter for this method is an EventHandler.\nEventHandler is a functional interface that has a single method called handle(Event event). Since it\u0026rsquo;s a functional interface, we can define it using a lambda function, which is what we\u0026rsquo;ve done in our code so far.\nIf you are treating a Button as nothing more than a trigger for an action, then you probably won\u0026rsquo;t ever reference the Event that was fired in your EventHandler. But we still need to pass it in, as handle() expects it.\nIn this case, we\u0026rsquo;re simply going to update the text in outputLabel with a String that\u0026rsquo;s built up of some static text and the name from our TextField.\nThere\u0026rsquo;s a Lot Wrong with this code # It\u0026rsquo;s important that you learn how to write clean, clear layout code or your applications will become very difficult to understand and maintain. Even with the smallest of applications, it\u0026rsquo;s a good idea to get into the habit of making your layout code as clean as possible.\nLet\u0026rsquo;s take a look at the problems with the code we\u0026rsquo;ve written so far:\nIt\u0026rsquo;s Getting Busy # This method no longer follows the \u0026ldquo;Single Responsibility Principle\u0026rdquo;. It does the following:\nPopulates the input row and configures its layout Creates the output Label Creates the Button Defines the code which updates the output Label It\u0026rsquo;s often very easy to come up with a definition of your code that describes something that seems to be a \u0026ldquo;single responsibility\u0026rdquo;, yet isn\u0026rsquo;t really. In this case we have createContent(), which does one thing - creates the content. Except it actually does a lot of stuff, doesn\u0026rsquo;t it?\n\u0026ldquo;Single Responsibility\u0026rdquo; really refers to a direct responsibility. A method can \u0026ldquo;do\u0026rdquo; a lot of things by delegating them to other methods. Then its single responsibility is to bring them all together.\nThe trick is to break your code down into pieces which do one thing that can\u0026rsquo;t be broken down any further, or where breaking down further would make the code less clear. Then you\u0026rsquo;re following the \u0026ldquo;Single Responsibility Principle\u0026rdquo;.\nThere\u0026rsquo;s Way Too Much Coupling # This is a big problem with our code, and something that we need to fix right away, before it gets out of hand.\nWhat is Coupling, and Why is it Bad? # Coupling is when components of your application rely on the implementations of other components of your application. This results in a situation where two or more components are dependent on the structure of each other, making it difficult to modify one component without needing to modify another component. And, if that second, dependent, component has other components that are dependent on it\u0026hellip; You see where this goes.\nCoupling is the thing that you need to fight every step of the way when you\u0026rsquo;re building an application because it sneaks in almost every time you stop looking for it. So get into the habit of looking for coupling and avoiding it constantly.\nWhere\u0026rsquo;s the Coupling Here? # actionButton calls inputTextField.getText() This is a big problem. These two elements are now totally coupled. Let\u0026rsquo;s say that inputTextField is changed to some other type of Node that doesn\u0026rsquo;t have a getText() method. Maybe the application is intended to be used in a situation where there are a limited number of users, and the name is picked using a ComboBox, which doesn\u0026rsquo;t have a getText() method. Then you\u0026rsquo;ll need to update the Button as well. actionButton calls outputLabel.setText() This is the same type of coupling as above. Our implementation of the EventHandler is dependent on the implementation of the output display. actionButton needs to access both outputLabel and inputTextField These elements are coupled in the code. Any attempt to move the instantiation of either of these two Nodes into another method is going to cause trouble with actionButton. The update of outputLabel happens inside the EventHandler definition. The logic about updating the contents of the Label are now encapsulated inside the EventHandler. It\u0026rsquo;s not the worst example of coupling, but it can be problematic. All of the code is in the same scope This is a direct result of violating the Single Responsibility Principle. Everything is in the same scope and references between elements of the screen are inter-related. Fixing the Coupling and Holding to the Single Responsibility Principle # Let\u0026rsquo;s look at some code that will do this:\npublic class Main extends Application { public static void main(String[] args) { launch(args); } private final StringProperty greeting = new SimpleStringProperty(\u0026#34;\u0026#34;); private final StringProperty name = new SimpleStringProperty(\u0026#34;\u0026#34;); @Override public void start(Stage primaryStage) { Scene scene = new Scene(createContent(), 400, 200); primaryStage.setScene(scene); primaryStage.show(); } private Region createContent() { VBox results = new VBox(20, createInputRow(), createOutputLabel(), createGreetingButton()); results.setAlignment(Pos.CENTER); return results; } private Node createGreetingButton() { Button results = new Button(\u0026#34;Hello\u0026#34;); results.setOnAction(evt -\u0026gt; setGreeting()); return results; } private Node createInputRow() { TextField textField = new TextField(\u0026#34;\u0026#34;); textField.textProperty().bindBidirectional(name); HBox hBox = new HBox(6, new Label(\u0026#34;Name:\u0026#34;), textField); hBox.setAlignment(Pos.CENTER); return hBox; } private Node createOutputLabel() { Label results = new Label(\u0026#34;\u0026#34;); results.textProperty().bind(greeting); return results; } private void setGreeting() { greeting.set(\u0026#34;Hello \u0026#34; + name.get()); } } StringProperty??? # The first thing you\u0026rsquo;ll notice is that we\u0026rsquo;ve added two StringProperty's fields to the class. Together these two Properties are going to act as our \u0026ldquo;State\u0026rdquo; for the application. One of them, the name will hold whatever the user types into the TextField and the other will hold the greeting that will display in the output Label.\nPutting these two Properties into the class allows all of the other coupling to be removed. It\u0026rsquo;s also our first baby step towards \u0026ldquo;Reactive Programming\u0026rdquo;.\ncreateContents() Now Just Sets Up the Main VBox # At a glance, you can now see that createContents() is going to create a VBox with 3 things centred in it: a place for input, a place for output and a Button.\nThis method still \u0026ldquo;creates the contents\u0026rdquo; for the screen, but it delegates the creation of the components to builder methods. You can see that the \u0026ldquo;Single Responsibility Principle\u0026rdquo; really refers to direct responsibility, and bringing together the results of several delegated responsibilities is, in fact, a single responsibility.\nAll of the various Nodes Now Have Generic Names # Since the layout of the screen has now been split into tiny, clearly named methods, there\u0026rsquo;s no value in having meaningful variable names for the individual Nodes. The scope of each of these variables is so small now that there\u0026rsquo;s no added clarity in having complicated names for them, and simple names like hBox should indicate to any reader that these have a limited scope.\nAll of the Builder Methods Have Meaningful Names # On the other hand, the builder methods now have names like createInputBox() and createOutputLabel(). It\u0026rsquo;s clear what they do, and makes it easy to find the method that you\u0026rsquo;re interested in if you\u0026rsquo;re reading, enhancing or modifying this code.\nAll of the Builder Methods Return Node or Region # This is a very important concept to understand. If your builder method needs to return a very specific type of Node, then you\u0026rsquo;re probably doing it wrong.\nLet\u0026rsquo;s take createOutputLabel() as an example. As the code stands, you could return a Label instead of a Node and it would work. But if you do that, then it\u0026rsquo;s possible to something very Label specific, like set the font, inside of createContents(). That\u0026rsquo;s a potential issue, and certainly a \u0026ldquo;code smell\u0026rdquo;, because the configuration of the Label really belongs inside createOutputLabel().\nNow let\u0026rsquo;s say that you wanted to do something to the return value of createOutputLabel(). Perhaps you want to have the \u0026ldquo;Hello\u0026rdquo; in a different font from the name. The best way to do that might be to split the text between two Labels, and put them into an HBox and return the HBox.\nNow you have a problem since createContents() is expecting a Label so that it can mess with its font. But you want to return an HBox instead. This means you have to refactor createContents() in order to change things that should just be the responsibility of createOutputLabel().\nThe EventHandler in the Button is Just a Trigger to call setGreeting() # Even though the code in setGreeting() is just one line, now the action in the EventHandler has a name, \u0026ldquo;Set Greeting\u0026rdquo; which makes it easier to read the intent of the EventHandler at a glance.\nBinding # The last thing to note about this code are these two lines:\nresults.textProperty().bind(greeting); and:\ntextField.textProperty().bindBidirectional(name); What are these two functions bind() and bindBidirectional()?\nOne of the key components of JavaFX is a library of classes that are called \u0026ldquo;Observable\u0026rdquo;. These are all wrapper classes that hold particular kinds of values and notify other objects when their contents change.\nThe main types of observable classes are called Properties. There are different types of Properties for different types of content. In this example we have used StringProperty, which is an Observable wrapper around a String value.\nOne of the key ways to use this observability is with something called Binding. Binding allows you to link two Properties together, so that when one changes, the other will change too. This can be in one direction, or bidirectional.\nBoth Label and TextField have an internal property called Text. This holds the contents of the Node, and whatever is in this property will be displayed on the screen. Label is output only, so we\u0026rsquo;ve bound its Text Property to our greeting Property, while TextField allows user input, so we\u0026rsquo;ve connected it to name bidirectionally.\nBinding is the critical component that allows us to decouple all of the elements of our layout and create a data representation of the \u0026ldquo;State\u0026rdquo; of our GUI. It\u0026rsquo;s really the only way that you should be updating any non-static Nodes in your layout. Get comfortable with Binding, because you are going to be using it a lot.\n","externalUrl":null,"permalink":"/beginners/part3/","section":"JavaFX","summary":"GUI applications need a way for the user to interact with them.  In this lesson we’ll show how to allow user input with TextField, and to launch an action with a Button.","title":"Part 3 - User Interaction","type":"javafx"},{"content":" TextFormatter # TextFormatter is the coolest JavaFX feature that you\u0026rsquo;\u0026rsquo;ve probably never heard of. With TextFormatter you can turn any TextField or TextArea into a customized data entry field that will accept only a specific kind of input and bound directly to any type of Property in your Data Model. You can create TextFields for Numbers, Dates, Phone Numbers or Postal Codes. Really, anything you can think of.\nNote As with almost everything else in JavaFX: Here we are given some really powerful and well designed low-level tools and we\u0026rsquo;re left to figure out how to do something useful with them.\nThere\u0026rsquo;s no standard implementations of TextFormatter in the library to handle even typical situations like integer or decimal number input, and nothing to use a starting point to build your own TextFormatter. This can be daunting.\nThis section should help you.\nHow TextFormatter Works # TextFormatter is a component that you install onto a TextField which then provides two interfaces. One to your Data Model and one for the user interaction.\nThe Converter # The Converter is a bi-directional transform that converts the String data in your TextField to an external format or type. This means that you can have the types and the format of the Properties in your Data Model match their purpose, without having to worry about how they will be represented in the GUI.\nYou could, for instance provide an IntegerProperty in your Model, and have the Converter translate it to the String required by TextField.\nAdditionally, you could have a StringProperty in your Model, but its format is different from the best representation in the GUI. For instance you could have phone numbers stored in your model with just the digits, something like \u0026ldquo;9995551212\u0026rdquo;, but have the TextField display \u0026ldquo;(999)555-1212\u0026rdquo;. Converter will handle that for you, and you can directly bind the Model Property to the Value Property in the TextFormatter.\nThe Filter # The Filter is the interface that sits between the TextField itself and all of the user interactions. This includes keystrokes and mouse actions, including selecting text inside the TextField.\nNo more trying to capture KeyEvents and interpreting them! The Filter does it for you.\nThe Filter is called \u0026ldquo;Filter\u0026rdquo; because each user action is packaged up as a \u0026ldquo;Change\u0026rdquo; and passed through the Filter before it is applied to the TextField. The Change object contains virtually all of the information that you need to evaluate the impact of the change on the TextField. The Filter can either let the change go through as is, halt the change, or let a modified version of the change pass on to the TextField.\nPutting it Together # The basic process is straight forward. Create a Converter that will handle your data type and then a Filter that will ensure that the TextField will behave the way you want. Then you pass them to the constructor of a TextFormatter that install onto a TextField or a TextArea using its setTextFormatter() method.\nThe Articles # This is a big subject, and cannot be adequately handled in a single article. So it\u0026rsquo;s been divided into parts:\n","date":"3 January 2022","externalUrl":null,"permalink":"/javafx/elements/nodes/textformatter/","section":"JavaFX","summary":"TextFormatter is the coolest JavaFX feature that you’’ve probably never heard of. With TextFormatter you can turn any TextField or TextArea into a customized data entry field that will accept only a specific kind of input and bound directly to any type of Property in your Data Model. You can create TextFields for Numbers, Dates, Phone Numbers or Postal Codes. Really, anything you can think of.","title":"All About TextFormatter","type":"javafx"},{"content":" What You\u0026rsquo;ll Learn # The structure of cascading style sheet How to implement a cascading style sheet How to define a custom class selector How to define some attributes in a style sheet How to implement a style sheet in an application How to attach styling to a JavaFX Node The importance of DRY (Don\u0026rsquo;t Repeat Yourself) What is a Stylesheet in JavaFX? # Style sheets in JavaFX fill much the same purpose that they do in web design. The allow you to specify the styling of various Nodes of a GUI in a self-contained element outside of your code.\nThe Code So Far # Let\u0026rsquo;s review how far we\u0026rsquo;ve come with our sample application. We have a screen with a TextField for user input and a Button to trigger an action which will update our output.\npublic class Main extends Application { public static void main(String[] args) { launch(args); } private final StringProperty greeting = new SimpleStringProperty(\u0026#34;\u0026#34;); private final StringProperty name = new SimpleStringProperty(\u0026#34;\u0026#34;); @Override public void start(Stage primaryStage) { Scene scene = new Scene(createContent(), 400, 200); primaryStage.setScene(scene); primaryStage.show(); } private Region createContent() { VBox results = new VBox(20, createInputRow(), createOutputLabel(), createGreetingButton()); results.setAlignment(Pos.CENTER); return results; } private Node createGreetingButton() { Button results = new Button(\u0026#34;Hello\u0026#34;); results.setOnAction(evt -\u0026gt; setGreeting()); return results; } private Node createInputRow() { TextField textField = new TextField(\u0026#34;\u0026#34;); textField.textProperty().bindBidirectional(name); HBox hBox = new HBox(6, new Label(\u0026#34;Name:\u0026#34;), textField); hBox.setAlignment(Pos.CENTER); return hBox; } private Node createOutputLabel() { Label results = new Label(\u0026#34;\u0026#34;); results.textProperty().bind(greeting); return results; } private void setGreeting() { greeting.set(\u0026#34;Hello \u0026#34; + name.get()); } } Now we are going add some styling to our screen using a cascading stylesheet.\nThe Structure of the Stylesheet # A stylesheet is just a text file with a particular structure:\n.root { colour1 : value; colour2 : value; } .classSelector1 { -fx-attributeA: value; -fx-attributeB: value; } .classSelector1 { -fx-attributeA: value; -fx-attributeC: value; } The lines that start with a \u0026ldquo;.\u0026rdquo;, like \u0026ldquo;.root\u0026rdquo; and \u0026ldquo;.classSelector1\u0026rdquo; are called \u0026ldquo;selectors\u0026rdquo;. More specifically, they are called \u0026ldquo;Class Selectors\u0026rdquo;. There are other kinds of selectors, but for now we are going to stick to just Class Selectors as they do everything we need.\nThe \u0026ldquo;.root\u0026rdquo; class selector is special. It\u0026rsquo;s invoked whenever the stylesheet is applied, and is usually used to define colours that are used throughout the stylesheet, in much the same way that you\u0026rsquo;d assign static values to constants in a Java class.\nFor any given Selector, you can assign values to various JavaFX \u0026ldquo;attributes\u0026rdquo;. These are things like border colours, border widths, opacity, alignments and so on. Specific attributes might not apply to certain Node types. For instance there\u0026rsquo;s no point trying to assign a font to a Circle, since it doesn\u0026rsquo;t have any text.\nAlso, each attribute will have a different kind of data that it can be set to. Some attributes take colour values, some take pixel values, some take decimal values, some take something else. Some attributes can have fairly complicated values assigned to them. We\u0026rsquo;ll look at those much later.\nIf you are curious, there\u0026rsquo;s a web page with a reference guide to JavaFX CSS.\nLoading a Stylesheet # As previously noted, a stylesheet is just a file, and it\u0026rsquo;s treated as a resource in Java. So you put it in the \u0026ldquo;resources\u0026rdquo; folder in the \u0026ldquo;main\u0026rdquo; section of your project and access it through Class.getResource().\nVirtually all Nodes in JavaFX can have a stylesheet added to them. In order to work, though, the Node that you attach your stylesheet to has to be the \u0026ldquo;root\u0026rdquo; of the Scene. We\u0026rsquo;ll see how this works in our example.\nHere\u0026rsquo;s our stylesheet:\n.root { prompt-colour: #a04000; greeting-colour: #6000b0; } .greeting-label { -fx-text-fill: greeting-colour; -fx-font-size: 32px; } .prompt-label { -fx-text-fill: prompt-colour; -fx-font-size: 16px; -fx-font-weight: bold; } We\u0026rsquo;ve defined two custom colours in \u0026ldquo;.root\u0026rdquo;. One called \u0026ldquo;prompt-colour\u0026rdquo;, which is a dark orange, and another called \u0026ldquo;greeting-colour\u0026rdquo; which is purple. Then we\u0026rsquo;ve created two Class Selectors, one called \u0026ldquo;.greeting-label\u0026rdquo; and the other called \u0026ldquo;.prompt-label\u0026rdquo;. Each one assigns a colour to the text, and a size for the text. In addition, \u0026ldquo;.prompt-label\u0026rdquo; also sets the font weight.\nIt\u0026rsquo;s usually best to name your Class Selectors in a functional way. In this case, \u0026ldquo;.greeting-label\u0026rdquo; is very, very specific and probably not a great name. On the other hand, \u0026ldquo;.prompt-label\u0026rdquo; is likely to be used anytime you have a Label + TextField combination, and is a pretty good description of what it does.\nBoth of these Class Selector names end in \u0026ldquo;-label\u0026rdquo; to indicate that they are intended to be used with Labels in the layout. This seems to be a best practice.\nHere\u0026rsquo;s the application, updated to use the stylesheet:\npublic class Main extends Application { public static void main(String[] args) { launch(args); } private final StringProperty greeting = new SimpleStringProperty(\u0026#34;\u0026#34;); private final StringProperty name = new SimpleStringProperty(\u0026#34;\u0026#34;); @Override public void start(Stage primaryStage) { Scene scene = new Scene(createContents(), 400, 200); primaryStage.setScene(scene); primaryStage.show(); } private Region createContents() { VBox results = new VBox(20, createInputRow(), createOutputLabel(), createButton()); results.setAlignment(Pos.CENTER); results.getStylesheets().add(this.getClass().getResource(\u0026#34;/css/beginners.css\u0026#34;).toExternalForm()); return results; } private Button createButton() { Button results = new Button(\u0026#34;Hello\u0026#34;); results.setOnAction(evt -\u0026gt; setGreeting()); return results; } private HBox createInputRow() { TextField textField = new TextField(\u0026#34;\u0026#34;); textField.textProperty().bindBidirectional(name); Label namePrompt = new Label(\u0026#34;Name:\u0026#34;); namePrompt.getStyleClass().add(\u0026#34;prompt-label\u0026#34;); HBox hBox = new HBox(6, namePrompt, textField); hBox.setAlignment(Pos.CENTER); return hBox; } private Node createOutputLabel() { Label results = new Label(\u0026#34;\u0026#34;); results.getStyleClass().add(\u0026#34;greeting-label\u0026#34;); results.textProperty().bind(greeting); return results; } private void setGreeting() { greeting.set(\u0026#34;Hello \u0026#34; + name.get()); } } And here\u0026rsquo;s what it looks like when it\u0026rsquo;s running:\nOnly three things have been changed in the code.\nFirst, in createContents() there\u0026rsquo;s a call to results.getStylesheets() which returns an ObservableList of stylesheets attached to the Node. Then we add our stylesheet to it after fetching it as a resource. Now we have an active stylesheet.\nNext, in createInputRow() we\u0026rsquo;ve done two things. First, we\u0026rsquo;ve instantiated our prompt Label as a variable, instead of inside the constructor of the HBox. This lets us call its getStyleClass() method. This also returns an ObservableList, and we can add a Class Selector from our stylesheet to it. The Class Selector is added here without the \u0026ldquo;.\u0026rdquo; prefix - that\u0026rsquo;s just the way you do it. So here we assign it the \u0026ldquo;prompt-label\u0026rdquo; class selector.\nFinally, we add the \u0026ldquo;greeting-label\u0026rdquo; class selector to the label returned from createOutputLabel() method.\nOne Last Thing # Did you notice that in order to add styling to namePrompt we had to instantiate it separately so that we could then call its getStyleClass() method?\nThis is an aspect of JavaFX which can get a bit frustrating. There\u0026rsquo;s a lot of boilerplate code you need to use to do some fairly simple and common things. None of it is very difficult, but it can clutter up your code pretty quickly. In this case, now we have 3 lines of code where we used to have one. It\u0026rsquo;s instantly just that little bit harder to read.\nThe other thing that you might notice is that exactly the same pattern happens in createOutputLabel(). It\u0026rsquo;s a different Class Selector, but the same basic operation is happening. This violates the principle of \u0026ldquo;Don\u0026rsquo;t Repeat Yourself\u0026rdquo; (DRY).\nWith JavaFX, one of the best tactics you can use to keep the boilerplate out of your layout code, and to keep it as simple as possible to read is to follow DRY religiously. This is one of the most important concepts to understand.\nLet\u0026rsquo;s look at how you would do it here:\npublic class Main2 extends Application { public static void main(String[] args) { launch(args); } private final StringProperty greeting = new SimpleStringProperty(\u0026#34;\u0026#34;); private final StringProperty name = new SimpleStringProperty(\u0026#34;\u0026#34;); @Override public void start(Stage primaryStage) { Scene scene = new Scene(createContents(), 400, 200); primaryStage.setScene(scene); primaryStage.show(); } private Region createContents() { VBox results = new VBox(20, createInputRow(), createOutputLabel(), createButton()); results.setAlignment(Pos.CENTER); results.getStylesheets().add(this.getClass().getResource(\u0026#34;/css/beginners.css\u0026#34;).toExternalForm()); return results; } private Button createButton() { Button results = new Button(\u0026#34;Hello\u0026#34;); results.setOnAction(evt -\u0026gt; setGreeting()); return results; } private HBox createInputRow() { TextField textField = new TextField(\u0026#34;\u0026#34;); textField.textProperty().bindBidirectional(name); HBox hBox = new HBox(6, styledLabel(\u0026#34;Name:\u0026#34;, \u0026#34;prompt-label\u0026#34;), textField); hBox.setAlignment(Pos.CENTER); return hBox; } private Node createOutputLabel() { Label results = styledLabel(\u0026#34;\u0026#34;, \u0026#34;greeting-label\u0026#34;); results.textProperty().bind(greeting); return results; } private Label styledLabel(String contents, String classSelector) { Label results = new Label(contents); results.getStyleClass().add(classSelector); return results; } private void setGreeting() { greeting.set(\u0026#34;Hello \u0026#34; + name.get()); } } What we\u0026rsquo;ve used here is the \u0026ldquo;Builder Pattern\u0026rdquo;. We\u0026rsquo;ve created a method called styledLabel() that is a \u0026ldquo;builder\u0026rdquo;. It\u0026rsquo;s a utility method that allows us to create a Label with a particular styling in a single call. It removes the repeated pattern, and allows us to avoid instantiating promptLabel as a variable altogether, so that now it can be called directly as a constructor parameter for the HBox.\nThis is a very small application with a simple layout, so the repeated pattern and the extra lines of code are not really a big issue here specifically. But this gets out of hand quickly as the size of the application grows, and the difference in code clarity between following DRY or not becomes enormous when an application is even moderately complicated.\n","externalUrl":null,"permalink":"/beginners/part4/","section":"JavaFX","summary":"JavaFX includes Cascading Style Sheets for styling the elements of your GUI.  This is a quick guide about how to use them.","title":"Part 4 - Styling","type":"javafx"},{"content":" Image and ImageView # ImageView is the basic JavaFX Node to display images in your GUI. Image is the class used to hold the data contained within an ImageView. The relationship between the two is very similar to the relationship between Label and String.\nMastering ImageView is fairly simple and allows you to add a lot of extra flair to your layouts. From putting images on Buttons, to adding logos and image backgrounds, ImageView is the essential class to know.\nThe Articles: # ","date":"10 April 2022","externalUrl":null,"permalink":"/javafx/elements/nodes/images/","section":"JavaFX","summary":"Images are an important part of many layouts.","title":"Handling Images In JavaFX","type":"javafx"},{"content":" What You\u0026rsquo;ll Learn # The importance of having an application structure. How to build a bare-bones structure that runs. Creating a Framework # In order to organize our application so that the GUI will be separated from the business logic, and to facilitate the creation of a Reactive application, we\u0026rsquo;ll need to adopt a framework. There are lots to choose from, but I\u0026rsquo;m going to use one that I\u0026rsquo;ve developed myself that I find works very well.\nThis framework is called Model-View-Controller-Interactor (MVCI). Rather than spend a lot of time talking about how it works, we\u0026rsquo;ll just start by cooking it up as simply as possible - and it should be clear how things go as we build it out in our application.\nBut, to start with, we\u0026rsquo;ll need the skeleton to build everything on. So here we go.\nThe Application Itself # Since this is JavaFX, we still need to have a class that extends Application and one that implements main(). So, we\u0026rsquo;ll call it Main and add what we need to start using our framework.\npublic class Main extends Application { @Override public void start(Stage primaryStage) throws Exception { primaryStage.setScene(new Scene(new CustomerController().getView())); primaryStage.show(); } } This is about the simplest Application implementation that you can have. We instantiate a Controller and then call its getView() method to get the \u0026ldquo;root\u0026rdquo; for our Scene, which we put into the Stage. Then we call Stage.show().\nThe Controller # public class CustomerController { private Builder\u0026lt;Region\u0026gt; viewBuilder; private CustomerInteractor interactor; public CustomerController() { CustomerModel model = new CustomerModel(); viewBuilder = new CustomerViewBuilder(model); interactor = new CustomerInteractor(model); } public Region getView() { return viewBuilder.build(); } } From this you can see that the Controller is responsible for instantiating all of the other parts of the framework. The Model is passed to the constructors of the ViewBuilder and the Interactor. This way all of the elements have a reference to the Model. You can see that getView() just delegates to the build() method of the ViewBuilder.\nThe View Builder # public class CustomerViewBuilder implements Builder\u0026lt;Region\u0026gt; { private final CustomerModel model; public CustomerViewBuilder(CustomerModel model) { this.model = model; } @Override public Region build() { return new VBox(); } } There\u0026rsquo;s a key point hidden away here. Since we are only going to use our View as standard descendent of Region, we don\u0026rsquo;t need, nor do we want, a custom class extending some container Node, like VBox, HBox or BorderPane. It will be customized, but we\u0026rsquo;re not going to add any new features to it (which really means adding public methods), so just returning Region is all we want.\nTo do this, we use a Builder. So here you have a Builder. Right now, it just builds an empty VBox.\nThe Interactor # public class CustomerInteractor { private CustomerModel model; public CustomerInteractor(CustomerModel model) { this.model = model; } } Just the bare-bones here. A class with the appropriate constructor, but nothing else.\nThe Model # public class CustomerModel { } Even more bare-bones. We need this class, but it hasn\u0026rsquo;t come into play yet.\nThis Will Run # Yep. It\u0026rsquo;s complete, and you get an empty window on the screen. Woo-hoo!\nOkay, so there\u0026rsquo;s not much here, but\u0026hellip;\nYou should be able to see how all three of the other components share a reference to the Model so it\u0026rsquo;s going to be a way for them to communicate with each other without actually knowing about each other. That\u0026rsquo;s a crucial concept.\nYou should also be able to see that the Controller knows about both the ViewBuilder and the Interactor, yet neither of those classes knows about the Controller.\n","externalUrl":null,"permalink":"/beginners/part5/","section":"JavaFX","summary":"Building an application that “does something” means adopting a framework that works well with JavaFX.  Here we build the skeleton of the Model-View-Controller-Interactor framework that we’re going to use for our CRUD application.","title":"Part 5 - Application Framework","type":"javafx"},{"content":" Introduction # For some reason, tables (so in JavaFX, TableView) are one of the first elements that programmers want to include in their screens. Yet at the same time, they are one of the more complicated screen elements and the concepts behind them are a bit more advanced than most beginners are ready to handle.\nIn all honesty, you won\u0026rsquo;t find anything in this article that isn\u0026rsquo;t covered in just about every \u0026ldquo;TableView 101\u0026rdquo; article out there. What I\u0026rsquo;ve tried to do, however, is to explain why things are done the way that they are done, and how to think about TableView as a data presentation tool.\nWhat is a TableView? # A TableView is a screen element for displaying tabular data. You can find lots of examples on StackOverflow and elsewhere on the web where programmers ask questions like, \u0026ldquo;How do I print my TableView?\u0026rdquo;, \u0026ldquo;How do I create a PDF from my TableView?\u0026rdquo;, \u0026ldquo;How do I populate my TableView from this SQL command\u0026rdquo;, or \u0026ldquo;How do I import my TableView from JSON?\u0026rdquo;. And really, none of these questions have anything to do with TableView. TableView is a screen element, and all of these questions are data-centric and don\u0026rsquo;t really have anything to do with TableView at all.\nYou should understand this first: TableView is all about letting you display your data in rows and columns on the screen. Nothing else (well, you can edit it too - but that\u0026rsquo;s a subject for another article).\nTableView is an excellent choice when you have dense data that fits naturally into a row and column presentation like a spreadsheet. If users need to move columns around, or sort and filter based on column values, then that\u0026rsquo;s also a good reason to use a TableView.\nMy experience is that screen real-estate is always at a premium. If you have a column in your TableView that only has data in about 10% of its rows, then that is a huge waste of screen space. If your data is sparsely populated, or has a great deal of variation in its content and structure, then you should probably think about using a ListView instead.\nUnderstanding the Structure of TableView # Before we look at some examples, you should understand the basic structure of TableView.\nTableView is a Generic Class # This means that any instantiation of TableView should include a type. So you can have a TableView\u0026lt;PersonModel\u0026gt; or TableView\u0026lt;Customer\u0026gt; or TableView\u0026lt;InventoryItem\u0026gt;. Things like that.\nIdeally, the type should be a class which is constructed like a typical JavaFX presentation data model - meaning that it has fields which are Observables and with appropriate getters and setters for the fields. Beyond that, your TableView type can be any class you like, but it needs to provide different data elements for each column.\nTableView Data # Every TableView has a property called items. Items is a wrapper around an ObservableList\u0026lt;TableType\u0026gt;. So it\u0026rsquo;s a list of whatever class you\u0026rsquo;ve defined as your generic type for your TableView. Since it\u0026rsquo;s an ObservableList, any changes to its contents are going to be instantly reflected on the screen in your TableView.\nColumns # The first internal elements of TableView are the columns. Columns are all instances of TableColumn, which is also a generic class. TableColumn has two type parameters: the first is the generic type of the TableView itself, and the second is the type of the data in the TableColumn.\nTableColumn defines three important things: the layout of the column including things like width and heading, how to extract the data from the TableView generic type class, and how to create cells. We\u0026rsquo;ll look at all of these later.\nCells # At the intersection of rows and columns are the cells. Cells are all instances of the class TableCell. Customizing TableCell is one of the key ways to alter the display of data in your TableView. TableCell is a subclass of Labeled; which means that it has two visual elements, the Text and the Graphic. Just as with Labels, you can customize the Graphic to be whatever you want.\nThe default TableCell will simply take whatever data you give it, run it through toString() and put it in the Text element. Essentially this is just like using Label.setText().\nRows # Much less useful on a day-to-day basis are rows, which are instances of the class TableRow. For the most part, you\u0026rsquo;ll deal with rows in order to handle special click actions and styling. Notionally, a TableRow is associated with a single instance of the TableView's generic type, but there\u0026rsquo;s seldom any need to access the data this way. Just like any other scroll-able screen element, TableView has the concept of a \u0026ldquo;viewport\u0026rdquo;. This is the area of the TableView which is currently visible on the screen. TableViews can scroll horizontally and vertically, but vertical scrolling is the most important part to understand.\nThe key concept that you absolutely have to understand is this:\nNote TableView only instantiates enough cells to populate the viewport plus a little bit extra. These cells are then recycled as you scroll up or down through the TableView.\nWhat this means is that if you have a TableView with 1,000,000 items in its list, but a viewport that can only show 10 rows at a time, then you\u0026rsquo;ll probably have around 12-15 rows of cells instantiated. As a row scrolls off the top or the bottom of the viewport, its cells are released, and then populated with new data as new rovs scroll onto the screen.\nThe other implication of this is that virtually all the cells that are used are instantiated when the TableView itself is instantiated or when the viewport is expanded. Data loading into the cells is performed constantly, and potentially frequently. This has two important impacts on how you design your cells:\nThe cell constructor needs to run in the absence of any table data. The data load function needs to be as efficient as possible. From a practical standpoint, this means that any layout code in your TableCells should happen in the constructor, and the data load function should only put data into the layout.\nPerhaps the best way to understand this is to put some console output statements into the constructor and the data load method of a custom TableCell. Maybe put a unique identifier into each TableCell which you include in the output. Then you can see how TableCells are instantiated and recycled.\nA Simple Example # With the background info out of the way, let\u0026rsquo;s build a simple example that doesn\u0026rsquo;t rely on understanding any of that.\nI feel that it\u0026rsquo;s a good practice to separate out the inner workings of your TableViews from the rest of your layout. Years ago, I would have created a custom class extending TableView, but now I prefer to use the builder pattern. So that\u0026rsquo;s what we\u0026rsquo;ll do here.\nThis example is just a table of orders of some type. It\u0026rsquo;s typical of the kind of thing you\u0026rsquo;d see in a business application, and it has lots of potential for customization and fancy presentation. But we\u0026rsquo;ll start of with the basics.\nThe Model # Let\u0026rsquo;s take a look at the data first. We\u0026rsquo;ll create some kind of inventory item:\npublic class OrderModel_1 { private final StringProperty sku = new SimpleStringProperty(\u0026#34;\u0026#34;); private final ObjectProperty\u0026lt;Integer\u0026gt; uid = new SimpleObjectProperty\u0026lt;\u0026gt;(0); private final ObjectProperty\u0026lt;Integer\u0026gt; quantity = new SimpleObjectProperty\u0026lt;\u0026gt;(0); private final ObjectProperty\u0026lt;LocalDate\u0026gt; date = new SimpleObjectProperty\u0026lt;\u0026gt;(LocalDate.now()); private final BooleanProperty shipped = new SimpleBooleanProperty(false); public OrderModel_1(int uid, String sku, int quantity, LocalDate date, Boolean shipped) { this.uid.set(uid); this.sku.set(sku); this.quantity.set(quantity); this.date.set(date); this.shipped.set(shipped); } public StringProperty skuProperty() { return sku; } public ObjectProperty\u0026lt;Integer\u0026gt; uidProperty() { return uid; } public ObjectProperty\u0026lt;Integer\u0026gt; quantityProperty() { return quantity; } public ObjectProperty\u0026lt;LocalDate\u0026gt; dateProperty() { return date; } public BooleanProperty shippedProperty() { return shipped; } public static List\u0026lt;OrderModel_1\u0026gt; generateList() { List\u0026lt;OrderModel_1\u0026gt; results = new ArrayList\u0026lt;\u0026gt;(); int uid = 0; results.add(new OrderModel_1(uid++, \u0026#34;PART 1\u0026#34;, 20, LocalDate.of(2022, 1, 20), false)); results.add(new OrderModel_1(uid++, \u0026#34;PART 17\u0026#34;, 20, LocalDate.of(2021, 7, 15), true)); results.add(new OrderModel_1(uid++, \u0026#34;PART 20\u0026#34;, 20, LocalDate.of(2020, 5, 5), false)); results.add(new OrderModel_1(uid++, \u0026#34;PART 101\u0026#34;, 20, LocalDate.of(2021, 4, 17), false)); results.add(new OrderModel_1(uid++, \u0026#34;PART 171\u0026#34;, 20, LocalDate.of(2022, 12, 27), true)); results.add(new OrderModel_1(uid++, \u0026#34;PART 35\u0026#34;, 20, LocalDate.of(2021, 11, 4), false)); results.add(new OrderModel_1(uid++, \u0026#34;PART 18\u0026#34;, 20, LocalDate.of(2020, 6, 10), true)); results.add(new OrderModel_1(uid++, \u0026#34;PART 59\u0026#34;, 20, LocalDate.of(2021, 9, 19), true)); results.add(new OrderModel_1(uid++, \u0026#34;PART 28837\u0026#34;, 20, LocalDate.of(2022, 10, 23), false)); results.add(new OrderModel_1(uid++, \u0026#34;PART A73B\u0026#34;, 20, LocalDate.of(2021, 4, 15), true)); results.add(new OrderModel_1(uid++, \u0026#34;PART 4J1\u0026#34;, 20, LocalDate.of(2020, 2, 14), false)); results.add(new OrderModel_1(uid++, \u0026#34;PART FFC32\u0026#34;, 20, LocalDate.of(2021, 7, 13), true)); results.add(new OrderModel_1(uid++, \u0026#34;PART 1832-882B\u0026#34;, 20, LocalDate.of(2022, 3, 1), false)); results.add(new OrderModel_1(uid++, \u0026#34;SUBPART 25\u0026#34;, 20, LocalDate.of(2022, 1, 6), false)); results.add(new OrderModel_1(uid++, \u0026#34;ASSEMBLY 7K22\u0026#34;, 20, LocalDate.of(2022, 11, 12), true)); results.add(new OrderModel_1(uid++, \u0026#34;PART 9983\u0026#34;, 20, LocalDate.of(2021, 9, 24), true)); results.add(new OrderModel_1(uid++, \u0026#34;PART GH887\u0026#34;, 20, LocalDate.of(2020, 4, 30), false)); results.add(new OrderModel_1(uid++, \u0026#34;PART 1003\u0026#34;, 20, LocalDate.of(2021, 3, 17), false)); results.add(new OrderModel_1(uid++, \u0026#34;PART 31\u0026#34;, 20, LocalDate.of(2021, 10, 5), true)); results.add(new OrderModel_1(uid++, \u0026#34;PART 98XB\u0026#34;, 20, LocalDate.of(2022, 3, 22), false)); return results; } } This is a pretty basic JavaFX data model. I\u0026rsquo;ve not included any of the delegated setters and getters, since we won\u0026rsquo;t be using them.\nTo keep things clean, we\u0026rsquo;ve got a static method that supplies some dummy data included here. It\u0026rsquo;s just a list of OrderModel with random values put in them. I think this took longer to write than all the rest of the code.\nThe TableView # Now, let\u0026rsquo;s take a look at the Builder for the TableView:\npublic class TableViewBuilder1 implements Builder\u0026lt;Region\u0026gt; { private final ObservableList\u0026lt;OrderModel_1\u0026gt; model; public TableViewBuilder1(ObservableList\u0026lt;OrderModel_1\u0026gt; model) { this.model = model; } @Override public Region build() { TableView\u0026lt;OrderModel_1\u0026gt; results = new TableView\u0026lt;\u0026gt;(); TableColumn\u0026lt;OrderModel_1, Integer\u0026gt; uidCol = new TableColumn\u0026lt;\u0026gt;(\u0026#34;UID\u0026#34;); uidCol.setCellValueFactory(cdf -\u0026gt; cdf.getValue().uidProperty()); results.getColumns().add(uidCol); TableColumn\u0026lt;OrderModel_1, String\u0026gt; skuCol = new TableColumn\u0026lt;\u0026gt;(\u0026#34;SKU\u0026#34;); skuCol.setCellValueFactory(cdf -\u0026gt; cdf.getValue().skuProperty()); results.getColumns().add(skuCol); TableColumn\u0026lt;OrderModel_1, Integer\u0026gt; quantityCol = new TableColumn\u0026lt;\u0026gt;(\u0026#34;Quantity\u0026#34;); quantityCol.setCellValueFactory(cdf -\u0026gt; cdf.getValue().quantityProperty()); results.getColumns().add(quantityCol); TableColumn\u0026lt;OrderModel_1, LocalDate\u0026gt; dateCol = new TableColumn\u0026lt;\u0026gt;(\u0026#34;Date\u0026#34;); dateCol.setCellValueFactory(cdf -\u0026gt; cdf.getValue().dateProperty()); results.getColumns().add(dateCol); TableColumn\u0026lt;OrderModel_1, Boolean\u0026gt; shippedCol = new TableColumn\u0026lt;\u0026gt;(\u0026#34;Shipped?\u0026#34;); shippedCol.setCellValueFactory(cdf -\u0026gt; cdf.getValue().shippedProperty()); results.getColumns().add(shippedCol); results.setItems(model); results.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); return results; } } This is pretty straight-forward. First we instantiate the TableView itself, then we instantiate and configure each of five columns and add them to the TableView. Finally, we connect the TableView to the Model and set the ColumnResizePolicy.\nConfiguring the Columns # TableColumns are generic, and each one needs to specify the type of data in the TableView, and then the type of data in the column. In this case the generic type of our TableView is OrderModel_1, and we have columns with String, Integer, Boolean and LocalDate. The constructor parameter for each TableColumn specifies the column heading.\nWe also need to tell each TableColumn how to extract the data from the Model for this column. You do this with TableColumn.setCellValueFactory(). This needs a little explanation\u0026hellip;\nIf you look at the JavaDocs for TableColumn, you\u0026rsquo;ll find this:\npublic final void setCellValueFactory​(Callback\u0026lt;TableColumn.CellDataFeatures\u0026lt;S,​T\u0026gt;,​ObservableValue\u0026lt;T\u0026gt;\u0026gt; value) Yikes! What\u0026rsquo;s that?\nIt\u0026rsquo;s a Callback which is identical to a Function in its definition. What makes a Callback a Callback is how it\u0026rsquo;s used. A Callback is intended to provide a \u0026ldquo;hook\u0026rdquo; back to some data or functionality from some particular context that we don\u0026rsquo;t really know about. In this case, the TableView internal code is going to need to extract an ObservableValue from our table model, and we need to tell it how to do that. We don\u0026rsquo;t know, and don\u0026rsquo;t want to know, the particulars of how or when it\u0026rsquo;s going to do this - the only piece of context we need is to know that it will have an instance of CellDataFeatures at hand when it does it.\nThis means that we need to provide a Function that, given a CellDataFeatures will return some kind of ObservableValue, and that ObservableValue will be the data for the TableCell.\nSo now we need to know a little about CellDataFeatures. It\u0026rsquo;s an internal class of TableColumn, and it\u0026rsquo;s, \u0026ldquo;a wrapper class to provide all necessary information for a particular Cell\u0026rdquo; - whatever that means. Most importantly, it contains a reference to the data in the TableRow that the TableCell is in. There\u0026rsquo;s a method TableColumn.CellDataFeatures.getValue() that returns the row data.\nAt this point, that\u0026rsquo;s all you need to know about CellDataFeatures.\nLet\u0026rsquo;s look at our SKU column:\nTableColumn\u0026lt;OrderModel_1, String\u0026gt; skuCol = new TableColumn\u0026lt;\u0026gt;(\u0026#34;SKU\u0026#34;); skuCol.setCellValueFactory(cdf -\u0026gt; cdf.getValue().skuProperty()); SKU is a String, so that\u0026rsquo;s the column data type, and OrderModel_1 is the class that has our row data. Our non-generic version of setCellValueFactory will look like this:\npublic final void setCellValueFactory​(Callback\u0026lt;TableColumn.CellDataFeatures\u0026lt;OrderModel_1,String\u0026gt;,​ObservableValue\u0026lt;String\u0026gt;\u0026gt; value) We have our Callback (Function) defined by the lambda expression: cdf.getValue().skuProperty(). We know that getValue() is going to return an OrderModel_1 object, and that skuProperty() is going to return us a StringProperty, which satisfies the need for an ObservableValue\u0026lt;String\u0026gt;.\nConfiguring the TableView # There\u0026rsquo;s only two things we need to do with the TableView as a whole:\nConnect it to the Data We use the method TableView.setItems() to do this. Remember that items is actually a ObjectProperty\u0026lt;ObservableList\u0026lt;OrderModel_1\u0026gt;\u0026gt;. So by calling setItems() we are installing a new ObservableList on the TableView. Disable the Last Empty Column This is a fussy, annoying thing, but it just takes one line to fix. If you don\u0026rsquo;t include setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY) then you\u0026rsquo;ll get an empty column on the right side of your TableView. The Application # And now, finally, the rest of the application:\npublic class TableViewDemo1 extends Application { private final ObservableList\u0026lt;OrderModel_1\u0026gt; tableItems = FXCollections.observableArrayList(); public static void main(String[] args) { launch(); } @Override public void start(Stage primaryStage) throws Exception { primaryStage.setScene(new Scene(createContent())); primaryStage.show(); fetchData(); } private Region createContent() { VBox results = new VBox(new TableViewBuilder1(tableItems).build()); results.setPadding(new Insets(20)); results.setMinWidth(500); return results; } private void fetchData() { Task\u0026lt;List\u0026lt;OrderModel_1\u0026gt;\u0026gt; loadTask = new Task\u0026lt;\u0026gt;() { @Override protected List\u0026lt;OrderModel_1\u0026gt; call() throws InterruptedException { Thread.sleep(2000); return OrderModel_1.generateList(); } }; loadTask.setOnSucceeded(evt -\u0026gt; tableItems.setAll(loadTask.getValue())); Thread loadThread = new Thread(loadTask); loadThread.start(); } } A lot of this is the usual Application.start() boilerplate. The content of the screen is just a VBox with the TableView in it. The VBox is included so that we can put some padding around the TableView, so it won\u0026rsquo;t butt up against the edges of the window.\nWe also have a simulated data load routine. This has a Thread.sleep() in it because databases always have some amount of latency, and this way we can see how the GUI behaves in a realistic environment. You can also see that we\u0026rsquo;re using Task for this to run our simulated database access in a background thread and loading the results into the TableView on the FXAT.\nWhen it starts, it looks like this for a few seconds:\nThen it changes to this:\nThe first screen snap shows us the \u0026ldquo;placeholder\u0026rdquo; for the TableView. This is the graphic that is shown when the TableView has no data in it. You can customize this, but that\u0026rsquo;s a topic for a later article.\nCustomizing a Cell # The only topic left to cover in TableView basics is how to customize a TableCell. This is done by configuring the TableColumn to use a custom CellFactory. Once again, this is done via a Callback, which we\u0026rsquo;ve already noted is really just a Function.\nThe declaration of the method is this:\npublic final void setCellFactory​(Callback\u0026lt;TableColumn\u0026lt;S,​T\u0026gt;,​TableCell\u0026lt;S,​T\u0026gt;\u0026gt; value) The input is just our TableColumn, and the return value is a TableCell with the generic types set to match our TableColumn generic types. Now we need to look at how to customize a TableCell\u0026hellip;\nIn our example we have a Boolean column called shippedCol. The data displayed in this column is either true or false, which is pretty ugly. We\u0026rsquo;ll look at some better ways to do this.\nThe main thing to remember is that TableCells are created once and re-used many times. This means that we need to define the layout of the TableCell in its constructor, and then provide a mechanism to load data into it as required.\nUsing a CheckBox # This is, in my opinion, the least satisfying way to show Boolean data, but it does have the advantage of being a simple load of the cell data into a Node. This technique will translate nicely into any type of data that you would display in any appropriate JavaFX Node, so it\u0026rsquo;s worth taking a look at:\npublic class BooleanTableCell1\u0026lt;S\u0026gt; extends TableCell\u0026lt;S, Boolean\u0026gt; { CheckBox checkBox = new CheckBox(); StackPane container; public BooleanTableCell1() { container = new StackPane(checkBox); container.setPadding(new Insets(8)); setText(null); setGraphic(null); } @Override protected void updateItem(Boolean item, boolean empty) { super.updateItem(item, empty); if (!empty) { if (item != null) { checkBox.setSelected(item); } else { checkBox.setSelected(false); } setGraphic(container); } else { setGraphic(null); } } } First, you should note that it\u0026rsquo;s more common to just declare a custom TableCell as an anonymous inner class, but it\u0026rsquo;s shown here as a stand-alone class for clarity. Second, it\u0026rsquo;s been left as a partially generic class to make the point that it can be used with any TableView type - you could design this as a utility class to use across your application to have the presentation of different data types consistent in all TableViews.\nFinally, I\u0026rsquo;m aware that there is a CheckBoxTableCell in the JavaFX library that does pretty much the same thing.\nTo use this custom TableCell, there\u0026rsquo;s been one line of code added to the TableViewBuilder1 class:\nshippedCol.setCellFactory(tableColumn -\u0026gt; new BooleanTableCell1\u0026lt;\u0026gt;()); The Callback here is defined with a lambda, and it does nothing more than call the constructor of our BooleanTableCell1 class. The generic type can be inferred inside the lambda with the \u0026lt;\u0026gt; operator.\nThe result looks like this:\nThe Custom TableCell Code # Let\u0026rsquo;s have a look at actual code in our custom BooleanTableCell1\nThe Custom TableCell Gotcha! # The most important line in all of this class is:\nsuper.updateItem(item, empty); Eventually, you will forget to include this line. The result will be hours of lost time and frustration, messing with all the different parts of your TableCell layout, testing, testing, testing and retesting. But still weird things happen. Finally, you\u0026rsquo;ll have the, \u0026ldquo;Ay! Carumba!\u0026rdquo; moment and you\u0026rsquo;ll realize the super.updateItem() call is missing. We all go through this. You will too.\nThe Constructor # Just like any Labeled subclass, TableCell has a Text and a Graphic property, either or both of which you can use. In this case, we\u0026rsquo;re creating a layout that will go into the Graphic property. We won\u0026rsquo;t be using the Text property at all.\nIn the constructor we create a layout, which is just a StackPane with a CheckBox in it. We probably don\u0026rsquo;t need the StackPane, but it\u0026rsquo;s been included to make the point that Graphic can be any kind of layout that you want. Then we set the Graphic and the Text properties to null, just to make sure that the TableCell is initially empty.\nThe updateItem() Method # Every time that the TableView decides that it needs to reload or re-use the TableCell, it will call the updateItem() method. This is the method that you need to customize in order to load the data into your custom layout.\nFirst, a TableCell can either be in a row with an item, or it may be in an empty row. This is why one of the parameters is the Boolean empty. If this flag is true, then the normal action is to just clear out the Graphic property (or the Text property if you are using it), of the TableCell, and you\u0026rsquo;re all done. That\u0026rsquo;s what we do here.\nIf empty is false, then we\u0026rsquo;re not displaying in empty row. However, since the value for this cell is unpacked from an ObservableObject wrapper, it\u0026rsquo;s possible that the value might be Null, so we need to account for this. Here we are just displaying an unselected CheckBox.\nIf you have a non-Null value in a non-empty cell, then the value is simply loaded into the appropriate property in the layout Node. In this case, we\u0026rsquo;re calling CheckBox.setSelected().\nUsing an Image # An image can be a bit more satisfying. Here we\u0026rsquo;re using images of checkboxes filled in with checkmarks and crossmarks in green and red. Visually it looks a bit better than using the CheckBox control.\npublic class BooleanTableCell2 { public BooleanTableCell2() { checkMark = createImageView(this.getClass().getResource(\u0026#34;check-mark.png\u0026#34;).toExternalForm()); crossMark = createImageView(this.getClass().getResource(\u0026#34;cross-mark.png\u0026#34;).toExternalForm()); container = new StackPane(checkMark, crossMark); container.setPadding(new Insets(2)); setText(null); setGraphic(null); } private ImageView createImageView(String path) { ImageView imageView = new ImageView(path); imageView.setPreserveRatio(true); imageView.setFitHeight(24); imageView.setVisible(false); return imageView; } @Override protected void updateItem(Boolean item, boolean empty) { super.updateItem(item, empty); if (!empty) { if (item != null) { checkMark.setVisible(item); crossMark.setVisible(!item); } else { checkMark.setVisible(false); crossMark.setVisible(false); } setGraphic(container); } else { setGraphic(null); } } } Here we use the same StackPane, but this time we put two ImageViews in it. One is a checkmark and the other is a crossmark. The updateItem() method now manipulates the visibility of these to ImageViews so that only the appropriate on is shown in any given row. If the row is empty, or the ObservableValue contains Null then we just hide both of the images.\nThe most important take-away from this is that the layout of the Graphic is not changed in updateItem(). Any code that create in updateItem() which instantiates Nodes, or adds or removes Nodes to the layout is doomed to have performance issues. Yes, go ahead and change properties of the Nodes in the layout, but never, never change the layout itself.\nYou don\u0026rsquo;t have to stick to checkbox style images, either. Here\u0026rsquo;s one using emojis:\nUsing a Shape # Finally, we\u0026rsquo;re going to look at using a simple shape, in this case a Circle, for the boolean value:\npublic class BooleanTableCell3\u0026lt;S\u0026gt; extends TableCell\u0026lt;S,Boolean\u0026gt; { private final Shape circle; public BooleanTableCell3() { circle = new Circle(5); circle.setFill(Color.GREEN); circle.setStroke(Color.GREEN); setText(null); setGraphic(null); } @Override protected void updateItem(Boolean item, boolean empty) { super.updateItem(item, empty); setGraphic(null); if (!empty \u0026amp;\u0026amp; (item != null) \u0026amp;\u0026amp; item) { setGraphic(circle); } } } In this case, we\u0026rsquo;re only going to have the one shape, and we\u0026rsquo;re going to only display it when the value of the boolean is present and true. In any other case, we\u0026rsquo;ll simply leave the cell empty. It looks like this:\nThis can be an extremely effective approach, especially when you have multiple boolean columns and you want the user to be able to quickly see which rows have positive values. Any kind of graphic for the false value clutters up the TableView and makes it harder to see the positives. Of course, this is subjective and the kind of decision that needs to be made as part of the UX design.\nIf you want to have red Circles for the false values, then make the Circle always visible for non-empty, non-Null cells, and manipulate the Fill and Stroke colours of the Circle.\nConclusion # At this point, you have all the information you need to create a basic TableView, populate it with data, and do some basic customization of the TableCells.\nWe looked at the TableCell and how it\u0026rsquo;s layout is very much the same as Label, and we looked at how data is loaded into it. While we looked at different types of Nodes to put in the Graphic, the most important aspect of these examples was to see the different ways that TableCell.updateItem() interacted with the layout to change the way the Graphic looked, or whether to show it at all.\nThere\u0026rsquo;s a lot more that can be done with TableView that involves customizing the TableRows, creating complicated TableCells and dealing with row selections. There\u0026rsquo;s the idea of having TableCells update automatically when data inside the Properties inside the data model change. Not to mention TableView styling, which can be a learning odyssey all of its own.\nWe\u0026rsquo;ll look these topics in future articles.\n","date":"10 October 2023","externalUrl":null,"permalink":"/javafx/elements/tableview-basics/","section":"JavaFX","summary":"What is a TableView and how do you use it?  Here’s enough information to get you started with TableView the right way.","title":"TableView Basics","type":"javafx"},{"content":" What You\u0026rsquo;ll Learn # How to pick a suitable scope Structure of a State object - the \u0026ldquo;Model\u0026rdquo; Using builder methods for clarity The Single Responsibility Principle Returning \u0026ldquo;generic\u0026rdquo; objects Avoid passing Events between classes Implementing Create - Selecting Scope # The first feature that we\u0026rsquo;re going to implement is a minimal \u0026ldquo;Create\u0026rdquo; function. While I would really, really like to build a single field version, I can\u0026rsquo;t think of any way that it would be enough to actually test. So we\u0026rsquo;re going to use two fields: Account Number and Customer Name.\nIn this part, we\u0026rsquo;re going to concentrate on the GUI side of the feature then, in part 7, we\u0026rsquo;ll look at the business logic and persistence side of the application.\nThe next question is how \u0026ldquo;wide\u0026rdquo; do we make our feature scope? There\u0026rsquo;s no \u0026ldquo;right\u0026rdquo; answer here, but my preference is to make every feature as small as possible. In fact, there\u0026rsquo;s no such thing as \u0026ldquo;too small\u0026rdquo; when it comes to features. But just remember, every feature has to cut through the application from GUI Node right down to the database. So it\u0026rsquo;s really, really easy to end up with features that are too big.\nI like to start with bare-bones functionality. Something that works, but isn\u0026rsquo;t polished. Then add the error checking, GUI flash and convenience, and things like that as additional features in the next steps. So that\u0026rsquo;s what we\u0026rsquo;ll do here.\nOur scope is going to be a simple screen, and we\u0026rsquo;re not going to worry too much about formatting and appearance just yet. For a first cut, this is probably best. Then when we\u0026rsquo;re happy that the plumbing is good, we can go back and sort things out so that it looks nice, handles errors and checks to make sure that things are good before saving.\nThe best way to understand this is to look at how it\u0026rsquo;s done\u0026hellip;\nThe Model # The first step is to build out our Model with the Properties for the data that we\u0026rsquo;re adding:\npublic class CustomerModel { private final StringProperty accountNumber = new SimpleStringProperty(\u0026#34;\u0026#34;); private final StringProperty customerName = new SimpleStringProperty(\u0026#34;\u0026#34;); public String getAccountNumber() { return accountNumber.get(); } public StringProperty accountNumberProperty() { return accountNumber; } public void setAccountNumber(String accountNumber) { this.accountNumber.set(accountNumber); } public String getCustomerName() { return customerName.get(); } public StringProperty customerNameProperty() { return customerName; } public void setCustomerName(String customerName) { this.customerName.set(customerName); } } This is just a POJO (Plain Old Java Object) with Properties as fields. Each field is configured as a JavaFX \u0026ldquo;Bean\u0026rdquo;, with a getter that delegates to Property.get(), a setter that delegates to Property.set(), and a \u0026ldquo;Property Getter\u0026rdquo; that retrieves the actual Property.\nIn this case, both the account number and customer name fields are StringProperty. Note that the fields are final, which means that their references can never be changed. The contents of the Properties can change - that\u0026rsquo;s the point of Properties - but once some other class has obtained a reference to one of these Properties it will always be valid.\nThe ViewBuider # Now, let\u0026rsquo;s take a look at the ViewBuider, which is, naturally, the most complicated part of this section since we\u0026rsquo;re concentrating on the GUI:\npublic class CustomerViewBuilder implements Builder\u0026lt;Region\u0026gt; { private final CustomerModel model; private final Runnable saveHandler; public CustomerViewBuilder(CustomerModel model, Runnable saveHandler) { this.model = model; this.saveHandler = saveHandler; } @Override public Region build() { BorderPane results = new BorderPane(); results.getStylesheets().add(Objects.requireNonNull(this.getClass().getResource(\u0026#34;/css/customer.css\u0026#34;)).toExternalForm()); results.setTop(headingLabel(\u0026#34;Customer Information\u0026#34;)); results.setCenter(createCentre()); results.setBottom(createButtons()); return results; } private Node createCentre() { VBox results = new VBox(6, accountBox(), nameBox()); results.setPadding(new Insets(20)); return results; } private Node accountBox() { return new HBox(6,promptLabel(\u0026#34;Account #:\u0026#34;), boundTextField(model.accountNumberProperty())); } private Node nameBox() { return new HBox(6, promptLabel(\u0026#34;Name:\u0026#34;), boundTextField(model.customerNameProperty())); } private Node createButtons() { Button saveButton = new Button(\u0026#34;Save\u0026#34;); saveButton.setOnAction(evt -\u0026gt; saveHandler.run()); HBox results = new HBox(10, saveButton); results.setAlignment(Pos.CENTER_RIGHT); return results; } private Node boundTextField(StringProperty boundProperty) { TextField textField = new TextField(); textField.textProperty().bindBidirectional(boundProperty); return textField; } private Node promptLabel(String contents) { return styledLabel(contents, \u0026#34;prompt-label\u0026#34;); } private Node headingLabel(String contents) { return styledLabel(contents, \u0026#34;heading-label\u0026#34;); } private Node styledLabel(String contents, String styleClass) { Label label = new Label(contents); label.getStyleClass().add(styleClass); return label; } } In the constructor, we\u0026rsquo;ve added a new parameter, a Runnable called saveHandler.\nNext, we\u0026rsquo;ve changed our layout class from VBox to BorderPane. BorderPane is a great general-purpose layout class for screens like this. It\u0026rsquo;s divided up like so:\nYou can put a single Node into any of these areas. If you want to put more than one thing into an area, then you have to put it into a wrapper layout class, which can be anything you like, including another BorderPane. In our case, for Center and Bottom, we\u0026rsquo;ve used VBox and HBox respectively.\nThe build() method code is very simple. Instantiate a BorderPane then call some builder methods to populate Top, Bottom and Center.\nTop # In Top we\u0026rsquo;ve put a heading, which is just a styled Label. Let\u0026rsquo;s take a look at how this is done:\nThere are two private methods that return Labels that are called from the layout code. These are named promptLabel() and headingLabel(). Both of these methods delegate to a single method called styledLabel().\nWhat\u0026rsquo;s with this?\nAs we\u0026rsquo;ve seen before, we\u0026rsquo;re following DRY by putting the styling code for Label into a builder method so that we\u0026rsquo;re not repeating a pattern in our layout code. In my opinion, making a call to promptLabel() is a little bit easier to read in the layout code than calling styledLabel(\u0026quot;some text\u0026quot;, \u0026quot;prompt-label\u0026quot;). There also the argument that repeatedly calling styledLabel() with the same styleClass value is a violation of DRY. Think of how much refactoring you would need if you decided to change the css selector from \u0026ldquo;prompt-label\u0026rdquo; to something else.\nBottom # The Bottom is populated with an HBox containing a single Button, and the HBox is aligned CENTER_RIGHT. This tucks the Button over to the very right side of the screen, which is where people generally expect to find such buttons.\nLet\u0026rsquo;s look at this Button. It\u0026rsquo;s treated as a simple trigger, and it just invokes the Runnable called saveHandler.\nWhy do it this way?\nIt\u0026rsquo;s best to think of EventHandlers as local values. As local as possible. In this case, we\u0026rsquo;ve made it extremely local, as a lambda defined in the Button.setOnAction() method call. The reason for this is that EventHandlers are all context defined. There are different classes of EventHandlers for Button actions, for mouse clicks and movement and for keyboard actions, just to name a few. As soon as you start passing EventHandlers around, you start to restrict the context in which those handlers can be used.\nIn this case, our Button needs to eventually trigger some code in our Interactor (without knowing about the Interactor), so the actual handler needs to be defined by the Controller. If we passed in, say, a Mouse Click EventHandler, then we\u0026rsquo;d have to monkey around in our ViewBuilder code to deal with mouse clicks, which is totally inappropriate for a Button. So the best way to pass the handler to the ViewBuilder is as some kind of functional interface, like a Runnable or a Consumer. This way, the Controller doesn\u0026rsquo;t need to know anything about how the handler is actually invoked, and the ViewBuilder doesn\u0026rsquo;t need to know anything about what the handler does.\nCenter # Here we just have a VBox containing two HBoxes. Each HBox contains a prompt Label, and a TextField. The TextFields are each bound bidirectionally to the appropriate Property in the Model. Bidirectional binding means that either value can be changed, and the the other value will automatically follow along with it. So if we programmatically change the value in model.customerNameProperty(), then we\u0026rsquo;ll see the value in the TextField change instantly. And if we type in the TextField, then the Property in the Model will instantly change to the same value.\nReturn Values of Builder Methods # Notice that all of the private builder methods in this class return Node. This is deliberate. The idea is that all of the code which needs to deal with the inner workings of the Node that it is building - in other words, the code that needs to know specifically what class is being built - belongs in the builder method itself. Once it\u0026rsquo;s done, then there\u0026rsquo;s no need for the calling program to know exactly what kind of Node was returned.\nIt\u0026rsquo;s possible that the calling method may need to understand that the returned Node has height and width that might need to be manipulated to fit into the layout property. In this case, return a Region instead of a Node.\nThe other benefit to this is that if the code needs to be refactored later on, and the class of the return Node changes, it won\u0026rsquo;t require any refactoring of the calling method. This is all about reducing coupling.\nSingle Responsibility Principle # One of the other key principles that\u0026rsquo;s been followed with this code is the \u0026ldquo;Single Responsibility Principle\u0026rdquo;. It states that any method should only be directly responsible for a single task.\nThe benefits of this are seen with createCentre(). It\u0026rsquo;s directly responsible for creating the VBox which holds the content of the centre. Then it delegates the details of the two rows inside it to builder methods. Because the builder methods have clear names, it\u0026rsquo;s now trivial to look at createCentre() and see exactly what it does, and where to look if you need more information about something.\nAnd that concept flows through the entire structure of this class. You can look at build() and because it\u0026rsquo;s very small you can see immediately that it creates a BorderPane, and that the heading is in the top and the buttons are in the bottom. Maybe createCentre() could be better named to something like createDataEntry(), or createBody() but it\u0026rsquo;s usually fairly common that the meat of the screen goes into the centre of a BorderPane.\nThe Stylesheet # There\u0026rsquo;s not much to this stylesheet yet. Just some colours and some formatting for our two types of Labels:\n.root { prompt-colour: #a04000; heading-colour: #2090c0; } .heading-label { -fx-text-fill: heading-colour; -fx-font-size: 32px; } .prompt-label { -fx-text-fill: prompt-colour; -fx-font-size: 16px; -fx-font-weight: bold; } The Controller # The Controller needs to be expanded to define the action handler for save. We\u0026rsquo;re not going to do much yet, just call a method in the Interactor to handle the actual \u0026ldquo;Save\u0026rdquo; function:\npublic class CustomerController { private final Builder\u0026lt;Region\u0026gt; viewBuilder; private final CustomerInteractor interactor; public CustomerController() { CustomerModel model = new CustomerModel(); interactor = new CustomerInteractor(model); viewBuilder = new CustomerViewBuilder(model,interactor::saveCustomer); } public Region getView() { return viewBuilder.build(); } } Notable here is the fact that we\u0026rsquo;ve added a dependency between the Controller and the Interactor, as the Interactor now has to have a public method called saveCustomer().\nThe Interactor # While we\u0026rsquo;re not doing much, and certainly not adding the persistence component just yet, we want to make sure that all of the plumbing that we\u0026rsquo;re building is working. So the Interactor has to do something. In this case, we\u0026rsquo;ll just have some console output.\npublic class CustomerInteractor { private CustomerModel model; public CustomerInteractor(CustomerModel model) { this.model = model; } public void saveCustomer() { System.out.println(\u0026#34;Saving account: \u0026#34; + model.getAccountNumber() + \u0026#34; Name: \u0026#34; + model.getCustomerName()); } } Note Now, one thing that you really should make note of is that exactly zero data is sent from the View through the Controller to the Interactor as part of the action. Everything that the Interactor needs is already in the Model from the bidirectional bindings on the two TextFields.\nIt Runs! # Here it is:\nAnd here\u0026rsquo;s the console output:\n\u0026gt; Task :run Saving account: 1234 Name: Fred As expected, it\u0026rsquo;s not pretty but it does work and contains all of the groundwork to connect the GUI to the business logic. Now we\u0026rsquo;re ready for the next step\u0026hellip;\n","externalUrl":null,"permalink":"/beginners/part6a/","section":"JavaFX","summary":"The first feature for our CRUD application has to be “Create”.  In this lesson we’ll get started by designing the GUI and connecting it to the Controller.","title":"Part 6A - First Feature: Minimal Create","type":"javafx"},{"content":" What You\u0026rsquo;ll Learn # How to build a layered structure for the back-end of the application. How to simulate a database for development/testing The purpose of a Data Access Object The purpose of a Broker How Domain Objects and the GUI Model interact What you can and should test The Back-End of the Framework # Overall you can view MVCI with this diagram:\nWhat we are going to concentrate on here is the Interactor, Domain and Domain Objects. Let\u0026rsquo;s break this down a little bit more, and look at how it fits in with the whole application:\nA typical application will have some quantity of MVCI frameworks, typically one for each type of function in the application. For instance, you might have one for Customer CRUD operations, one for Inventory management, another for Order management, and so on. Each of these has its own business logic that is unique to its function, and this business logic goes into its Interactor.\nUnderneath all of this is a layer of business logic that is NOT unique to a specific function or MVCI framework, and that is (or potentially could be) shared with other functions and MVCI frameworks in the application. This includes things like retrieving data and saving data in databases or external applications.\nHow you integrate these components with MVCI is what we are going to look at in this article.\nJump down to the very bottom of the diagram. All those icons represent different kinds of external systems that we might need to access. This could be a database server, some kind of web service, a local file system or some kind of a proprietary system with an API or a library that we can use. We need to isolate these from our MVCI framework as much as possible.\nThe lowest level of our application is the \u0026ldquo;Data Access Objects\u0026rdquo;. These are the classes that implement the API or library that allows them to talk to an external system. They understand the data formats that they need to use to transfer information to and from theses systems. They are also the ONLY elements in our system that even know about the existence of these external systems.\nThe next level up is the \u0026ldquo;Broker\u0026rdquo; and \u0026ldquo;Service\u0026rdquo; level. These are classes that know about the Data Access Objects and how to talk to them. They transfer data to and from the DAO level using \u0026ldquo;Data Transfer Objects\u0026rdquo; or \u0026ldquo;DTO\u0026rsquo;s\u0026rdquo;. DTO\u0026rsquo;s are pure data. They can be any format you like, but they are disconnected from the format that comes from the external application.\nThis structure is all about isolation.\nYou do NOT want to have to rewrite a Broker because the data format from the external application changed! The DAO handles that, and then it continues to send data on to the Broker/Service level without changing the structure of the DTO\u0026rsquo;s it uses to communicate to them.\nThe Brokers and Services can perform any number of functions, and they may need to call multiple DAO\u0026rsquo;s in order to complete their tasks. They pass data up to the Interactors via \u0026ldquo;Domain Objects\u0026rdquo;.\nDomain Objects are true business objects, not just data. As such, they can do anything you want and contain internal logic that can be called from any other business logic. For instance Customer might be a Domain Object and it might internally have fields for first name, last name and title. It might have a method called getSalutation() which combines the title with the last name and a string, like, \u0026ldquo;Dear Mr. Smith\u0026rdquo;. Things like that.\nFinally, the Interactor knows about Domain Objects and it knows how to get data from them and the methods that it can call and how to use them. It also knows about the Brokers and Services and what methods they have that it can call and how they will react. This allows it to implement the business logic that is specific to the MVCI framework that it belongs to. It then uses these Domain Objects to create and update the data elements in the Model, which then automatically updates the View.\nThat\u0026rsquo;s what we are looking at here, although we are going to simulate everything and keep it very simple\u0026hellip;\nThe Database # At first I thought about using something like Redis as a database, but then decided that we didn\u0026rsquo;t need the added complexity that it would bring about. After all, this is a tutorial about JavaFX, not database programming. Then I thought that I\u0026rsquo;d store the records as JSON, but discarded this as a useless distraction. So our \u0026ldquo;database\u0026rdquo; is going to be this:\nMap\u0026lt;Integer, Map\u0026lt;String,String\u0026gt;\u0026gt; Many modern databases are designed as Key/Value pairs, with the database record being the value. This is really the same thing, but all in Java. In the outer Map, the keys are the record id\u0026rsquo;s, and the inner Map holds the individual fields - the key being the field name and the value being the field value.\nThis is nice because it\u0026rsquo;s all Java. We don\u0026rsquo;t need to worry about JSON libraries and yet the database storage is distinctly different from how we generally store objects in Java, so that we\u0026rsquo;ll need a translator class. In that respect, it mimics what you\u0026rsquo;d expect to see in the real world.\nBut a database is more than just a single Map. There are indexes and queries and multiple tables and data relationships that all need to be handled. So our database simulator will eventually need to do those things too. To start with, though, we\u0026rsquo;re going to generate internal record id\u0026rsquo;s that we\u0026rsquo;ll use as record keys.\nHere\u0026rsquo;s the start for our database class:\npublic class CustomerDatabase { private Map\u0026lt;Integer, Map\u0026lt;String, String\u0026gt;\u0026gt; data = new HashMap\u0026lt;\u0026gt;(); private Integer nextKey = 0; public int saveCustomer(Map\u0026lt;String, String\u0026gt; customerRecord) { customerRecord.put(\u0026#34;_id\u0026#34;, (++nextKey).toString()); data.put(nextKey, customerRecord); return nextKey; } Map\u0026lt;Integer, Map\u0026lt;String,String\u0026gt;\u0026gt; getData() { return data; } } This is pretty straight-forward. We have one public method, which takes a \u0026ldquo;record\u0026rdquo; and saves it in the database, assigning it a unique key. The key is just a counter that starts at zero.\nThere\u0026rsquo;s not a lot here, but it is testable, so we should test it.\nTesting # One of the big issues with building JavaFX applications is that the way that the FXAT works makes it very difficult to test with standard automated code-based testing tools. People have pulled their hair out trying to figure out ways around this, but it is just about impossible. That\u0026rsquo;s because the FXAT treats every Event as a job, and there is no continuity between jobs. Testing tools tend to work on the basis of, \u0026ldquo;Run this code, check the result\u0026rdquo;. But if the \u0026ldquo;Run this code\u0026rdquo; part launches something on the FXAT, then you get into trouble.\nThis means that you are out of luck when it comes to automated testing of your layouts, unless you use some \u0026ldquo;Robot\u0026rdquo; based testing tool like Selenium to do the testing.\nHowever, the nice thing about using a structure like MVCI is that virtually everything from the Interactor down to the DAO\u0026rsquo;s can run without the FXAT getting involved. And that means that we can write automated tests for it. For this project I\u0026rsquo;m using JUnit 5.\nExplaining JUnit is way beyond the scope of this course. If you understand it, that\u0026rsquo;s great. If you don\u0026rsquo;t understand how it works, at least you can see that the tests can be done, and what they test for.\nThe main take-away here is that you understand that a properly organized JavaFX application can - and should - have automated testing for its business logic.\nThis is the bottom layer of our application structure. It\u0026rsquo;s vital that we know that it works as intended, so that we can put it out of our minds when we our investigating issues with the higher layers of our application. Here\u0026rsquo;s a test class:\nclass CustomerDatabaseTest { @Test void saveCustomerIdIncrementTest() { CustomerDatabase dataBase = new CustomerDatabase(); assertEquals(1,dataBase.saveCustomer(new HashMap\u0026lt;String, String\u0026gt;()), \u0026#34;Id is incremented\u0026#34;); } @Test void saveCustomerIdIncrementTwiceTest() { CustomerDatabase dataBase = new CustomerDatabase(); dataBase.saveCustomer(new HashMap\u0026lt;String, String\u0026gt;()); assertEquals(2,dataBase.saveCustomer(new HashMap\u0026lt;String, String\u0026gt;()), \u0026#34;Id is incremented\u0026#34;); } @Test void saveCustomerIdInsertionTest() { CustomerDatabase dataBase = new CustomerDatabase(); HashMap\u0026lt;String, String\u0026gt; customerRecord = new HashMap\u0026lt;\u0026gt;(); dataBase.saveCustomer(new HashMap\u0026lt;String, String\u0026gt;()); assertEquals(\u0026#34;1\u0026#34;,dataBase.getData().get(1).get(\u0026#34;_id\u0026#34;), \u0026#34;Id is inserted\u0026#34;); } @Test void saveCustomerRecordCorrectTest() { CustomerDatabase dataBase = new CustomerDatabase(); HashMap\u0026lt;String, String\u0026gt; customerRecord = new HashMap\u0026lt;\u0026gt;(); customerRecord.put(\u0026#34;full_name\u0026#34;, \u0026#34;Fred\u0026#34;); dataBase.saveCustomer(customerRecord); assertEquals(\u0026#34;Fred\u0026#34;, dataBase.getData().get(1).get(\u0026#34;full_name\u0026#34;), \u0026#34;Id is inserted\u0026#34;); } } In order to do the testing, we need to be able to reach inside and see the raw Map data independent of any functional method calls. So there\u0026rsquo;s a getter for the Map in CustomerDatabase. We might be able to remove this later on, when we have some retrieve functions in our application, but for now we\u0026rsquo;ll use this. In the real world of corporate application development, this approach might not be possible as it exposes parts of the database class to outside classes that really shouldn\u0026rsquo;t be opened up.\nThe first two tests are to make sure that the ID is incremented by just one each time that saveCustomer() is called. The next test ensures that the internal _id field is added to our record when it is saved. The final test makes sure (to some degree) that the data that we wanted saved in the database is actually saved.\nThe Data Access Object (DAO) # The DAO class is the class that understands how to access the database. It knows about the various tables, the record structures, the relationships between the tables and how to query and retrieve data, as well as how to store it.\nHere\u0026rsquo;s our DAO so far:\npublic class CustomerDAO { static CustomerDatabase database = new CustomerDatabase(); public int saveCustomer(CustomerDTO customerRecord) { return database.saveCustomer(createCustomerRecord(customerRecord)); } Map\u0026lt;String, String\u0026gt; createCustomerRecord(CustomerDTO customer) { Map\u0026lt;String, String\u0026gt; customerRecord = new HashMap\u0026lt;\u0026gt;(); customerRecord.put(\u0026#34;full_name\u0026#34;, customer.getFullName()); customerRecord.put(\u0026#34;account_number\u0026#34;, customer.getAccountNumber()); return customerRecord; } } and here\u0026rsquo;s the Data Transfer Object:\npublic class CustomerDTO { private String accountNumber = \u0026#34;\u0026#34;; private String fullName = \u0026#34;\u0026#34;; public CustomerDTO(String accountNumber, String name) { this.accountNumber = accountNumber; this.fullName = name; } public String getAccountNumber() { return accountNumber; } public void setAccountNumber(String accountNumber) { this.accountNumber = accountNumber; } public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } } There\u0026rsquo;s not much to it. The database is instantiated as a static field since we only ever want to have one database in our application, no matter how we instantiate the DAO, or how often we instantiate it.\nThere\u0026rsquo;s only two methods so far, and at this point saveCustomer() merely delegates to the database saveCustomer() method. There is nothing to test here - at least so far.\nThe other method converts CustomerDTO to the database structure, which is Map\u0026lt;String, String\u0026gt;. This is something we should test:\npublic class CustomerDAOTest { @Test void createCustomerRecord_AccountNumberTest() { CustomerDAO dao = new CustomerDAO(); CustomerDTO customer = new CustomerDTO(\u0026#34;1234\u0026#34;, \u0026#34;Fred\u0026#34;); assertEquals(\u0026#34;1234\u0026#34;, dao.createCustomerRecord(customer).get(\u0026#34;account_number\u0026#34;), \u0026#34;Account number check\u0026#34;); } @Test void createCustomerRecord_CustomerNameTest() { CustomerDAO dao = new CustomerDAO(); CustomerDTO customer = new CustomerDTO(\u0026#34;1234\u0026#34;, \u0026#34;Fred\u0026#34;); assertEquals(\u0026#34;Fred\u0026#34;, dao.createCustomerRecord(customer).get(\u0026#34;full_name\u0026#34;), \u0026#34;Customer name check\u0026#34;); } } The Broker and Domain Objects # Now we get to the application layer that introduces the idea of a \u0026ldquo;Domain Object\u0026rdquo;. Domain Objects are business objects. They hold the data required to perform business operations and business logic. In our application we have just one kind of Domain Object - the Customer:\npublic class Customer { private String accountNumber = \u0026#34;\u0026#34;; private String name = \u0026#34;\u0026#34;; public String getAccountNumber() { return accountNumber; } public void setAccountNumber(String accountNumber) { this.accountNumber = accountNumber; } public String getName() { return name; } public void setName(String name) { this.name = name; } } Yeah, this almost identical to our Model but without the Properties. It feels like a duplication of information. That\u0026rsquo;s mostly because our Model at this stage is so simple. We don\u0026rsquo;t have Properties to control data validation, or to handle other aspects of State that become important in a real application. So, for now, we just have to live with this feeling of duplication.\nThe Broker lives in a world in-between the business logic and the DAO. It knows about Domain Objects and database record structures. It knows how to communicate with the DAO, but it doesn\u0026rsquo;t know anything about tables, queries or database commands.\nThe Broker\u0026rsquo;s job is to turn Domain Objects into database-friendly data and pass them to the DAO for storage. Eventually it will do the opposite - ask the DAO for records and turn them into Domain Objects.\nHere\u0026rsquo;s our Broker so far:\npublic class CustomerBroker { private final CustomerDAO dao = new CustomerDAO(); public int saveCustomer(Customer customer) { return dao.saveCustomer(createCustomerDTO(customer)); } CustomerDTO createCustomerDTO(Customer customer) { return new CustomerDTO(customer.getAccountNumber(), customer.getName()); } } We have some independent, functional code here, so we\u0026rsquo;ll need create some tests. I\u0026rsquo;ve deliberately made createCustomerRecord() package private, so that we can call it directly in a test. If we didn\u0026rsquo;t do this, then we\u0026rsquo;d have to use a mocking library to mock the DAO so that we can see what\u0026rsquo;s being passed to it. That\u0026rsquo;s too much of a side trip.\nHere\u0026rsquo;s the test class:\nclass CustomerBrokerTest { @Test void createCustomerRecord_AccountNumberTest() { CustomerBroker broker = new CustomerBroker(); Customer customer = new Customer(); customer.setAccountNumber(\u0026#34;1234\u0026#34;); customer.setName(\u0026#34;Fred\u0026#34;); assertEquals(\u0026#34;1234\u0026#34;, broker.createCustomerDTO(customer).getAccountNumber(), \u0026#34;Account number check\u0026#34;); } @Test void createCustomerRecord_CustomerNameTest() { CustomerBroker broker = new CustomerBroker(); Customer customer = new Customer(); customer.setAccountNumber(\u0026#34;1234\u0026#34;); customer.setName(\u0026#34;Fred\u0026#34;); assertEquals(\u0026#34;Fred\u0026#34;, broker.createCustomerDTO(customer).getFullName(), \u0026#34;Customer name check\u0026#34;); } } One note here: When I write these tests, I always fiddle with the \u0026ldquo;expected value\u0026rdquo; parameter of the Assert to something that is wrong, just to make sure that the test will fail if the CUT (Class/Code Under Test) is broken. Then I put it back to the correct value so the test passes.\nBack to the Interactor # Now that we have our back-end built, we can go back to the Interactor and integrate it into our application:\npublic class CustomerInteractor { private final CustomerModel model; private final CustomerBroker broker = new CustomerBroker(); public CustomerInteractor(CustomerModel model) { this.model = model; } public void saveCustomer() { int result = broker.saveCustomer(createCustomerFromModel()); System.out.println(\u0026#34;Saving account: \u0026#34; + model.getAccountNumber() + \u0026#34; Name: \u0026#34; + model.getCustomerName() + \u0026#34; Result: \u0026#34; + result); } Customer createCustomerFromModel() { Customer customer = new Customer(); customer.setAccountNumber(model.getAccountNumber()); customer.setName(model.getCustomerName()); return customer; } } At this point it becomes clear that the Interactor is the place where the Domain Objects and the GUI Model co-exist. The Broker only deals in Domain Objects, and will never have any knowledge of our GUI Model.\nYou can see that we\u0026rsquo;ve added a field for the Broker, and then added a call to it into CustomerBroker.saveCustomer(). I\u0026rsquo;ve left the console output in there so that we can see how it works from the GUI. Aside from that, saveCustomer() itself simply delegates to the Broker, so there\u0026rsquo;s no need for a test here.\nHowever, we do have a step that takes the data from the Model and turns it into a Domain Object. That\u0026rsquo;s something that we do need to test:\nclass CustomerInteractorTest { @Test void createCustomer_NameTest() { CustomerModel model = new CustomerModel(); CustomerInteractor interactor = new CustomerInteractor(model); model.setCustomerName(\u0026#34;Fred\u0026#34;); model.setAccountNumber(\u0026#34;ABCDE\u0026#34;); assertEquals(\u0026#34;Fred\u0026#34;, interactor.createCustomerFromModel().getName(),\u0026#34;Check customer name\u0026#34;); } @Test void createCustomer_AccountTest() { CustomerModel model = new CustomerModel(); CustomerInteractor interactor = new CustomerInteractor(model); model.setCustomerName(\u0026#34;Fred\u0026#34;); model.setAccountNumber(\u0026#34;ABCDE\u0026#34;); assertEquals(\u0026#34;ABCDE\u0026#34;, interactor.createCustomerFromModel().getAccountNumber(),\u0026#34;Check customer name\u0026#34;); } } Even though CustomerModel is composed of Properties that are JavaFX elements, it\u0026rsquo;s OK to access them from a JUnit test without any FXAT running at all. This means that we can test methods in Interactor without any issues.\nThe Data Classes # We now have four different data object types, each primarily used at a different level of the application. At this point in our application, all four of these object types are holding pretty much the same information just in different ways.\nThe Model This holds the data inside Observable classes that can be integrated with the View Nodes. This might also be known as the \u0026ldquo;Presentation Model\u0026rdquo; or \u0026ldquo;GUI State\u0026rdquo; object. The Domain Object This is Customer and it holds the data in regular Java data fields. Note that the customer\u0026rsquo;s name is stored in name. The Data Transfer Object This is CustomerDTO and it also holds the data in regular Java data fields. It differs from the Domain Object in that the data is organized in a fashion oriented towards the database storage. Note that the customer\u0026rsquo;s name is stored in fullName, not name. The Database Record This is a Map\u0026lt;String, String\u0026gt;, but it still holds exactly the same data as the other three classes, just in a different format. I don\u0026rsquo;t want to repeat myself too much, but the reason that we have 4 different data classes, all holding very similiar information, is to prevent coupling. With this structure, the database can completely change and that change will not ripple up past the DAO, and the DTO will remain the same. Changes at the DAO won\u0026rsquo;t ripple up past the Broker, and the Domain Objects will remain unchanged.\nNote This is so important that it cannot be overstated. Coupling is the thing that will make your application difficult to extend and maintain, so you need to avoid it whenever possible.\nConclusion # That\u0026rsquo;s our back-end completed and our first, bare-bones, feature all wrapped up. We have an application that can create a Customer and store it in our database. We have tests for everything that can and should be tested.\nOnce again, this thing runs!\nIt\u0026rsquo;s also potentially deployable, even though it\u0026rsquo;s ugly and doesn\u0026rsquo;t have any error checking or validation controls.\n","externalUrl":null,"permalink":"/beginners/part6b/","section":"JavaFX","summary":"In this lesson we finish up a “bare-bones” Create function by building the back-end and connecting it to the business logic in the Interactor.  Part of this involves creating a simulated database.","title":"Part 6B - Adding Persistence","type":"javafx"},{"content":" The Application So Far # Now we have an application that allows the user to enter in an account number and name, and then to save it in our database. It looks like this when it\u0026rsquo;s running:\nWhere Do We Go From Here? # What should we do next?\nTypically, in a Agile development scenario, you would get feedback from the Product Owner who is responsible for setting the priorities on the \u0026ldquo;Product Backlog\u0026rdquo;. The Product Backlog is just a big list of possible updates to the application, which might be new features, bug fixes or changes to something that\u0026rsquo;s already been done. The Product Owner always works from the position that what\u0026rsquo;s been delivered so far is the final product, unless they ask for something to be done.\nIn our case, we have no Product Owner, but we can speculate about how a reasonable Product Owner would decide about the priorities for development.\nOur Product Backlog # The Product Backlog is just a list of things that we might do to our application. It\u0026rsquo;s a dynamic thing, and every time we update our application and show it to the users, they\u0026rsquo;ll come up with new ideas that go into the Product Backlog. We\u0026rsquo;ll keep our list to just things that seem likely at this stage.\nDivide the Name Field # Just having everything in one field is a bit loose. What\u0026rsquo;s the format? \u0026ldquo;Fred Smith\u0026rdquo;? What about \u0026ldquo;Mr. Fred Smith\u0026rdquo;? Maybe \u0026ldquo;Smith, Fred\u0026rdquo;? It\u0026rsquo;s probably a good idea to break it down into three fields: a salutation, the first name and the last name.\nDatabase Latency # Just about every database that you can use has at least the possibility of latency and blocking. Our simulated database has none of that, and our application doesn\u0026rsquo;t have any way to handle it. This is a big problem, and we need to deal it before we can go \u0026ldquo;live\u0026rdquo;.\nEmpty Records # Let\u0026rsquo;s run the application and hit \u0026ldquo;Save\u0026rdquo; without entering anything in the TextFields:\n\u0026gt; Task :run Saving account: Name: Result: 1 That\u0026rsquo;s not good. We shouldn\u0026rsquo;t be able to save empty records.\nDuplicate Records # If you run the application, enter an account number and name and then hit save several times, you\u0026rsquo;ll get console output that looks like this:\n\u0026gt; Task :run Saving account: 1234 Name: Fred Smith Result: 1 Saving account: 1234 Name: Fred Smith Result: 2 Saving account: 1234 Name: Fred Smith Result: 3 Saving account: 1234 Name: Fred Smith Result: 4 Oops! It looks like we saved the same record in our database 4 times!\nEven worse, if we change the name each time, we get this:\n\u0026gt; Task :run Saving account: 1234 Name: Fred Smith Result: 1 Saving account: 1234 Name: George White Result: 2 Saving account: 1234 Name: Robert Bland Result: 3 Saving account: 1234 Name: Sara Brown Result: 4 Yikes! Now we have 4 different records for account \u0026ldquo;1234\u0026rdquo;, each for a different person.\nWe should fix this.\nMore Fields # There\u0026rsquo;s very little practical use for an application that only stores the customer name. We\u0026rsquo;ll need to add fields for address, phone number and email.\nLook and Feel # This application is still pretty ugly, and it will only get worse as we expand it. Maybe we should do some work to clean up the look and feel before we go any further?\nRetrieve # So far, we\u0026rsquo;ve only done the \u0026ldquo;Create\u0026rdquo; part of \u0026ldquo;CRUD\u0026rdquo;. We really cannot do \u0026ldquo;Delete\u0026rdquo; or \u0026ldquo;Update\u0026rdquo; until we\u0026rsquo;ve implemented \u0026ldquo;Retrieve\u0026rdquo;, so that should be the next major feature that we add.\nThe Decision # At this point, our application works but it relies on the users behaving themselves and not creating duplicates or empty records. These things can corrupt our database, so they need to be addressed before we expand the feature set.\nEven more importantly though, is that our application wouldn\u0026rsquo;t work very well with a real database that introduces latency and potentially blocks while waiting for a response. From that respect, our application is \u0026ldquo;non-functioning\u0026rdquo; and needs to fixed before we do anything else.\n","externalUrl":null,"permalink":"/beginners/part7/","section":"JavaFX","summary":"Now we’ve completed the core programming for our “Create” function, where do go next.  We’ll look at the idea of a “Product Backlog”, and talk about how we should prioritize our next steps.","title":"Part 7 - What Next?","type":"javafx"},{"content":" What You\u0026rsquo;ll Learn # The importance of using background threads. How to use Task to process on a background thread. Considerations for the GUI while a background task is running. Avoiding concurrency issues when multi-threading Avoiding Button double-clicks. The Problem With Our Application # Virtually any database or external API you call from your application has at least the potential to take a measurable amount of time to respond, or to possibly time out and not return an answer at all.\nWhat happens is that your Java code eventually needs to hand off to some other process to do the work. Maybe you\u0026rsquo;ve executed an HTTP \u0026ldquo;Get\u0026rdquo; command, or you\u0026rsquo;ve asked the kernel to read a file, or you\u0026rsquo;ve invoked an API handler for an application running on your computer. In all of these cases, your Java code is going to invoke an external process and then execute a wait() statement, or at least its equivalent. When the external process completes, it will send a signal to your Java process to wake up and continue on.\nThe problem that this presents for JavaFX is that it\u0026rsquo;s always busy. It\u0026rsquo;s always checking for mouse movements, events that are triggered, button clicks, data changes that impact the GUI. It\u0026rsquo;s always busy and it all happens on a single thread - the \u0026ldquo;FX Application Thread\u0026rdquo; or \u0026ldquo;FXAT\u0026rdquo;. This includes the code that you write that works as an EventHandler - it all runs on the FXAT by default (as it should).\nThe one thing you should NEVER do is execute a wait() (or its equivalent) on the FXAT. When you do this, everything in your GUI - EVERYTHING - stops dead. As a matter of fact, you should never execute anything on the FXAT that takes more than a few milliseconds to run.\nRight now, our database is an inaccurate simulation because it has no latency. Let\u0026rsquo;s fix that\u0026hellip;\nAdding Latency to Our Database # The easiest way to add latency is to simple put a Thread.sleep() command in our database method. Like this:\npublic class CustomerDatabase { private Map\u0026lt;Integer, Map\u0026lt;String, String\u0026gt;\u0026gt; data = new HashMap\u0026lt;\u0026gt;(); private Integer nextKey = 0; public int saveCustomer(Map\u0026lt;String, String\u0026gt; customerRecord) { try { Thread.sleep(10000); } catch (InterruptedException e) { throw new RuntimeException(e); } customerRecord.put(\u0026#34;_id\u0026#34;, (++nextKey).toString()); data.put(nextKey, customerRecord); return nextKey; } Map\u0026lt;Integer, Map\u0026lt;String,String\u0026gt;\u0026gt; getData() { return data; } } If you run this now, you\u0026rsquo;ll see that the GUI goes entirely inactive for 10 seconds. Nothing happens when you click on a TextField, click the button or try to expand the window. I\u0026rsquo;d show you, but there\u0026rsquo;s simply nothing to see. It\u0026rsquo;s just\u0026hellip;dead.\nThere\u0026rsquo;s a problem with this approach, though. Our tests now take forever to run. Let\u0026rsquo;s look at the console output from running the database tests:\n\u0026gt; Configure project : Project : =\u0026gt; \u0026#39;ca.pragmaticcoding.beginners\u0026#39; Java module \u0026gt; Task :compileJava UP-TO-DATE \u0026gt; Task :processResources UP-TO-DATE \u0026gt; Task :classes UP-TO-DATE \u0026gt; Task :mergeClasses SKIPPED \u0026gt; Task :compileTestJava \u0026gt; Task :processTestResources NO-SOURCE \u0026gt; Task :testClasses \u0026gt; Task :test BUILD SUCCESSFUL in 52s 4 actionable tasks: 2 executed, 2 up-to-date 11:36:56 a.m.: Execution finished \u0026#39;:test --tests \u0026#34;ca.pragmaticcoding.beginners.part8.CustomerDatabaseTest\u0026#34;\u0026#39;. 52 seconds!\nThis is not acceptable. Unit tests should never take more than a few milliseconds to run each. This is one of the reasons that you never access live databases from unit tests.\nWe need to find a way to add the latency to the application without slowing down our tests.\nOne way to do this is to implement the sleep() in a separate class extended from our database class. Then we can continue to test the original database class, while using a \u0026ldquo;slow\u0026rdquo; version for our production code:\npublic class SlowCustomerDatabase extends CustomerDatabase { private final int delay; public SlowCustomerDatabase(int delay) { this.delay = delay; } @Override public int saveCustomer(Map\u0026lt;String, String\u0026gt; customerRecord) { delay(); return super.saveCustomer(customerRecord); } private void delay() { try { Thread.sleep(delay); } catch (InterruptedException e) { throw new RuntimeException(e); } } } Then we\u0026rsquo;ll modify CustomerDao to use our \u0026ldquo;slow\u0026rdquo; version for the production code:\npublic class CustomerDAO { static CustomerDatabase database = new SlowCustomerDatabase(5000); public int saveCustomer(Map\u0026lt;String, String\u0026gt; customerRecord) { return database.saveCustomer(customerRecord); } } Now our production code runs with a delay, and our test code completes in less than 1 second.\nI\u0026rsquo;ll admit that this is a bit of a cheat. In a real world example you don\u0026rsquo;t have the luxury of simulating a database without latency for your automated testing. But in the real world, you never run automated tests against an actual database. What you would have to do is create Mocking classes for the database connections that can respond instantly and in exactly the way that you want - every time. But that is way beyond the scope of this tuturial.\nThe Solution: A Background Thread # Now, if our database has latency and blocks, meaning that we can\u0026rsquo;t access it on the FXAT, then the correct solution is to do the access on some other thread. This is usually referred to as a \u0026ldquo;background thread\u0026rdquo;, because it runs in the background while the FXAT keeps doing its thing.\nJavaFX has a tool just for doing this. It\u0026rsquo;s called Task. So that\u0026rsquo;s what we\u0026rsquo;re going to use.\nIn MVCI, the Controller is responsible for controlling threads. So that\u0026rsquo;s where we are going to implement our Task:\npublic class CustomerController { private final Builder\u0026lt;Region\u0026gt; viewBuilder; private final CustomerInteractor interactor; public CustomerController() { CustomerModel model = new CustomerModel(); interactor = new CustomerInteractor(model); viewBuilder = new CustomerViewBuilder(model,this::saveCustomer); } private void saveCustomer() { Task\u0026lt;Void\u0026gt; saveTask = new Task\u0026lt;\u0026gt;() { @Override protected Void call() { interactor.saveCustomer(); return null; } }; Thread saveThread = new Thread(saveTask); saveThread.start(); } public Region getView() { return viewBuilder.build(); } } Some Information About Task # Task is a utility class that is designed to run some code in a background thread. Task itself is abstract, and has 1 abstract method: call(). The most common way to use Task is to instantiate an anonymous inner class that extends Task and supply the implementation of call(). That\u0026rsquo;s what we\u0026rsquo;ve done in this example. Task extends classes that implement Runnable, so that when we supply it to a Thread in it\u0026rsquo;s constructor and then invoke Thread.start() it will invoke Task.call() in that Thread.\nThis means that any code that we put into Task.call() will be run on a background thread.\nWhen you start dealing with threads, you need to wrap your mind around the idea that pieces of your code will run simultanously, and you can no longer expect your code to run in a linear fashion. The code that instantiates the Task, defines Task.call() and starts the Task running on a background thread all runs on the FXAT. Whatever code that comes after Thread.start() will happen immediately, and will not depend on the completion of Task.call(). You cannot wait for it on the FXAT, because then your GUI would freeze.\nIn our example, saveThread.start() is the last line of code in CustomerController.saveCustomer() and is probably the last line of code in the job that the FXAT is running when it executes it. So the FXAT is going to going to go on and handle the next job in its queue.\nYou may also notice that Task is generic, and that Task.call() returns a value of whatever type we declare our instance of Task as. In our case, we don\u0026rsquo;t have a value to return, and so we are just using Void.\nBack to Our Example # Really, at this point, all we\u0026rsquo;ve done is take the call to CustomerInteractor.saveCustomer() and run it in another thread. We don\u0026rsquo;t need Task to do that, do we?\nLet\u0026rsquo;s take a look at what happens when this runs. There\u0026rsquo;s nothing much to see, but the GUI isn\u0026rsquo;t dead any more. I can interact with it while I\u0026rsquo;m waiting for the database call to complete, and it works.\nBut there\u0026rsquo;s a problem. Here\u0026rsquo;s what I did:\nEntered account, \u0026ldquo;123\u0026rdquo; and name \u0026ldquo;Fred\u0026rdquo; Clicked \u0026ldquo;Save\u0026rdquo; Clicked \u0026ldquo;Save\u0026rdquo; again. Went back to the name TextField and added \u0026quot; Smith\u0026quot; Clicked \u0026ldquo;Save\u0026rdquo; again. Clicked \u0026ldquo;Save\u0026rdquo; again. Here\u0026rsquo;s the console output:\n\u0026gt; Task :run Saving account: 123 Name: Fred S Result: 1 Saving account: 123 Name: Fred Smith Result: 2 Saving account: 123 Name: Fred Smith Result: 3 Saving account: 123 Name: Fred Smith Result: 4 Oh, oh! There\u0026rsquo;s no save for just \u0026ldquo;Fred\u0026rdquo;! The first save is for \u0026ldquo;Fred S\u0026rdquo;. At no point did I click when the name TextField said just \u0026ldquo;Fred S\u0026rdquo;. What\u0026rsquo;s going on here?\nIt turns out the reason for this is in the Interactor:\npublic void saveCustomer() { int result = broker.saveCustomer(createCustomerFromModel()); System.out.println(\u0026#34;Saving account: \u0026#34; + model.getAccountNumber() + \u0026#34; Name: \u0026#34; + model.getCustomerName() + \u0026#34; Result: \u0026#34; + result); } We call the Broker, which calls the DAO which calls the \u0026ldquo;slow\u0026rdquo; database which returns a results after 5 seconds. Then we output some information to the console which combines the return value from database with data from the Model. But the Model is now shared between two threads, the FXAT and our background thread. And when we click over and over, it\u0026rsquo;s shared by even more threads.\nThis can be a problem.\nSince changes to the GUI are \u0026ldquo;live\u0026rdquo; changes to State, whenever we need to use State in a background thread we need to do one of two things:\nCopy the elements of State to some place private to the background thread. Lock the GUI so that it cannot update State. Let\u0026rsquo;s do the first here (this code is in the Interactor):\npublic void saveCustomer() { String customerName = model.getCustomerName(); String account = model.getAccountNumber(); int result = broker.saveCustomer(createCustomerFromModel()); System.out.println(\u0026#34;Saving account: \u0026#34; + account + \u0026#34; Name: \u0026#34; + customerName + \u0026#34; Result: \u0026#34; + result); } That\u0026rsquo;s an improvement. Now when I enter \u0026ldquo;123\u0026rdquo; and \u0026ldquo;Fred\u0026rdquo;, click \u0026ldquo;Save\u0026rdquo; and then change the name to \u0026ldquo;Fred Smith\u0026rdquo;, then click save two more times I get:\n\u0026gt; Task :run Saving account: 123 Name: Fred Result: 1 Saving account: 123 Name: Fred Smith Result: 2 Saving account: 123 Name: Fred Smith Result: 3 That\u0026rsquo;s way better. It\u0026rsquo;s probably a safe bet that the background thread is going to launch and the broker call is going to start executing before anyone could ever get their hand off the mouse and start typing into any of the TextFields. That being said, this is something that you might want to look out for in a real application.\nBut it\u0026rsquo;s still disturbing that you could click on that Button three times before it finished processing. That\u0026rsquo;s something that we should deal with.\nMore About Task - Completion Events # You may have been wondering, \u0026ldquo;Why bother with Task? Why not put a Runnable in a Thread?\u0026rdquo;.\nTask is specifically designed to allow you to co-ordinate code on the FXAT with background code, which is a crucial ability. One of the ways that it does this is with a set of Events that are fired when the background job ends. These are defined in the Task as ObjectProperty\u0026lt;EventHandler\u0026gt; and you can put EventHandlers in them.\nThere are three ways that a Task can end:\nCompleted This is fired when the code in Task.call() completes normally. Failed This happens only when the code in Task.call() terminates with an uncaught, unhandled Exception of any type. Cancelled This fires when some code calls Task.cancel(). If you don\u0026rsquo;t have any code that does this, then you don\u0026rsquo;t have to worry about it. You can set up these event handlers by calling Task.setOnCompleted(), Task.setOnFailed() or Task.setOnCancelled().\nThe key idea behind this is that all EventHandlers run on the FXAT! This means that you can write some code, in an EventHandler, that will be executed on the FXAT when the Task completes its job. This turns out to be very, very powerful\u0026hellip;\nBack to Our Example (Again) # One of the ways that you can utilize this power is to create a \u0026ldquo;workflow\u0026rdquo; for creating and running a Task. Generally, that workflow is something like this:\n[FXAT] Put the GUI in the state that you want it while the Task runs. [Background] Execute the Task. [FXAT] Put the GUI back into its \u0026ldquo;normal\u0026rdquo; state when the Task completes. The first and last steps are performed on the FXAT, the Task executes on the background thread.\nThere\u0026rsquo;s just one problem, though. The GUI stuff is the property of the View, and the Task is part of the Controller. How do you handle this?\nThe first thing we\u0026rsquo;re going to do is change our saveHandler from a Runnable to a Consumer\u0026lt;Runnable\u0026gt;. Then we\u0026rsquo;re going to have our Controller execute that Runnable passed through the Consumer in the Task completion EventHandler. It\u0026rsquo;s more complicated to explain than it is to just see the code:\nprivate void saveCustomer(Runnable postTaskGuiActions) { Task\u0026lt;Void\u0026gt; saveTask = new Task\u0026lt;\u0026gt;() { @Override protected Void call() { interactor.saveCustomer(); return null; } }; saveTask.setOnSucceeded(evt -\u0026gt; postTaskGuiActions.run()); Thread saveThread = new Thread(saveTask); saveThread.start(); } Now saveCustomer() is passed a Runnable, which it runs when the Task completes.\nWhat happens in the View is a little bit more interesting, first the constructor:\npublic CustomerViewBuilder(CustomerModel model, Consumer\u0026lt;Runnable\u0026gt; saveHandler) { this.model = model; this.saveHandler = saveHandler; } You can see here that we\u0026rsquo;ve changed the type of saveHandler from a Runnable to a Consumer\u0026lt;Runnable\u0026gt;. Let\u0026rsquo;s take a look at that a little closer. A Consumer is a \u0026ldquo;Functional Interface\u0026rdquo; that accepts a single parameter and returns nothing. It \u0026ldquo;consumes\u0026rdquo; the value that it is passed. In this case, it will be consuming a Runnable, which is just a snippet of executable code.\nRemember that this saveHandler is passed in to the View. The View isn\u0026rsquo;t going to define it, it is going to invoke it. But the View is going to define the Runnable that it passes to saveHandler.\nLet\u0026rsquo;s look at how that works in the code for the Button:\nprivate Node createButtons() { Button saveButton = new Button(\u0026#34;Save\u0026#34;); saveButton.setOnAction(evt -\u0026gt; { saveButton.setDisable(true); saveHandler.accept(() -\u0026gt; saveButton.setDisable(false)); }); HBox results = new HBox(10, saveButton); results.setAlignment(Pos.CENTER_RIGHT); return results; } The Runnable that is passed to saveHandler is the single command, saveButton.setDisable(false). This is passed to the Consumer via Consumer.accept(), and is the code that is then passed back to the Controller as a Runnable and then gets put into the onCompleted EventHandler of the Task. So it\u0026rsquo;s actually passed around twice.\nNow, when the Button is clicked, the first thing that happens is that it is disabled. Then the saveHandler is invoked, passing it a Runnable that re-enables the Button. Like this:\nThe Button will remain disabled until the Task completes, and the onCompleted EventHandler is executed. That EventHandler will call Runnable.run() which will execute saveButton.setDisable(false) and the Button will be re-enabled.\nIn this way, we keep the GUI code in the ViewBuilder, and the control over the threading in the Controller.\nKeeping Secrets # Let\u0026rsquo;s look at what the elements of our MVCI framework \u0026ldquo;know\u0026rdquo;, and \u0026ldquo;don\u0026rsquo;t know\u0026rdquo; about each other.\nThe Controller knows that it needs to supply the View with a Consumer that it can call to pass back a Runnable that is associated with the completion saving a customer record. Invocation of this Consumer should trigger the customer record save.\nThe Controller has no knowledge of what that Runnable does.\nThe Controller has no knowledge of how the Consumer will be invoked.\nThe View knows that it will get a Consumer from whatever class instantiates it. It knows that this Consumer is related to saving a customer and needs to be invoked (via Consumer.accept()) to execute the save.\nThe View does not know what the Consumer does.\nThe View does not know how the Runnable that it supplies will be invoked.\nThe Controller knows that it has to call Interactor.saveCustomer() via a Task in response to invocation of the Consumer by the View.\nThe Controller has no knowledge of what Interactor.saveCustomer() does.\nThe Interactor has no knowledge of any external components except the Model.\nThe Interactor is NOT aware that saveCustomer() will be run on a background thread.\nThis last item is a bit fluid. There may be occasions when an Interactor method is designed to run on either the FXAT or a background thread and you\u0026rsquo;ll need to keep away from doing certain things in either case.\nConclusion # Connecting your application to the real world means that you have to cope with blocking API calls, and that means you have to use background threads to handle these connections. But multi-threading means concurrency and concurrency brings its own issues. This is unavoidable, as the GUI needs to remain active while external access is running, and just something you\u0026rsquo;ll need to learn how to handle.\nProgrammers are used to imperative programming, where programs execute in a linear fashion and you always know how you got somewhere and the state of your application when you get there. Reactive programming breaks this paradigm, and multi-threaded programming complicates it even more. It\u0026rsquo;s not really difficult to cope with, but it takes some time to get used to the new way of thinking.\n","externalUrl":null,"permalink":"/beginners/part8/","section":"JavaFX","summary":"Our simulated database doesn’t really do a good job standing in for a real database because it works too fast.  We’ll slow it down and see what horrible things this does to our application.  Then we’ll see how to deal with this.","title":"Part 8 - Dealing With Latency and Blocking","type":"javafx"},{"content":" What You\u0026rsquo;ll Learn # How to define a business rule How to link a business rule to the Model with a Binding How to control a Button with Binding How to keep the business rules out of the View Next Problem - Empty Fields # We still have a functional problem with our application. It\u0026rsquo;s possible to save a customer without specifying an account number or a name. We\u0026rsquo;ll need to prevent this.\nEmpty Fields is a Business Rule # Here\u0026rsquo;s a very important concept: Validation rules about data and when things can be saved are business rules. They cannot be defined in the View, they need to be defined in the Interactor.\nHow do these rules get to the View, then?\nThrough the Model!\nThis means that we\u0026rsquo;re going to need to create something in the model that says, \u0026ldquo;It\u0026rsquo;s OK to save\u0026rdquo;:\npublic class CustomerModel { private final StringProperty accountNumber = new SimpleStringProperty(\u0026#34;\u0026#34;); private final StringProperty customerName = new SimpleStringProperty(\u0026#34;\u0026#34;); private final BooleanProperty okToSave = new SimpleBooleanProperty(false); } I\u0026rsquo;ve left out all of the getter and setter stuff, because I\u0026rsquo;m sure you understand how that all works. We have a new BooleanProperty called okToSave.\nHow does CustomerModel.okToSave get set?\nThrough the Interactor. We\u0026rsquo;re going to bind okToSave to the other fields through custom Binding that makes sure that all of the fields have valid values:\npublic CustomerInteractor(CustomerModel model) { this.model = model; model.okToSaveProperty().bind(Bindings.createBooleanBinding(this::isDataValid, model.accountNumberProperty(), model.customerNameProperty())); } private boolean isDataValid() { return !model.getAccountNumber().isEmpty() \u0026amp;\u0026amp; !model.getCustomerName().isEmpty(); } Let\u0026rsquo;s take a look at isDataValid() first. It\u0026rsquo;s straight-forward method that just looks at all of the fields in CustomerModel that must have a value in them before the data can be saved. If any one of them is empty, then it will return false.\nIn the constructor of the Interactor we\u0026rsquo;re going to bind CustomerModel.okToSave to the other fields through a BooleanBinding. We\u0026rsquo;re using the Bindings library, which has huge number of static methods to create different kinds of Bindings for us. Here we\u0026rsquo;re using Bindings.createBooleanBinding().\nBindings.createBooleanBinding() takes at least two parameters, and can take many more. The first parameter is always a Supplier\u0026lt;Boolean\u0026gt;, and here we\u0026rsquo;re using a method reference to isDataValid(). The remaining parameters are the Properties that will trigger the Binding to recalculate whenever they change. In this case, it\u0026rsquo;s CustomerModel.accountNumber and CustomerModel.customerName.\nThis means that whenever either of those Properties change, the Binding will be recalculated and CustomerModel.okToSave will always be synchronized to them. In other words, okToSave will always have the correct value.\nThis is how we inject a business rule into the Model without putting the code in the Model. Now it\u0026rsquo;s available for the View to use.\nIs This the Best Approach? # There is a school of thought that the Interactor should never treat the elements of the Model as ObservabaleValues - which we are clearly doing here. Instead you should treat your Observable fields in the Model as generic containers for values.\nThere are some who believe that it should be possible to have an Interactor without any javafx.* imports at all. Here, we have:\nimport javafx.beans.binding.Bindings; because we have that call to Bindings.createBooleanBinding().\nHow can you create the binding without having the code to do so in the Interactor?\nThe only way you can do this is to move the binding creation into the Model itself. You can do this, but, if you want to keep the business logic in the Interactor, you\u0026rsquo;ll have to leave isDataValid() in the Interactor.\nThis would be fine, except that you\u0026rsquo;ll now have a reference to the Interactor in the Model. Something that we didn\u0026rsquo;t need before.\nI feel that this would be a mistake. As it stands, the Model is the central dependency that all of the other components share, but it has zero dependencies of its own. All of the dependencies are to the Model, and none of them are from the Model. I wouldn\u0026rsquo;t change this just to remove the JavaFX nature of the Model from the Interactor.\nAlternatively, you could move the binding code into the Controller. The Controller already knows about methods in the Interactor, so it could call Interactor.isDataValid() without creating onerous new dependencies between the Controller and the Interactor. Additionally, if I was to implement a ChangeListener on some field in the Model, I\u0026rsquo;d probably implement it in the Controller. Setting up a binding is just a small step from that.\nI feel that there\u0026rsquo;s a benefit to having both the binding code and the business logic that supports it in the same class. Why spread it around and make it harder to keep track of? Additionally, I feel that the Interactor isn\u0026rsquo;t really supposed to be agnostic towards the GUI environment. This is a JavaFX construct, and it can be acknowledged inside the Interactor without any real practical implications.\nAdding the Validation to the View # We are going to control the ability to save by disabling the \u0026ldquo;Save\u0026rdquo; Button when the data is not valid. We are going to do this with a Binding.\nThere\u0026rsquo;s a complication, though. We are already manually setting and unsetting Button.disable() as part of our Button action. So we\u0026rsquo;re going to need to deal with that:\nprivate Node createButtons() { Button saveButton = new Button(\u0026#34;Save\u0026#34;); saveButton.disableProperty().bind(model.okToSaveProperty().not()); saveButton.setOnAction(evt -\u0026gt; { saveButton.disableProperty().unbind(); saveButton.setDisable(true); saveHandler.accept(() -\u0026gt; saveButton.disableProperty().bind(model.okToSaveProperty().not())); }); HBox results = new HBox(10, saveButton); results.setAlignment(Pos.CENTER_RIGHT); return results; } Now we are creating the Button and then immediately binding it\u0026rsquo;s disable Property to CustomerModel.okToSave with the not() modifier. That\u0026rsquo;s pretty simple.\nBut we cannot directly set a Property that\u0026rsquo;s bound. We\u0026rsquo;ll get a runtime error if we do that. So in the Button onAction EventHandler we have to first unbind the Property, then set it to true. In the Runnable that we pass to saveHandler, we re-establish the binding instead of setting it to false.\nNow it Works # That\u0026rsquo;s it, and it was pretty painless, too.\nYou can see how we\u0026rsquo;ve established the business rule in the Interactor, connected it to the View via the Model, and the View uses it without having any knowledge about how it works.\nNow, let\u0026rsquo;s look at it in action:\nThe \u0026ldquo;Save\u0026rdquo; Button is disabled. Then with just account number filled out:\nIt\u0026rsquo;s still disabled. And then with both fields completed:\nNow it\u0026rsquo;s enabled and save can happen. We\u0026rsquo;re done!\n","externalUrl":null,"permalink":"/beginners/part9/","section":"JavaFX","summary":"We’ve also got a big problem with our application because there’s no data validation in our screen.  This means that users can save invalid customer data.  We’ll look at how to cope with this properly.","title":"Part 9 - GUI Validation","type":"javafx"},{"content":" Note This is not a beginners subject. You\u0026rsquo;ll just be scared away from Reactive JavaFX if you read this article as your introduction to Properties and Bindings.\nIf you have ever wondered, \u0026ldquo;What\u0026rsquo;s the difference between ObservableValue and ObservableObjectValue?\u0026rdquo;, or \u0026ldquo;When and how should I use ReadOnlyObjectProperty?\u0026rdquo;, or \u0026ldquo;What\u0026rsquo;s a ReadOnlyObjectWrapper?\u0026rdquo;, then this is the series for you to read.\nI spent hours and hours pouring over all of the JavaDocs and the source code for pretty much all of the JavaFX observable types (interfaces and classes) to figure out how they relate to each other and what they do. I don\u0026rsquo;t think that you\u0026rsquo;ll find a similar analysis anywhere else, and I think it can be quite valuable whenever you\u0026rsquo;re attempting to do something a bit more complex with JavaFX, especially if you venture into creating your own custom observable classes.\n","date":"28 August 2024","externalUrl":null,"permalink":"/javafx/elements/observables_guide/","section":"JavaFX","summary":"A three part series (so far) that provides an in-depth examination of all of the classes and interfaces that make up the library of observable types in JavaFX.","title":"Guide To the Observable Classes","type":"javafx"},{"content":" What You\u0026rsquo;ll Learn # Dealing with external API failures The difference between checked and unchecked exceptions How and where to handle exceptions Translating exceptions into Task results Communicating errors to the users through Alerts The Last Functional Issue # The last problem we have with out application is that it is possible to save customer records with duplicate account numbers. This is different from the last problem because we cannot know that there is an issue until after we attempt to save it in the database. Even if we wanted to build a retrieve function to check first, we still have to go to the database to determine if there is a problem.\nIn a lot of databases, you\u0026rsquo;d define the account number that we\u0026rsquo;re using as a \u0026ldquo;unique index\u0026rdquo;. This would mean that it was defined in our database as a field that we wish to do quick look-ups on, and that it will always have unique values. Attempting to save a new record with a value in a unique index field would generate an error.\nIt\u0026rsquo;s not really reasonable to take all of the rules in our database and bake them into our front end. At some point, we have to cope with the fact that our database might reject our update and somehow handle that situation and notify the user through the GUI. That\u0026rsquo;s what we are going to do here.\nDefining the Rule in the Database # The bulk of the work in this section is actual about how to get our database to simulate a real-world database that enforces rules about how data can be saved. So let\u0026rsquo;s get to it\u0026hellip;\nIn order to enforce this rule in the database, we\u0026rsquo;re going to need a way to report that it has been broken. This will be with a Checked Exception:\npublic class CustomerAlreadyOnFileException extends Exception { public CustomerAlreadyOnFileException(String accountNumber) { super(\u0026#34;Customer account: \u0026#34; + accountNumber + \u0026#34; is already on file\u0026#34;); } } That\u0026rsquo;s pretty simple. Now, we are going to need to put some code in our database to establish \u0026ldquo;account_number\u0026rdquo; as a unique index in our database:\npublic class CustomerDatabase { private final Map\u0026lt;Integer, Map\u0026lt;String, String\u0026gt;\u0026gt; data = new HashMap\u0026lt;\u0026gt;(); private Integer nextKey = 0; public int saveNewCustomer(Map\u0026lt;String, String\u0026gt; customerRecord) throws CustomerAlreadyOnFileException, IllegalArgumentException { Optional\u0026lt;String\u0026gt; accountNumberOptional = getAccount(customerRecord); if (accountNumberOptional.isPresent()) { String accountNumber = accountNumberOptional.get(); if (!isAccountOnFile(accountNumber)) { return saveCustomer(customerRecord); } else { throw new CustomerAlreadyOnFileException(accountNumber); } } else { throw new IllegalArgumentException(\u0026#34;Account number must be included in Customer Record\u0026#34;); } } int saveCustomer(Map\u0026lt;String, String\u0026gt; customerRecord) { customerRecord.put(\u0026#34;_id\u0026#34;, (++nextKey).toString()); data.put(nextKey, customerRecord); return nextKey; } boolean isAccountOnFile(String accountNumber) { return data.values().stream().anyMatch(record -\u0026gt; getAccount(record).map(value -\u0026gt; value.equals(accountNumber)).orElse(false)); } private Optional\u0026lt;String\u0026gt; getAccount(Map\u0026lt;String, String\u0026gt; customerRecord) { return Optional.ofNullable(customerRecord.get(\u0026#34;account_number\u0026#34;)); } Map\u0026lt;Integer, Map\u0026lt;String, String\u0026gt;\u0026gt; getData() { return data; } } We\u0026rsquo;ve now wrapped the call to saveCustomer in a new method called saveNewCustomer that has the logic to ensure that duplicate account numbers are not saved. This new method is now the public method that will be called from the DAO. The names accurately represent what they do.\nIn order to enforce the unique account numbers, we\u0026rsquo;ll need to search through the existing records, extract the \u0026ldquo;account_number\u0026rdquo; field from each one and check if it matches our new account number, which we\u0026rsquo;ll also have to extract from the new customer record.\nA method called getAccount() was created to do the extraction of the account number from a customer record. At this point we need some further explanation:\nBecause \u0026ldquo;account_number\u0026rdquo; is now an index value, it\u0026rsquo;s not allowed - at the database level - to be empty. Ignore the fact that our front-end doesn\u0026rsquo;t allow empty, that doesn\u0026rsquo;t matter this far down as we cannot know what the GUI is doing. This is now a database rule and needs to be enforced here. So getAccount() now returns an Optional, and it\u0026rsquo;s up to saveNewCustomer() to deal with the Optional.isEmpty() situation - which is to throw an exception and refuse to save the record.\nChecked vs Unchecked Exceptions # A little diversion here to explain why only one of these exceptions is checked\u0026hellip;\nChecked exceptions are used when there\u0026rsquo;s a possibility of a method failing for unavoidable reasons that can occur at runtime. In this case, we have a database that has no retrieve function, so there\u0026rsquo;s no way for our DAO to check that the record isn\u0026rsquo;t on file before attempting to save it.\nBut even if we did have a retrieve function there, it still might be reasonable to have a duplicate record. Some other user, or some other process or thread might sneak in and create the record in between our call to do a test retrieve and our call to save the record. It\u0026rsquo;s extremely unlikely, but it\u0026rsquo;s not impossible, and that means we have to have some way to deal with it in our code.\nOn the other hand, the requirement to have an \u0026ldquo;account_number\u0026rdquo; field in our new customer record is something that the DAO can check before calling CustomerDatabase.saveNewCustomer(). We need to check for it in our database because allowing this to happen would corrupt our database, but it should never occur in production because the DAO should prevent it from happening.\nChecked Exception An issue that cannot be avoided by writing good code, but must be handled by the code when it occurs in production. Unchecked Exception An issue that needs to guarded against but can absolutely be avoided by writing good code in the calling method. An unchecked exception usually indicates a program bug. Testing the Database # This is going to add a whole new set of testing requirements to our database. So clean-up and reorganization of the test class has been done, as well as to add some new test cases:\nclass CustomerDatabaseTest { CustomerDatabase dataBase; @BeforeEach void init() { dataBase = new CustomerDatabase(); } @Test void saveCustomerIdIncrementTest() { assertEquals(1, dataBase.saveCustomer(createFred()), \u0026#34;Id is incremented\u0026#34;); } @Test void saveCustomerIdIncrementTwiceTest() { dataBase.saveCustomer(createFred()); assertEquals(2, dataBase.saveCustomer(createGeorge()), \u0026#34;Id is incremented\u0026#34;); } @Test void saveCustomerIdInsertionTest() { dataBase.saveCustomer(createFred()); assertEquals(\u0026#34;1\u0026#34;, dataBase.getData().get(1).get(\u0026#34;_id\u0026#34;), \u0026#34;Id is inserted\u0026#34;); } @Test void saveCustomerRecordCorrectTest() { dataBase.saveCustomer(createFred()); assertEquals(\u0026#34;Fred\u0026#34;, dataBase.getData().get(1).get(\u0026#34;name\u0026#34;), \u0026#34;Id is inserted\u0026#34;); } @Test void findAccountTest_Found() { dataBase.saveCustomer(createFred()); dataBase.saveCustomer(createGeorge()); assertTrue(dataBase.isAccountOnFile(\u0026#34;123\u0026#34;), \u0026#34;Lookup Fred\u0026#34;); } @Test void findAccountTest_NotFound() { dataBase.saveCustomer(createFred()); dataBase.saveCustomer(createGeorge()); assertFalse(dataBase.isAccountOnFile(\u0026#34;7777\u0026#34;), \u0026#34;Lookup someone not present\u0026#34;); } @Test void saveWithoutAccountNumber() { HashMap\u0026lt;String, String\u0026gt; customerRecord = new HashMap\u0026lt;\u0026gt;(); customerRecord.put(\u0026#34;name\u0026#34;, \u0026#34;Fred\u0026#34;); assertThrows(IllegalArgumentException.class, () -\u0026gt; dataBase.saveNewCustomer(customerRecord)); } @Test void saveRecordTwiceTest() { dataBase.saveCustomer(createFred()); assertThrows(CustomerAlreadyOnFileException.class, () -\u0026gt; dataBase.saveNewCustomer(createFred())); } private Map\u0026lt;String, String\u0026gt; createFred() { HashMap\u0026lt;String, String\u0026gt; customerRecord = new HashMap\u0026lt;\u0026gt;(); customerRecord.put(\u0026#34;name\u0026#34;, \u0026#34;Fred\u0026#34;); customerRecord.put(\u0026#34;account_number\u0026#34;, \u0026#34;123\u0026#34;); return customerRecord; } private Map\u0026lt;String, String\u0026gt; createGeorge() { HashMap\u0026lt;String, String\u0026gt; customerRecord = new HashMap\u0026lt;\u0026gt;(); customerRecord.put(\u0026#34;name\u0026#34;, \u0026#34;George\u0026#34;); customerRecord.put(\u0026#34;account_number\u0026#34;, \u0026#34;567\u0026#34;); return customerRecord; } } Added here are some tests for isAccountOnFile(), as well as for the two exceptions that might be thrown.\nHandling the Exception # The situation where you have a method that calls another method that, in turn, calls another method, and so on - and the method all the way at the bottom throws a checked exception happens a lot. Your code needs to work backwards through the method chain, deciding what to do with exception in each method. There are three options available in each method. Here they are, in order of preference:\nHandle it Transform it into a more meaningful exception and throw that. Throw it to the calling method. Obviously, if a method can effectively deal with an exception, then that\u0026rsquo;s end of it and the method that calls it need not know that it happened. If your method can\u0026rsquo;t deal with it, then it\u0026rsquo;s possible that your method can add better context to what has happened, in which case it should throw a new exception.\nOtherwise, just specify that the method can throw that exception type in it\u0026rsquo;s signature and let it move on up to the previous method in the call chain.\nLet\u0026rsquo;s look at the DAO and the Broker.\nThere\u0026rsquo;s nothing the DAO can do about this error, and there\u0026rsquo;s no context that the DAO can meaningfully add to the error, either. So that DAO should just let it propagate up to the calling method in the Broker.\nThe Broker can\u0026rsquo;t do anything with it either. Once again, there\u0026rsquo;s no context that it can add. So it just throws it again and it goes to the Interactor.\nHandling the Exception in the Interactor # The Interactor is the lowest class that can actually do something about this error, as deciding what should happen next is part of the business logic. So, what should it do?\nThe main thing that needs to happen is that we notify the user that it couldn\u0026rsquo;t be saved and tell them why. This is something that the Interactor cannot do by itself. We\u0026rsquo;ll need to get the Controller involved. We also need to transform this exception into some sort of data that we can communicate back to the Controller.\nWe only have two possible results: the save worked, or the saved failed because of a duplicate account. This means we can get away with a simple Boolean result.\nHere\u0026rsquo;s the new Interactor code:\npublic boolean saveCustomer() { String customerName = model.getCustomerName(); String account = model.getAccountNumber(); try { int recordId = broker.saveCustomer(createCustomerFromModel()); System.out.println(\u0026#34;Saving account: \u0026#34; + account + \u0026#34; Name: \u0026#34; + customerName + \u0026#34; Result: \u0026#34; + recordId); return true; } catch (CustomerAlreadyOnFileException e) { return false; } } We\u0026rsquo;ve changed the return value from void to boolean. Then we return true unless we catch the CustomerAlreadyOnFileException where we return false.\nCommunicating With the User # The Controller is going to be responsible for making sure that the user is told about this issue. We\u0026rsquo;re going to use an Alert to do this.\nWe need to transform our Task from one that returns Void to one the returns Boolean. Then we can test for this in our onSuccess EventHandler. Task always completes with success unless its call generates an uncaught exception. Since we\u0026rsquo;re catching the exception in the Interactor it will still complete successfully, even though the save itself failed.\nHere\u0026rsquo;s what our method looks like now:\nprivate void saveCustomer(Runnable postTaskGuiActions) { Task\u0026lt;Boolean\u0026gt; saveTask = new Task\u0026lt;\u0026gt;() { @Override protected Boolean call() { return interactor.saveCustomer(); } }; saveTask.setOnSucceeded(evt -\u0026gt; {Alert postTaskGuiActions.run(); if (!saveTask.getValue()) { Alert alert = new Alert(Alert.AlertType.ERROR); alert.setContentText(\u0026#34;This customer is already on file, cannot save.\u0026#34;); alert.show(); } }); Thread saveThread = new Thread(saveTask); saveThread.start(); } And this is what it looks like:\nWe still need to run the postTaskGuiActions, but now we need to check the results of the Task\u0026rsquo;s call() method to see if the save was successful. If it wasn\u0026rsquo;t then we\u0026rsquo;ll show an Alert with an appropriate message.\n","externalUrl":null,"permalink":"/beginners/part10/","section":"JavaFX","summary":"The last problem with our application is that it can save duplicate customer records, corrupting our database.  This means adding some rules to our database and telling our GUI when those rules have been broken.  We’ll see how to handle exceptions from the back-end in our application.","title":"Part 10 - Handling Database Errors","type":"javafx"},{"content":" Reviewing What We\u0026rsquo;ve Done So Far # At this point we\u0026rsquo;ve actually got a simple, but fully functional \u0026ldquo;Create\u0026rdquo; application. It doesn\u0026rsquo;t have anything more than an account number and a name, but to get this far we had to put together a lot of moving parts.\nThis is probably a good time to pause, and look at what we\u0026rsquo;ve done.\nWe\u0026rsquo;ve Built on a Framework # If you\u0026rsquo;ve followed along this far, then the value of building on a framework should be readily apparent.\nHere are some of the things it does for us:\nWe Can Use Unit Tests Because we now have a pretty clear dividing line between the Reactive JavaFX code and the rest of our application, it\u0026rsquo;s now clear that some of our code is just plain old Java code and there\u0026rsquo;s nothing special about it. That means that we can test it, and in turn that means that we should test it. Our GUI is Just the GUI There\u0026rsquo;s no business logic in our GUI, it\u0026rsquo;s just layout. Coupling is Minimized Within the framework, there\u0026rsquo;s no dependency between the View and the Interactor. They both deal with the Model, but don\u0026rsquo;t even know of the existence of each other. Within our View, the various screen widgets have virtually no knowledge of one another. Everything is Easy to Find There\u0026rsquo;s no need to go looking for code. You won\u0026rsquo;t find business rules in the View or the Model, they\u0026rsquo;re all in the Interactor. Everything is Small No 1000 line classes here. Keeping everything broken up into discrete parts means that all of the parts stay small. Other Important Ideas # Reactive Programming Through this processes, we\u0026rsquo;ve seen how Reactive programming concepts make it easier to separate our components without having to worry about how they are going to be used. Having a synchronized data object holding our application state enables this. Background Threads We\u0026rsquo;ve learned about the importance of keeping blocking operations off the FXAT, and how to use Task to start up a process that runs on a background thread. We\u0026rsquo;ve also learned how to set up our GUI before a background task starts, and how to trigger the GUI to respond to the completion of a background task. Single Responsibility Principle We looked at how a method should only be directly responsible for a single thing and how, by delegating to other methods our code stays simple and easy to read. Don\u0026rsquo;t Repeat Yourself (DRY) We\u0026rsquo;ve seen that this is the most important principle for dealing with the boilerplate code that bog down a JavaFX layout. As we go on, we\u0026rsquo;ll see this become even more important. GUI Validation We\u0026rsquo;ve applied validation to our GUI to enforce business rules built by the Interactor and embedded into the Model. Simulated Database and Access We\u0026rsquo;ve seen how to simulate a database and connect it to our Interactor through a Broker and a DAO. We\u0026rsquo;ve dealt with the issues that latency can have when testing these components. Handling Exceptions We\u0026rsquo;ve learned about the difference between checked and unchecked exceptions and how to deal with checked exceptions that happen deep down a chain of method calls. We learned how to transform an exceptions into an action result, and integrate it into our UX. Moving On From Here # Our next steps are going to build on what we\u0026rsquo;ve done so far:\nAdd Another Field We\u0026rsquo;ll add a \u0026ldquo;Phone Number\u0026rdquo; field to the screen so that we can see how the feature build cuts all the way through the application from GUI to the database. Clean Up the GUI It\u0026rsquo;s going to become clear that our GUI is a bit ugly when we add another field. In the process of cleaning up the GUI, we\u0026rsquo;ll see how our layout has become cluttered with stuff that isn\u0026rsquo;t strictly\u0026hellip;layout. We\u0026rsquo;ll clean that up and see how an even nicer looking GUI can actually take up much less code in our ViewBuilder. Split the Name We\u0026rsquo;ll divide the \u0026ldquo;name\u0026rdquo; field up into three parts including a \u0026ldquo;salutation\u0026rdquo; field that will let us use a ComboBox. Add the Rest of the Fields We\u0026rsquo;ll add a bunch of address fields to our screen, and then down through to our database. Add a \u0026ldquo;Retrieve\u0026rdquo; Function Finally, we\u0026rsquo;ll be ready to add the next CRUD function, \u0026ldquo;Retrieve\u0026rdquo;. Without \u0026ldquo;Retrieve\u0026rdquo; we can\u0026rsquo;t do \u0026ldquo;Update\u0026rdquo; or \u0026ldquo;Delete\u0026rdquo; ","externalUrl":null,"permalink":"/beginners/part11/","section":"JavaFX","summary":"At this point we have a pretty clean “Create” application.  It’s error resistant, doesn’t corrupt our database and ready to add some more functionality.  Let’s look at what we’ve done so far.","title":"Part 11 - A \"Working\" Application","type":"javafx"},{"content":" What You\u0026rsquo;ll Learn # How easy it is to expand the application by a single field Some more DRY How to keeping code clean as we expand functionality Adding a Field # At this point we\u0026rsquo;re going to add a single field to our screen. We\u0026rsquo;re going to do just one field because that will clearly show the process for extending the scope of our application, in this respect, down through the entire application. Later on, we\u0026rsquo;ll add a bunch of fields at once, but it will be virtually the same operation, but just with more than one field.\nAs we do this, we\u0026rsquo;re going to see the potential for this process to corrupt our nice clean code. We vigilant about how our code follows the two key priciples: Dry and the Single Responsibility Principle.\nStart with the Data # We have three kinds of data in our application:\nThe Model - which represents State Customer - which is a \u0026ldquo;Domain Object\u0026rdquo; Database record - which is the external data format of our database Since our database records are essentially free-form, there won\u0026rsquo;t be any need to make any changes there, nor will we need to write any new tests at that layer of our application.\nLet\u0026rsquo;s look at the new Model:\npublic class CustomerModel { private final StringProperty accountNumber = new SimpleStringProperty(\u0026#34;\u0026#34;); private final StringProperty customerName = new SimpleStringProperty(\u0026#34;\u0026#34;); private final StringProperty email = new SimpleStringProperty(\u0026#34;\u0026#34;); private final BooleanProperty okToSave = new SimpleBooleanProperty(false); } and now, the Customer:\npublic class Customer { private String accountNumber = \u0026#34;\u0026#34;; private String name = \u0026#34;\u0026#34;; private String email = \u0026#34;\u0026#34;; } All the getters and setters have been left out - you should know how that stuff works.\nThese changes are pretty trivial, we\u0026rsquo;ve simply added a new String-based field to each class.\nThe Broker # The job of the Broker is to turn the Customer object into a database record. So we need to account for the new field:\nMap\u0026lt;String, String\u0026gt; createCustomerRecord(Customer customer) { Map\u0026lt;String, String\u0026gt; customerRecord = new HashMap\u0026lt;\u0026gt;(); customerRecord.put(\u0026#34;name\u0026#34;, customer.getName()); customerRecord.put(\u0026#34;account_number\u0026#34;, customer.getAccountNumber()); customerRecord.put(\u0026#34;email\u0026#34;, customer.getEmail()); return customerRecord; } Of course, new functionality means a new test:\nclass CustomerBrokerTest { @Test void createCustomerRecord_AccountNumberTest() { CustomerBroker broker = new CustomerBroker(); Customer customer = createCustomer(); assertEquals(\u0026#34;1234\u0026#34;, broker.createCustomerRecord(customer).get(\u0026#34;account_number\u0026#34;), \u0026#34;Account number check\u0026#34;); } @Test void createCustomerRecord_CustomerNameTest() { CustomerBroker broker = new CustomerBroker(); Customer customer = createCustomer(); assertEquals(\u0026#34;Fred\u0026#34;, broker.createCustomerRecord(customer).get(\u0026#34;name\u0026#34;), \u0026#34;Customer name check\u0026#34;); } @Test void createCustomerRecord_EmailTest() { CustomerBroker broker = new CustomerBroker(); Customer customer = createCustomer(); assertEquals(\u0026#34;abc@def.com\u0026#34;, broker.createCustomerRecord(customer).get(\u0026#34;email\u0026#34;), \u0026#34;Customer email check\u0026#34;); } private Customer createCustomer() { Customer customer = new Customer(); customer.setAccountNumber(\u0026#34;1234\u0026#34;); customer.setName(\u0026#34;Fred\u0026#34;); customer.setEmail(\u0026#34;abc@def.com\u0026#34;); return customer; } } It\u0026rsquo;s become obvious that we were getting into some repeated code here, and it was only going to get worse as we added more fields. So DRY kicks in and we\u0026rsquo;ve put the repeated code into a method, createCustomer(). Other than that, we\u0026rsquo;ve just added a single test case to handle the email address.\nThe Interactor # This change affects the Interactor in one spot - when it creates the Customer from the Model. So we\u0026rsquo;ll update that method and then add a test case:\nCustomer createCustomerFromModel() { Customer customer = new Customer(); customer.setAccountNumber(model.getAccountNumber()); customer.setName(model.getCustomerName()); customer.setEmail(model.getEmail()); return customer; } and the test class:\nclass CustomerInteractorTest { @Test void createCustomer_NameTest() { CustomerModel model = createCustomerModel(); CustomerInteractor interactor = new CustomerInteractor(model); assertEquals(\u0026#34;Fred\u0026#34;, interactor.createCustomerFromModel().getName(), \u0026#34;Check customer name\u0026#34;); } @Test void createCustomer_AccountTest() { CustomerModel model = createCustomerModel(); CustomerInteractor interactor = new CustomerInteractor(model); assertEquals(\u0026#34;ABCDE\u0026#34;, interactor.createCustomerFromModel().getAccountNumber(), \u0026#34;Check customer name\u0026#34;); } @Test void createCustomer_EmailTest() { CustomerModel model = createCustomerModel(); CustomerInteractor interactor = new CustomerInteractor(model); assertEquals(\u0026#34;abc@def.com\u0026#34;, interactor.createCustomerFromModel().getEmail(), \u0026#34;Check customer email\u0026#34;); } private CustomerModel createCustomerModel() { CustomerModel model = new CustomerModel(); model.setCustomerName(\u0026#34;Fred\u0026#34;); model.setAccountNumber(\u0026#34;ABCDE\u0026#34;); model.setEmail(\u0026#34;abc@def.com\u0026#34;); return model; } } You\u0026rsquo;ll see that we\u0026rsquo;ve applied DRY again here, to keep our code as simple as possible.\nFinally, The View # At this point we have a back-end that will handle our new field; it\u0026rsquo;s tested and ready to go. All we need to do now is to add it to our View:\nprivate Node createCentre() { VBox results = new VBox(6, accountBox(), nameBox(), emailBox()); results.setPadding(new Insets(20)); return results; } private Node accountBox() { return new HBox(6, promptLabel(\u0026#34;Account #:\u0026#34;), boundTextField(model.accountNumberProperty())); } private Node nameBox() { return new HBox(6, promptLabel(\u0026#34;Name:\u0026#34;), boundTextField(model.customerNameProperty())); } private Node emailBox() { return new HBox(6, promptLabel(\u0026#34;eMail:\u0026#34;), boundTextField(model.emailProperty())); } Do you see how simple that is? We just add a new builder method to create the HBox, then call it from the constructor of the Centre\u0026rsquo;s VBox.\nHowever, we do have some repeated code that\u0026rsquo;s starting to get ugly. This is a little bit harder to see, since we\u0026rsquo;ve already applied DRY to pull the HBox creation out into separate methods. But all three of these methods are pretty much the same, we just put in different strings and Properties. So let\u0026rsquo;s fix that:\nLet\u0026rsquo;s try it:\nprivate Node createCentre() { VBox results = new VBox(6, accountBox(), nameBox(), emailBox()); results.setPadding(new Insets(20)); return results; } private Node accountBox() { return rowBox(\u0026#34;Account #:\u0026#34;, model.accountNumberProperty()); } private Node nameBox() { return rowBox(\u0026#34;Name:\u0026#34;, model.customerNameProperty()); } private Node emailBox() { return rowBox(\u0026#34;eMail:\u0026#34;, model.emailProperty()); } private Node rowBox(String prompt, StringProperty boundProperty) { return new HBox(6, promptLabel(prompt), boundTextField(boundProperty)); } That\u0026rsquo;s removed the repeated code, as much as we can. We could stop at this point, but it\u0026rsquo;s probably better to refactor out those three methods since they probably don\u0026rsquo;t add much value any more.\nThe question here is: Which is more clear? Having methods with names that describe what they are doing, or having less code? Personally, I lean towards less code, following the idea that, \u0026ldquo;less code is better code.\u0026rdquo;\nLet\u0026rsquo;s see what it looks like:\nprivate Node createCentre() { VBox results = new VBox(6, rowBox(\u0026#34;Account #:\u0026#34;, model.accountNumberProperty()), rowBox(\u0026#34;Name:\u0026#34;, model.customerNameProperty()), rowBox(\u0026#34;eMail:\u0026#34;, model.emailProperty())); results.setPadding(new Insets(20)); return results; } private Node rowBox(String prompt, StringProperty boundProperty) { return new HBox(6, promptLabel(prompt), boundTextField(boundProperty)); } Honestly, I think this is clearer. The prompt text is what you\u0026rsquo;ll see on the screen, so it\u0026rsquo;s what you\u0026rsquo;d be looking for in the code. The method names are OK, but this is much more compact, and the intent of the code is obvious. If you want to know the gory details about rowBox(), then go look at it. But you probably won\u0026rsquo;t.\nAnd if there were 30 rows of prompt/TextFields, this code would still be clear. You can also see that adding a new field just adds another parameter to the VBox constructor. We\u0026rsquo;ll stick with this.\nWrapping it Up # At this point it should be clear that adding a new field, or a dozen new fields, is just a matter of adding a row to the GUI and then following the data down to the Broker. It\u0026rsquo;s rinse and repeat, \u0026ldquo;paint by numbers\u0026rdquo; for each field.\nWhat\u0026rsquo;s far more important is that you keep your code clean as you do this. Things that seemed reasonable with just one or two fields become obviously bad as you add more and more to your application. If not actually \u0026ldquo;bad\u0026rdquo;, then certainly \u0026ldquo;sub-optimal\u0026rdquo;. Most of the things we fixed in this lesson were already issues with the code; things that, to be honest, we should have already fixed. They really jumped out as problems once we added that third field.\nYou need to constantly take a step back and look at your code with a critical eye and make sure that you\u0026rsquo;re keeping on top of emerging issues. Make sure that you\u0026rsquo;re following DRY and the Single Responsibility Principle. Make sure that in those cases where you are not, that it\u0026rsquo;s because you think the code is cleaner in its current state.\nAnd don\u0026rsquo;t forget, as we\u0026rsquo;ve seen here, all of this goes for your JUnit test classes as well.\n","externalUrl":null,"permalink":"/beginners/part12/","section":"JavaFX","summary":"It’s finally time to add a third field to our screen!  In this article we’re going to add just a single new field, for email, to our screen.  We’re going to see how the process goes from the View all the way down to the Broker.  Most importantly, we going to see how we need to be vigilant in ensuring that our code stays clean as we add more features.","title":"Part 12 - Adding a New Field: eMail","type":"javafx"},{"content":" What You\u0026rsquo;ll Learn # The idea of Custom Widgets as View building blocks What is Feature Envy The principle of \u0026ldquo;Put Stuff Where it Belongs\u0026rdquo; Building a Widgets library How much cleaner code is when Feature Envy is dealt with Introduction # Even though we\u0026rsquo;ve been really careful to try to apply DRY and the Single Responsibility Principle, our ViewBuilder code has started to grow some weeds. It\u0026rsquo;s not as clean as it can be, and it now suffers from \u0026ldquo;Feature Envy\u0026rdquo;. Let\u0026rsquo;s look at what\u0026rsquo;s happened\u0026hellip;\nCustom Widgets # When I build screens I tend to think about them in terms of smaller pieces as building blocks, and that these smaller pieces are often complete, self-contained pieces of functionality. I think of them as \u0026ldquo;Custom Widgets\u0026rdquo;. I like the term \u0026ldquo;Widgets\u0026rdquo; better than \u0026ldquo;Nodes\u0026rdquo;, because the term \u0026ldquo;Node\u0026rdquo; has a specific definition as a Class type in JavaFX. \u0026ldquo;Widget\u0026rdquo;, however, is a generic term commonly used in talking about GUI\u0026rsquo;s and it simply refers to a screen element that interacts with the user. So, a widget can be just about anything on the screen, but a Node refers to a JavaFX class.\nA \u0026ldquo;Custom Widget\u0026rdquo; is something that somebody, probably you, has built, styled and configured. It can be, but usually isn\u0026rsquo;t, an extension of a Node class, and can be a container class with other widgets inside it.\nLet\u0026rsquo;s take a look at a piece code from our GUI:\nprivate Node rowBox(String prompt, StringProperty boundProperty) { return new HBox(6, promptLabel(prompt), boundTextField(boundProperty)); } The thing that this method returns is a custom widget. It\u0026rsquo;s an HBox, but it\u0026rsquo;s been configured and populated with a Label and a TextField. But it\u0026rsquo;s not treated as an HBox. It\u0026rsquo;s really just a generic widget that performs a function and that is placed into a layout. That\u0026rsquo;s why it\u0026rsquo;s returned as Node.\nNow, what does this widget do? Well\u0026hellip;it puts a prompt Label on the screen and a TextField beside it. The name \u0026ldquo;rowBox()\u0026rdquo; implies that it\u0026rsquo;s going to be oriented horizontally - in a row.\nThink about these questions:\nOnce we\u0026rsquo;ve instantiated it, do we care that it has a Label in it? No, not really. We care that there\u0026rsquo;s something on the screen that indicates whatever our prompt parameter specifies, but we don\u0026rsquo;t really care that it\u0026rsquo;s a Label. It could be a Text, or it could be an ImageView, or graphic of some sort. It doesn\u0026rsquo;t need to be a Label. We don\u0026rsquo;t care. What about the TextField? We don\u0026rsquo;t really care about that either. It could be any kind of control that allows user input that can be bound to our boundProperty parameter. Maybe TextArea, or some custom input control we downloaded in a library. As long as we get user input, we don\u0026rsquo;t really care. The important point is that everything that rowBox() needs to do it\u0026rsquo;s job is passed to rowBox() as a parameter. When we get the result back from rowBox(), we don\u0026rsquo;t care about any aspect of it except that we can put it in our layout. It\u0026rsquo;s self-contained. We can treat it as a fairly anonymous custom widget, that does its job.\nFeature Envy # \u0026ldquo;Feature Envy\u0026rdquo; is when a component (usually a class) takes on functionality which doesn\u0026rsquo;t really belong to it. It grows more features so it can be more important!\nWe want to avoid Feature Envy as much as possible.\nFeature Envy is the opposite of the principle of, \u0026ldquo;Put Stuff Where it Belongs\u0026rdquo;. And this idea itself is really is just a natural outcome of following the Single Responsibility Principle, and DRY.\nLet\u0026rsquo;s take a look at another method in our ViewBuilder to see how this works:\nprivate Node boundTextField(StringProperty boundProperty) { TextField textField = new TextField(); textField.textProperty().bindBidirectional(boundProperty); return textField; } We added this method to stick to DRY and to avoid repeating the code inside the method. But let\u0026rsquo;s take a closer look at it.\nFirst of all, the return value is essentially another Custom Widget. It\u0026rsquo;s a bound TextField.\nWhat does this code have to do with our layout, or our ViewBuilder class? Nothing much other than the fact that it\u0026rsquo;s called from it. It doesn\u0026rsquo;t use any fields from the class, nor does it call any other methods in the class. In fact, it could be somewhere else and it wouldn\u0026rsquo;t matter. That\u0026rsquo;s a problem, because stuff should only be in a class because it really needs to be there.\nLet\u0026rsquo;s look at it from the perspective of DRY. In any normal business application, you\u0026rsquo;re likely to have many screens, and each and every one of them is likely to have TextFields that are bidirectionally bound to StringProperties. Are you going to repeat this method in every one of those ViewBuilders? And is it going to be exactly the same code in each one? Yes, it would be if you did it, and this would be a clear violation of DRY - on an application level.\nNow let\u0026rsquo;s look at it from the perspective of the Single Responsibility Principle, but at the level of our ViewBuilder class. It\u0026rsquo;s only direct responsibility is to build and configure the View layout. This method handles a detail outside of that responsibility, and should be delegated to somewhere else.\nFinally - and this is really telling - this method is only called from rowBox() which itself is another method that has nothing to do with our layout.\nLet\u0026rsquo;s Put These General Purpose Custom Widgets Somewhere Else # When something doesn\u0026rsquo;t belong somewhere, then you have to put it someplace else. Here we are talking about fairly generic methods; ones that accept plain data and standard JavaFX properties as parameters, and return configured standard JavaFX Nodes. None of it has anything to do with our application directly. So, \u0026ldquo;someplace else\u0026rdquo;, means some other class in some other package.\nI\u0026rsquo;ve created a new class with static methods called Widgets. I\u0026rsquo;m putting it in a package called \u0026ldquo;widgets\u0026rdquo;, and it looks like this:\npublic class Widgets { public static Node promptLabel(String contents) { return styledLabel(contents, \u0026#34;prompt-label\u0026#34;); } public static Node headingLabel(String contents) { return styledLabel(contents, \u0026#34;heading-label\u0026#34;); } public static Node styledLabel(String contents, String styleClass) { Label label = new Label(contents); label.getStyleClass().add(styleClass); return label; } public static Node promptedTextField(String prompt, StringProperty boundProperty) { return new HBox(6, promptLabel(prompt), boundTextField(boundProperty)); } public static Node boundTextField(StringProperty boundProperty) { TextField textField = new TextField(); textField.textProperty().bindBidirectional(boundProperty); return textField; } } It has all of the Feature Envy methods from our ViewBuilder class. The name rowBox() has been changed to promptedTextField() because it needs to be more easily understood without any context. Maybe promptedInput() would be better? I don\u0026rsquo;t know. Naming things is hard!\nOn the subject of naming things: For these kinds of methods I prefer names that describe the returned value, not an action type name. The normal rules of naming would have use names like createHeadingLabel() or makeStyledLabel(), but these don\u0026rsquo;t really add any clarity in the context of a Widget library. Furthermore, they just make the names longer, which then makes them more cumbersome to use in constructors for container classes.\nSo I prefer to stick to names like Widgets.headingLabel() and to think of the value that it returns as HeadingLabel - even though it\u0026rsquo;s not an actual class that is returned. This makes sense to me, so it\u0026rsquo;s the convention I follow.\nAll of this stuff is now out of our layout code:\npublic class CustomerViewBuilder implements Builder\u0026lt;Region\u0026gt; { private final CustomerModel model; private final Consumer\u0026lt;Runnable\u0026gt; saveHandler; public CustomerViewBuilder(CustomerModel model, Consumer\u0026lt;Runnable\u0026gt; saveHandler) { this.model = model; this.saveHandler = saveHandler; } @Override public Region build() { BorderPane results = new BorderPane(); results.getStylesheets().add(Objects.requireNonNull(this.getClass().getResource(\u0026#34;/css/customer.css\u0026#34;)).toExternalForm()); results.setTop(Widgets.headingLabel(\u0026#34;Customer Information\u0026#34;)); results.setCenter(createCentre()); results.setBottom(createButtons()); return results; } private Node createCentre() { VBox results = new VBox(6, Widgets.promptedTextField(\u0026#34;Account #:\u0026#34;, model.accountNumberProperty()), Widgets.promptedTextField(\u0026#34;Name:\u0026#34;, model.customerNameProperty()), Widgets.promptedTextField(\u0026#34;eMail:\u0026#34;, model.emailProperty())); results.setPadding(new Insets(20)); return results; } private Node createButtons() { Button saveButton = new Button(\u0026#34;Save\u0026#34;); saveButton.disableProperty().bind(model.okToSaveProperty().not()); saveButton.setOnAction(evt -\u0026gt; { saveButton.disableProperty().unbind(); saveButton.setDisable(true); saveHandler.accept(() -\u0026gt; saveButton.disableProperty().bind(model.okToSaveProperty().not())); }); HBox results = new HBox(10, saveButton); results.setAlignment(Pos.CENTER_RIGHT); return results; } } Once you take it out, you realize how much those Feature Envy methods were messing up the layout code. You can take it in at a glance now. In comparison, the previous version of our ViewBuilder was cluttered.\nOne Last Point # Imagine for a minute that JavaFX came equipped with - straight out of the box - a Node class that did exactly what promptedTextField() does. Let\u0026rsquo;s call it PromptedTextField, and it had a constructor that took a String and a StringProperty.\nWould you go and look at the source code for it to see how it works, or to check if it was working properly?\nNo, you wouldn\u0026rsquo;t. I might, but you wouldn\u0026rsquo;t. You\u0026rsquo;d use it and you\u0026rsquo;d be glad it was there and assume that it was going to work properly.\nWidgets is really nothing different. It\u0026rsquo;s a set of utilities that are so generic that you\u0026rsquo;ll use them over, and over, and over again. And, because you use them all the time, you know that they will work properly. Most of them, just like the ones here, are so simple that there\u0026rsquo;s no reason to worry about them not working properly.\nBut they shift a huge burden of detail out of your layout code. And they shift a huge burden out of your mind when you\u0026rsquo;re working with your layout code. That\u0026rsquo;s a huge win.\n","externalUrl":null,"permalink":"/beginners/part13/","section":"JavaFX","summary":"In this article we’re going to look at how Feature Envy has crept into our View code.  We’re going to look at why this happens, and how to deal with it.  Finally, we’re going to see how clearing out Feature Envy makes our code much cleaner and easier to understand.","title":"Part 13 - Feature Envy","type":"javafx"},{"content":"","date":"24 June 2026","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"24 June 2026","externalUrl":null,"permalink":"/categories/homelab/","section":"Categories","summary":"","title":"Homelab","type":"categories"},{"content":"","date":"24 June 2026","externalUrl":null,"permalink":"/homelab/","section":"Homelabs","summary":"","title":"Homelabs","type":"homelab"},{"content":"","date":"24 June 2026","externalUrl":null,"permalink":"/","section":"PragmaticCoding","summary":"","title":"PragmaticCoding","type":"page"},{"content":" Introduction # As someone who has already gone through the process of registering and managing a domain for my public website, specifically \u0026ldquo;pragmaticcoding.ca\u0026rdquo;, it was a fairly trivial step to register one for personal use. I already had a GoDaddy account from years and years ago, so it was just a matter of finding a domain name and charging it to my account.\nBut why would you want to do this? How can it help with self-hosting? Let\u0026rsquo;s take a look\u0026hellip;\nWhat is a Public Domain? # A \u0026ldquo;public domain\u0026rdquo; (my term, since I couldn\u0026rsquo;t find a better one anywhere on the web) is any domain that is registered into the system of domains that are managed by the Internet Corporation for Assigned Names and Numbers (ICANN).\nEveryone is familiar with the \u0026ldquo;top level\u0026rdquo; domains. These are things like .com and .net and .uk or .ca. There are also some subdomains of these that you see quite often. You might have seen British domains that are part of .co.uk, because in the UK, all of the commercial sites have been forced into .co.uk domain. In Canada you might see a .on.ca or a .bc.ca which are domains for Ontario and British Columbia. Back in the early days of the internet, to get a .ca domain, you needed to prove that you did business across several provinces. Otherwise they told you to register with your provincial domain. They don\u0026rsquo;t do that any more.\nIf your domain ends with any of the ICANN top level domains, then it\u0026rsquo;s what I would call a \u0026ldquo;public domain\u0026rdquo;.\nAnd you should have one.\nRegistering a Domain # Each of these higher level domains maintain a registrar. If you want to get a subdomain of one of these domains, you must put in a request with the registrar. In practice, nobody deals directly with the registrars. Instead you deal with a company that acts as an agent (or maybe a delegate of a registrar), and you pay through them. These are companies like GoDaddy, and NameCheap. In Canada there is one called \u0026ldquo;Register.ca\u0026rdquo;, which isn\u0026rsquo;t the .ca registrar, but just another agent. You can register domains in .net, .org, .com as well as .ca through \u0026ldquo;Register.ca\u0026rdquo;.\nWho you pick is up to you. It literally doesn\u0026rsquo;t matter which one you pick, but it would probably be best if you pick one that\u0026rsquo;s likely to be around for a few years. This is because the future management of your domain is going to be connected to your account at that agent, and tranferring it might be problematic if they go out of business with little warning. Most of these companies also provide a range of other services like email and web hosting. Prices for the same domain may vary between agents, too.\nPersonally, I use GoDaddy. This is mostly because I was involved in a commercial operation many years ago that required me to set up an account with them. I still have the account, and it was easy just to use it. I\u0026rsquo;m not sure that registrations with GoDaddy are the cheapest, but they\u0026rsquo;ll probably be around for a while.\nDepending on the domain name, and the top level domain that you register with, you can expect to pay from $15 to $30 a year for a domain for your self-hosting.\nDomain Name Services # The important thing to remember about this is that all of that registration stuff is just about the names. But these names are important because they provide the structure to allow everyone on the Internet to find the actual addresses of services they are looking for. This facility is provided by \u0026ldquo;Domain Name Servers\u0026rdquo;, often just called DNS servers.\nThe one thing that every public domain must have is a DNS server that can service public requests for information about the domain. For the most part, the registrar of the parent domain that holds your domain will have some level of DNS services for your domain, although that may just redirect to the agent through which you registered the domain.\nGenerally speaking, information about your domain is stored in something called a \u0026ldquo;Zone File\u0026rdquo;. A \u0026ldquo;zone\u0026rdquo; is usually just a domain. The kind of information held in a zone file would be the addresses of the DNS servers that it uses, the addresses of the email servers that it uses and address of web servers, or application servers that it controls.\nThis is where the public part becomes very important. Anything that you put into your zone file can be seen by anyone in the internet. This is important for things like email handling, because you want email servers anywhere in the world to know where to connect to deliver email to your domain.\nThis public visibility is why you need a public domain.\nEmail Handling # Years and years and years ago, my wife and I got our first email addresses from our ISP, which was Bell Canada, and they had a service called \u0026ldquo;Sympatico\u0026rdquo;. This meant that we had name@sympatico.ca addresses. Over time, the \u0026ldquo;Sympatico\u0026rdquo; service disappeared, but those email addresses continued to work as part of the Bell service. However, we had migrated to Gmail 20 years ago, so those sympatico.ca addresses were largely unused and forgotten over time.\nIf you live in Canada, then you probably know that Canada is especially poorly served with both cell phone and internet services. There\u0026rsquo;s a tiny handful of providers, they\u0026rsquo;re ridiculously expensive, and the service is low quality.\nAbout a year ago, I finally became exhausted with the cost of my internet and TV service from Bell when the monthly bill increased by yet another $5 a month. Also, Bell is the fibre provider for most of Canada, and in my neighbourhood, which is part of a large metropolitan suburban area, they haven\u0026rsquo;t bothered to install fibre to the home. This means the fastest speed possible was a paultry 25Mbps.\nAs a result, I cancelled my fibre connection, held my nose, and signed up with the only alternative which was cable internet.\nI didn\u0026rsquo;t really think to much about it, but it was clear to me that as soon as I cancelled the service with Bell, those sympatico.ca address were going to cease to function.\nI\u0026rsquo;m suprised at how quickly the fallout happened. Just a couple of days, in fact.\nMy wife asked me, \u0026ldquo;Did anything strange happen with email, I\u0026rsquo;m not getting email updates from Expedia?\u0026rdquo;.\nIt seems that her Expedia account was so old that it still used her sympatico.ca address. Oops! You cannot change the email address on an account without having access to the old account because they send it a confirmation email that you have to acknowledge. We had some accumulated reward points that we didn\u0026rsquo;t want to lose, and Expedia\u0026rsquo;s Customer Service told us that she\u0026rsquo;d have to create a new account and then they could transfer the points over when it was set up. So that\u0026rsquo;s what we did.\nI\u0026rsquo;m sure that lots of you have email addresses provided from your ISP\u0026rsquo;s. I\u0026rsquo;m sure that you also have lots of accounts with various websites that depend on those email addresses.\nIn a way, you\u0026rsquo;re locked in with those ISP\u0026rsquo;s unless you\u0026rsquo;re willing to go through the pain and suffering of updating all of your accounts on every website that you use. And remember, you have to do this before you disconnect the ISP address. This could be a real bother if you\u0026rsquo;re moving and don\u0026rsquo;t have the option to keep your old ISP.\nYou could one of the other services, like gmail.com or hotmail.com, but then you\u0026rsquo;re tied in to those. What if, like me, you\u0026rsquo;re fed up with Gmail? They were cool back in the early, \u0026ldquo;Do no evil\u0026rdquo;, days. But those days are long gone.\nHaving a public domain solves that problem. It also means that for as long as you own that domain, you\u0026rsquo;ll never have to change your email address ever again. You can have that email address for life.\nIt also has one other key benefit: There\u0026rsquo;s no doubt that having your own domain-based email address is much more appealing than having whatever email address you could find on a public service. Think on the simplicity of george@thesmiths.org over georgesmith87302@hotmail.com.\nThere are two approaches you can take to achieve this. But first, one idea you should not pursue\u0026hellip;\nDon\u0026rsquo;t Manage Your Own eMail Server # For decades, I was involved with managing the email server for the company I worked for. In the beginning, it was really the only option, because hosted email services from providers like Google and Microsoft simply didn\u0026rsquo;t exist - especially in the corporate space. We installed a Lotus Notes server back in the 1990\u0026rsquo;s, and it was comparitively easy. Just set up the DNS to point to it and configure the Lotus Notes server and turn it on.\nOver the years, it became more and more complicated to manage this server. The big problem was spam - more specifically, the measures that the rest of the world were using to combat spam. This meant that we had to be very careful to make sure that we didn\u0026rsquo;t get blacklisted in the spam filters, and I can tell you this was a shock the first time that it happened.\nIn some ways we were lucky that we had been running our own server for years, because we had established ourselves as a legitimate source which avoided a lot of hassles.\nTechnically, it\u0026rsquo;s not difficult to set up and run an email server in your homelab today. It\u0026rsquo;s not like you\u0026rsquo;ll have to navigate the archane complexity of sendmail or anything like it. On the other hand, all the reports I\u0026rsquo;ve seen say that you\u0026rsquo;ll spend way too much time chasing around delivery problems caused by anit-spam measures implemented by your email recipient\u0026rsquo;s services.\nFinally, the other big downside to hosting your own mail server in your homelab is that, by definition, it has to be directly accessible from the Internet at large. This means that you absolutely have to open up a hole in your firewall to allow SMTP connections to come in from anywhere in the Internet. Not through a VPN, straight in. With care, this is something that you can do, but for me this is something that I\u0026rsquo;d rather not do.\nJust let somebody else handle the headache of running an email server.\nMail Redirect # This is a good alternative if you aren\u0026rsquo;t ready to ditch your Gmail account (or even your ISP\u0026rsquo;s email) but you just want to be able to use addresses from your own domain. You can set it up for free, and it works fairly reliably.\nOne caveat to this approach is that not all of the mail services that you might currently be using support the outbound redirection scheme described below. If this is the case with your mail service provider, you\u0026rsquo;ll have to go with the second solution that I describe.\nIncoming Mail # In order to have mail sent to somebody@yourdomain.org you\u0026rsquo;ll need to use an \u0026ldquo;Email Forwading\u0026rdquo; service. Your best bet here is to simply do a search for \u0026ldquo;email forwarding service\u0026rdquo; and find one that appeals to you. For what it\u0026rsquo;s worth, I\u0026rsquo;m using improvMX.com and it seems to work fine. It\u0026rsquo;s free, too - at least for forwarding just two addresses.\nYou\u0026rsquo;ll need to set up an account with one of these services and then use whatever interface they give you to set up \u0026ldquo;aliases\u0026rdquo; for the email addresses that you want to forward. The alias is the address that you want have people send to, and the forwarded address is the actual address in you email service that will receive the email. For instance you might set up somebody@yourdomain.org to forward to georgesmith9983203@gmail.com.\nThe last step is to set up your domain to enable the email forwarding and to specify the forwarding service as the mail handler for your domain. This involves adding entries to the zone file for you domain in your public DNS server.\nLet\u0026rsquo;s look at the how to direct email to your forwarding service\u0026hellip;\nThis is done via MX entries in your zone file. MX stands for \u0026ldquo;Mail Exchange\u0026rdquo; and each MX entry lists the URL of a mail server for your domain. You can also specify a priority for each mail server.\nSomewhere in the documentation or setup pages for the forwarding service, you\u0026rsquo;ll find information about how to set up your domain. This will include the URL\u0026rsquo;s of the mail servers that you should use. For each one, you create an MX entry in your zone file. The service I\u0026rsquo;m using specifies mx1.improvmx.com and mx2.improve.com with the first one having a priority of 10 and the second one with a priority of 20.\nHow you enter the addresses and priorities specified by the forwarding service you select will depend on the UI provided by your DNS service.\nFinally, you\u0026rsquo;ll need to set up something in your DNS zone that tells the forwarding service that they are authorized to perform the forwarding. At least, this is what ImprovMX.com wants, and I expect that others will too. For ImprovMX.com, they want a TXT entry with a specific string.\nA TXT entry in your zone file is just text string that anybody in the Internet can read. The important thing is that nobody else can write entries in your zone file except you. So if the TXT entry that they are looking for is in your zone file, then you had to have put it there. For my ImproveMX.com account, the string is \u0026ldquo;v=spf1 include:spf.improvmx.com ~all\u0026rdquo;. Presumably, this isn\u0026rsquo;t just authorization, but some sort of configuration as well.\nOutgoing Mail # Not all email service providers support alias accounts for outgoing emails. Gmail does, and I strongly suspect that all of the other big, big providers do as well.\nIn order to do this, however, you are going to need an SMTP relay service. I\u0026rsquo;ve been using Smtp2Go after having issues with Brevo, but there are quite a few free services available. Once again, do an Internet search for \u0026ldquo;smtp relay service\u0026rdquo; and you\u0026rsquo;ll quickly get lists of services that you can use. All of these free services have limits on how many emails you can send in a time period, typically daily or monthly. Smtp2Go, is pretty restrictive at 1,000/month, but even that is way more than I\u0026rsquo;ll ever need.\nWhatever service that you pick, it\u0026rsquo;s almost certainly going to require that you create an account. Then, of course, it will need you to prove that you own the domain from which you are going to be sending emails. That\u0026rsquo;s going to involve creating some specific entries in your DNS zone file. Smtp2Go uses 3 CNAME entries, and they give you the exact information with buttons to copy the data so that you can paste it into the GUI for your DNS server.\nThe last prepratory step is to create a user/password that can send emails through the SMTP service. These are the credentials that you\u0026rsquo;ll be putting into Gmail.\nNow you\u0026rsquo;re ready to set up the outgoing alias on Gmail. Go into the web interface for you Gmail account at https://mail.google.com and log in. Then click the gear icon, which should bring up a settings sidebar, which should have an link called \u0026ldquo;See all settings\u0026rdquo;. Click on that link, and you\u0026rsquo;ll get a settings screen that has about 10 tabs at the top. Click on the \u0026ldquo;Accounts and Import\u0026rdquo; tab, and you\u0026rsquo;ll then see a section that says \u0026ldquo;Send mail as:\u0026rdquo;. Then you can click on the \u0026ldquo;Add another email address\u0026rdquo; link, which should bring up a pop-up window that allows you to enter the details of the new address.\nGive this address an name and then enter the address that you\u0026rsquo;re using from your own domain, for instance somebody@mydomain.org. Make sure the \u0026ldquo;Treat as an alias\u0026rdquo; box is checked off.\nIn the next step you enter the URL and port of the SMTP server, the user id and the password. Then save it and you should be good to go. Use \u0026ldquo;Compose\u0026rdquo; to send a test email to yourself.\nMail Service that Uses Your Domain # My migration path plan is to move my email handling from Gmail and over to a Canadian company called NorthMail (northmail.ca). They are new, and I\u0026rsquo;ve contacted them a few times with some compatibility issues that I encountered, and they\u0026rsquo;ve always indicated that what I wanted was in their future plans.\nThe first thing I asked them about was compatibility with FairEmail, which is the email app I use on my phone. They now support it. However, they didn\u0026rsquo;t support the mail forwarding scheme described above for outgoing mail, although they indicated that they had requests for it.\nI checked back last month, and they now have a \u0026ldquo;For Business\u0026rdquo; product which allows you to set up accounts within your own domain. It will work just as well for families with their own public domains as for a business, and the price per mailbox is about the same as for an individual mailbox. This is the direction that I\u0026rsquo;m planning to go, although it won\u0026rsquo;t be transparent to my wife as she\u0026rsquo;s currently using the Gmail app for email on her phone. I\u0026rsquo;m going to flip her over to FairEmail first. This means it will have to be a process.\nSetting this up is going to be easy for me. Just purchase the account with NorthMail and tell them what my domain name and email addresses will be, and then configure my public DNS service such that my MX entries point to the NorthMail servers instead of the ImprovMX servers.\nI\u0026rsquo;m sure that you can find similar services from thousands of other providers around the world.\nSSL Certificates # Another story from the deep past\u0026hellip;\nBack around 1997 the company that I worked for needed to register a domain and get an SSL certificate for our web server. Back in those days you dealt directly with the registrar, and we had to prove that we did business across the country before we could register directly into the .ca domain. We also dealt directly with VeriSign to get the certificate.\nYou could not get a certicate from VeriSign unless you could prove that you owned the website and the business associated with it. I remember having to go get a copy of our articles of incorporation to provide this proof. In truth, I cannot remember 100% if we needed them for the domain or for the certificate, but I feel like it was more likely to have been the certificate. It was 30 years ago, though.\nI do remember that the whole process was an ordeal that literally took weeks to complete.\nNowadays, it\u0026rsquo;s not even remotely difficult. But you should remember that an SSL certificate does 2 things:\nProves that web server that you are connecting to is actually the one in your address bar. Provides the keys to allow your browser to use encrypted communication with the web server. Both of these are important, but it\u0026rsquo;s the first one which provides the technical hurdles to getting a certificate.\nFirst off, certificates are issued by only a few trusted certificate authorities. Your browser is going to have a list of certificate authorities that it trusts. If it encounters a website with a certificate that is not signed one of these trusted authorities, it will display ugly warning messages. This includes the \u0026ldquo;self-signed\u0026rdquo; certificates that some of your homelab servers may use by default.\nThis means that you need to get your certificates from one of these trusted authorities. Today, we have a system called \u0026ldquo;ACME\u0026rdquo;, which stands for \u0026ldquo;Automatic Certificate Management Environment\u0026rdquo;, that you can use to automate the process of getting a certificate from a trusted authority.\nJust like back in the 1990\u0026rsquo;s, the key element that you have to prove to the certificate authority is that you own and control the web server and the domain for which you are trying to acquire a certificate. There are two ways to do this:\nMake a specific customization to the web server that the certificate authority can connect to and detect. Make a specific customization to the zone file for the domain that the certificate authority can detect. Item (1) is problematic. Many of the servers for which you\u0026rsquo;ll want to issue certificates are not going to be publicly accessible, and therefore cannot be connected to by the certificate authority.\nItem (2) is trivial if you have a public domain.\nAnd remember, certificates expire. And proper certificates expire every few months. So this is something that you want to set up and then forget about. You\u0026rsquo;re not going to want to set up some hole in your firewall so that certificates can be re-issued at any random time.\nDynamic Domain Name System # You are probably aware that if you are using a normal residential internet connection, then your public IP address will change from time to time. Anything that you try to set up that relies on using the public IP address of your connection will need to make sure that it has the current IP address in order to work.\nYou can use a service like \u0026ldquo;Duck DNS\u0026rdquo; to do this. That\u0026rsquo;s going to require that you create an entry in their zone file, and you\u0026rsquo;ll end up with a hostname like \u0026lsquo;fred.duckdns.org\u0026rsquo;. Then you\u0026rsquo;ll need to set up a script that checks your connection\u0026rsquo;s IP address and updates the DuckDNS.org zone file with the new address.\nThis works, but you\u0026rsquo;re tied into yet another service that you\u0026rsquo;ve got to keep track of.\nYou can do exactly the same thing with a public domain. In fact, if you are using OPNSense, this is just another service that you can set up in your firewall and it will run on a scheduled basis and keep your zone file up to date with the current address of your IP connection.\nThis is called \u0026ldquo;Dynamic DNS\u0026rdquo; (or \u0026ldquo;DDNS\u0026rdquo;), because your DNS zone is dynamically adjusted to reflect changes in the IP addresses of the hostnames.\nThis is really useful if you are setting up an incoming VPN, because you\u0026rsquo;ll need to let the roaming clients know where to connect to\u0026hellip;\nIntegration with VPNs # This refers to VPN\u0026rsquo;s that you set up to allow access into your network from outside. You might be familiar with using a VPN as a client, where you connect into a commercial VPN in order to anonymize your traffic, enter the Internet from another country, or to prevent your ISP from snooping on your connections. Generally speaking, at some point the clients for these services are going to need to connect to a specific gateway or group of gateways in order to join the VPN.\nThe same situation exists when you create a VPN for an outside client (or peer) to access your network. You\u0026rsquo;ll need a publicly accessible gateway that the clients can connect to. But you cannot give them an IP address, because your ISP could change it at any given time. This is where DDNS comes in to play.\nTailscale # The other implementation of VPN\u0026rsquo;s that you are likely to use is \u0026ldquo;TailScale\u0026rdquo;. The TailScale service actually maintains its own DNS service for your TailScale network that only your TailScale clients can access. This is great, and allows you to use URL\u0026rsquo;s instead of IP addresses when you\u0026rsquo;re setting up clients.\nHowever, if you are like me and you don\u0026rsquo;t want to go through TailScale when you\u0026rsquo;re on your own WiFi this isn\u0026rsquo;t going to work all by itself. You\u0026rsquo;ll want to connect directly to the server from your WiFi, and when you leave your house you\u0026rsquo;ll want to connect through TailScale.\nIf you have a public DNS server and a private internal DNS server (which you probably should have), then you can set up a situation where your internal DNS server has entries that point directly to the internal addresses of your servers, and the public DNS server contains the TailScale addresses.\nWhen you are on your WiFi, you\u0026rsquo;ll be using your internal DNS server and therefore connect directly to your services without TailScale. When you are outside your house, you\u0026rsquo;ll be using your cell provider\u0026rsquo;s DNS server, which will get your addresses from your public DNS server and route you through TailScale to your severs. This is called \u0026ldquo;Split Horizen DNS\u0026rdquo;.\nAPI Tokens # Some of the things I described require that you are able to update your DNS zone file in an automated fashion. This is usually accomplished through API tokens, which provide a way for applications to connect to your DNS service and make changes to your zone file. Essentially, they are fairly long strings of gibberish which are unique to your account with the DNS service.\nGenerally, you can create multiple API tokens. This allows you to have different tokens for different purposes, and you can usually disable them, or have them expire, or delete them whenever you want.\nNot all DNS services support API tokens, so you\u0026rsquo;ll have to be on the lookout for this when you pick a DNS service. GoDaddy, for instance, only supports API tokens for what they call \u0026ldquo;premium\u0026rdquo; clients - those clients with 50 or more domains registered through them. I think this is a change in the past few years.\nYou don\u0026rsquo;t have to pick your registrar agent with this in mind, though. It\u0026rsquo;s relatively trivial to pass the handling of your DNS services off to any other service provider that you want. I chose to use deSEC.io, which is in Germany and is also free. I also see it popping up as an option in places where you need to pick your DNS service for automated processes. So it\u0026rsquo;s a good choice for me.\nIn my GoDaddy account, I just go to the area for my domain, pick the \u0026ldquo;DNS\u0026rdquo; section and plug in the servers for deSEC, which are ns1.desec.io, and ns2.desec.org. That\u0026rsquo;s it. From then on I just use my account at deSec to manage my DNS.\nConclusion # Having your own public domain with a public DNS server is a great idea because it removes a lot of friction from a few key infrastructure tasks that you\u0026rsquo;re going to need to do to run a homelab.\n","date":"24 June 2026","externalUrl":null,"permalink":"/homelab/public-domain/","section":"Homelabs","summary":"If you are self-hosting, then you should probably have a public domain registered.  In this article, we’ll look at why you should do this, and the things that it helps.","title":"You Need a Public Domain","type":"homelab"},{"content":" Introduction # Domain Name Servers (DNS Servers) are one of the cornerstones of both the Internet, and your own self-hosted infrastructure. In this article we are going to look at what DNS servers do, how they work and integrate with each other, and how to host your own private DNS server for your homelab.\nThe Absolute Basics - Addresses and Names # When you strip away everything else, every device connected to a TCP/IP network - which is just about everything we deal with here - is uniquely identified with an IP address. There are two versions of IP addresses in use now: IPv4 and IPv6. We aren\u0026rsquo;t going to talk about IPv6 in this article because it\u0026rsquo;s a subject all by itself. So, from now on when I refer to IP address, I\u0026rsquo;m talking about IPv4 addresses.\nIPv4 addresses are 32 bits long and divided into 4 octets of 8 bits. So, 4 bytes. They look like 192.168.1.1. Each byte, obviously, can have a value between 0 and 255.\nThe problem with IP addresses is that are clumsy and hard to remember. They are also absolutely attached to a particular machine - this becomes an important limitation later on.\nFinally they don\u0026rsquo;t have a hierachical structure that maps nicely to the real world relationships behind how the Internet is organized.\nTo overcome these issues, we use names to refer to devices on the Internet instead, and everybody is walking around with a whole bunch of these device names in their heads. Furthermore, the names are structured in a hierachical manner that lines up nicely with the way that the Interet is actually organized. This structure also makes them easier to remember.\nInternet names are divided up into sections separated by . characters. They go from most specific on the left, to least specific on the right. Let\u0026rsquo;s look at a typical sort of name: server.dmz.organization.com, and see how it is organized.\nAt the far left is the specific machine, server. It is located inside the domain .dmz.organization.com. Next along is .dmz which is a domain, but it is a subdomain of .organization.com.\nIf we keep going to the right, we get to .organization, which is a subdomain of .com. And, of course, we have .com which is what is referred to as a \u0026ldquo;top level domain\u0026rdquo;. You can think of this as three domains: .dmz.organization.com, .organization.com and .com. Of course, both .dmz.organization.com, and .organization.com are both subdomains of the domains to the right.\nEach domain has absolute control over its own contents. This means that whatever body owns/controls the .com domain is the only body allowed to add or remove anything to/from it. In order for .organization.com to exist, the owner of .com had to add it.\nIn this example, the subdomain .dmz.organization.com looks like it\u0026rsquo;s simply an administrative division inside of the .organization.com domain. It\u0026rsquo;s highly likely that the owner of .organization.com created it for the use of .organization.com, and not as a separately administered subdomain. This is quite normal, and any owner of a domain can freely add whatever subdomains to their domain that they want, for whatever purposes.\nDomain Name Servers # What we need in order to route traffic to server.dmz.organization.com is a way to turn that name into an IP address. This is where name servers, or DNS Servers (the \u0026ldquo;DNS\u0026rdquo; refers to \u0026ldquo;Domain Name System\u0026rdquo;), come into play.\nIn a nutshell, the domain that \u0026ldquo;owns\u0026rdquo; a name is responsible for providing the translation for that name into an IP address. In fact, a name on the Internet (or any IP network for that matter) simply doesn\u0026rsquo;t exist unless it\u0026rsquo;s discoverable in its domain\u0026rsquo;s DNS server.\nThis means that for every domain on any IP network, there has to be a DNS server that can give the IP address for every name in that domain.\nNow, if you type http://server.dmz.organization.com into your browser then how does your browser know where the DNS server for .dmz.organization.com is? How does it query it?\nThe answer is very simple. Each parent domain has to have a an entry in its DNS server that tells everyone where the DNS servers for every single subdomain it holds is.\nThis means that your browser has to go and query the .com domain to find out the location of the DNS server for .organization.com. Then it has to query that DNS server for the address for server.dmz.organization.com. It might be that .dmz.organization.com has its own DNS server, in which case the query have to continue at that server.\nAt least that\u0026rsquo;s the naive view of how it works. In reality, your browser makes a single request to its own local name server and the rest of the work is done by that. Your browser isn\u0026rsquo;t aware of how the name is resolved, just that it happens.\nWhen the DNS servers talk to each other, the process iterates over and over. Your local DNS server will answer the request if it has the information, otherwise it passes the request on to its upstream DNS server, which then goes through the same process. Eventually, some name server will query .com for the DNS server of organization.com and it will give the answer. At that point the process rolls itself back up, each name server passing back the answer until it eventually gets back to your browser.\nNote This is a good way to conceptialize how DNS servers communicate, meaning that it provides a working model that explains the behaviours that you see and the things that you need to do in order to properly configure a DNS server. Under the hood, this may not match up with how modern DNS servers actually go about doing what they do.\nThere was a critical component in this description: \u0026ldquo;DNS server will answer the request if it has the information\u0026rdquo;. How can it have the answer if it isn\u0026rsquo;t the DNS server for the domain specified?\nThe truth is that going through this entire process for every single request would be an incredibly wasteful approach. In reality, DNS servers cache the results that they\u0026rsquo;ve looked up for a period of time. DNS entries, for the most part, don\u0026rsquo;t change very often so it\u0026rsquo;s safe to store them for a while. In fact, every DNS entry includes a \u0026ldquo;Time to Live\u0026rdquo; (TTL) value. This is the number of seconds for which the answer can be assumed to be valid. DNS caches will respond immediately with values that haven\u0026rsquo;t yet reached their TTL.\nThis means that a DNS server can return two types of values:\nAuthoritative This is when the DNS server that responded to the request is the designated DNS server for the domain that was queried.\nNon-Authoritiative This is when the DNS server that responded to the request is NOT the desginated DNS server for the domain that was queried.\nI\u0026rsquo;m only defining this because we\u0026rsquo;ll need to understand this a little when we talk about setting up a DNS server. In real life, you rarely care whether or not an answer was authoritative. In those cases that you do, it\u0026rsquo;s because something went wrong.\nTwo Uses for DNS Servers # From the section above you can see that DNS servers have two direct uses.\nTo act as an agent to look up names from around the Internet. To act as the authoritative definition of the contents of a domain. When you are setting up a homelab, you\u0026rsquo;re going to want your DNS server to act as both.\nWhat Information is in a DNS Server? # The contents of a DNS server are organized into \u0026ldquo;zones\u0026rdquo;. Just about every DNS server you\u0026rsquo;ll encounter will contain multiple zones. Back in the old days, before we had GUI\u0026rsquo;s to manage the DNS servers, we used to configure them by creating \u0026ldquo;Zone Files\u0026rdquo; which were just text files in a particular format that the DNS server could parse. Dinosaurs like me still refer to the contents of a zone as a \u0026ldquo;Zone File\u0026rdquo; even though they aren\u0026rsquo;t really used any more. Saying, \u0026ldquo;Put it in the zone file\u0026rdquo;, is just a susinct way of refering to the process of configuring a zone in a DNS server.\nAll records in a zone file have at least these three elements:\nType Name Value Generally, DNS lookups are done by specifying the type and and the name. It is possible, even common, to do searches for just a name, and accept any type of records that are returned. It\u0026rsquo;s also possible to do searches for just type, for instance if you are looking for MX records to find the email servers for a domain.\nWhat kind of entries might you find in a zone file?\nSOA - Start of Authority This entry specifies the authoritive information about the zone. The name of the record is the name of the zone, but you\u0026rsquo;ll usually see it listed as @. The value is structured, and amongst other things will tell you the name of the primary nameserver for the domain.\nA - Address This is your bread and butter entry. The entry has a name, and the value is an IP address.\nCNAME - Redirection The most common use you\u0026rsquo;ll find for these is when you want to use multiple names for the same address, but you don\u0026rsquo;t want to have to update multiple entries if that address changes. A CNAME entry has a name of its own, and then returns the name of another entry that it shares an address with. An important point here is that if a DNS lookup encounters a CNAME, then it will continue on but now doing a lookup for the value in the CNAME record.\nTXT - Text TXT entries are generic informational entries that don\u0026rsquo;t have any defined purpose in the DNS system. Lot\u0026rsquo;s of proprietary systems use them to store publicly visible information that they need for their processes.\nMX - Mail Exchange These entries tell the internet where the email servers for your domain are located. These can be either IP addresses or names that can be looked up via DNS searches.\nNS - Name Server Like SOA these have a name of @, and they contain the names of authoritative DNS servers for the domain. There can (should) be more than one for each domain.\nPTR - Pointer PTR records are a bit like CNAME records, but they won\u0026rsquo;t cause DNS lookups to carry on looking up the new value. You are going to see these used in \u0026ldquo;reverse lookups\u0026rdquo; more often than not. A reverse lookup uses DNS to locate the name of an entry from its IP address. Most DNS server software will facilitate the creation of reverse lookup zones for this purpose.\nYou may encounter other types (there are lots more), but for a homelab these are the ones you\u0026rsquo;re most likely to care about.\nManaging Your Own DNS Server # Unless you want to remember a bunch of IP addresses - which means you\u0026rsquo;re never going to be able to expose your services to the non-technical people in your family - you\u0026rsquo;re going to need your own name server. At a minimum, you need to be able to assign names to the servers in your network.\nYou have choices. If you are running on OPNSense firewall, you can use one of the services it hosts to run your DNS. Or you can spin up a container in Proxmox and run one of the popular DNS servers.\nLots of people seem to come into DNS through PiHole or AdGuard without even realizing that are running a DNS server.\nPersonally, I prefer Technitium\u0026rsquo;s DNS server. It\u0026rsquo;s easy to install and configure, supports all the ad blocking you could ever want and has a bunch of advanced features that you\u0026rsquo;ll probably want to use sooner or later. The GUI interface is awesome and intuitive and the whole thing just works without any hitches.\nThat\u0026rsquo;s what I\u0026rsquo;m going to talk about here.\nInstalling Technitium # I installed Technitium from the Promox Community Scripts, and I think that\u0026rsquo;s the probably the easiest way to get it up and running.\nThe Script page is here. If you go there, you\u0026rsquo;ll see that the script creates an LXC guest, and defaults to using a single CPU core with 512MB of RAM. It\u0026rsquo;s fairly light-weight, as you would expect.\nIn the \u0026ldquo;INSTALL\u0026rdquo; section, there\u0026rsquo;s a bash command that will download the script and run it for you. You copy that command (there\u0026rsquo;s a little copy icon right at the end of the command), and then paste it into the shell of your Promox node. It will then walk you through a series of questions that will determine how the LXC is configured.\nThe first question asked by the scripts is whether you want to just use the \u0026ldquo;Default\u0026rdquo; or \u0026ldquo;Advanced\u0026rdquo; installation (at least those are the first two options). I always pick the \u0026ldquo;Advanced\u0026rdquo; because there are a couple of networking options that I want to customize, and I don\u0026rsquo;t remember seeing them the very first time I ever installed anything using \u0026ldquo;Default\u0026rdquo;. I also pick the \u0026ldquo;Verbose\u0026rdquo; output so that I can at least see that progress is still happening as the install runs, and I\u0026rsquo;m not sitting there staring at an unchanging screen wondering if it has got stuck or not. Other than that, I usually just accept all of the default values as they come up.\nWhen it\u0026rsquo;s done, it will tell you the address and port to log into the DNS Server\u0026rsquo;s administrative GUI. Plug that into your browser and, after you\u0026rsquo;ve logged in, you\u0026rsquo;ll see a screen that looks like this:\nThe \u0026ldquo;Menu\u0026rdquo; is actually just the tabs across the top of the page.\nUpstream Lookups # In order to use your DNS server, you\u0026rsquo;ll need to make it the primary DNS server for your network. Obviously, systems on your network will need to query this DNS server if they are going to be able to lookup names on your network.\nBut once you do this, then all of the searches from your network are going to go through this DNS server. Even the ones for names in other domains out there in the Internet. So before you can do this, you\u0026rsquo;ll need to tell Technitium how you want it forward your requests for lookups outside your domain.\nTo set this up, go to Settings \u0026ndash;\u0026gt; Proxy \u0026amp; Forwarders and scroll down to the second section. You\u0026rsquo;ll see something like this:\nTechnitium has a reasonable selection of commonly used public DNS servers that you can quickly select. You can, of course, put in the addresses or names of any other DNS servers that you would like to use, including the ones from your ISP - if you really want to do that. You should pick at least 2 from at least two different providers.\nThe \u0026ldquo;Save\u0026rdquo; button is at the bottom of the page. Now you can move on to creating a zone.\nCreating a Zone # Before we get into this, I strongly recommend that you keep your domain flat with a single zone. Unless you\u0026rsquo;re running some kind of complex organization with mulitple sub-units that need to independently assign names without worrying about collisions with names in other sub-units, you\u0026rsquo;ll just be making your life more complicated than you need to if you start adding subdomains and zones.\nSo stick to one zone, and come up with a naming scheme that avoids collisions in that one zone.\nFrom the main Dashboard screen pick the \u0026ldquo;Zones\u0026rdquo; tab, and then at the top left you\u0026rsquo;ll see a button called \u0026ldquo;Add Zone\u0026rdquo;. Click it, and you\u0026rsquo;ll get a screen that looks like this:\nFor internal purposes, you don\u0026rsquo;t have to have a properly registered domain to set up a DNS server. You can call this zone anything you want, but be aware that if you replicate some properly registered domain name then you won\u0026rsquo;t be able to access that external domain from inside your network. That being said, having an actual registered domain with an public, external DNS server is a good idea.\nFor now, just pick \u0026ldquo;Primary Zone\u0026rdquo;. If you do have a registered domain that you also want to use for internal purposes (I would recommend this), then you\u0026rsquo;ll eventually want to change it to \u0026ldquo;Conditional Forwarder Zone\u0026rdquo; (see the section below).\nThat\u0026rsquo;s all you need to do. The \u0026ldquo;Catalog Zone\u0026rdquo; area is for configuring a failover if you have more than 1 Technitium DNS server in your network. It\u0026rsquo;s a good idea to do this, but beyond the scope of this discussion.\nOne last point. If you click on the link: \u0026ldquo;Help: How To Self Host Your Own Domain Name\u0026rdquo; it will take you to an article about how to host a public DNS server for your registered domain. That is not what we are doing here, so that article is not relevant.\nAdding Entries # Once you\u0026rsquo;ve clicked \u0026ldquo;Add\u0026rdquo; on the previous screen, you\u0026rsquo;ll end up with a page that looks like this:\nFor free, you get an SOA record and an NS record that points to your Technitium server. For internal purposes, neither of these two records is particularly important. The default values in the SOA will work just fine, and you\u0026rsquo;ll probably be publishing this server via its IP address with either DHCP, or the static info you set up in all of your devices. So the NS record will never be used in real life.\nNow you can add your first address. Click on \u0026ldquo;Add Record\u0026rdquo;:\nMake sure that \u0026ldquo;Type\u0026rdquo; says \u0026ldquo;A\u0026rdquo;. Fill in the name that you want to add\u0026hellip;\nOk, naming stuff is hard. It\u0026rsquo;s probably the single hardest aspect of programming, and it\u0026rsquo;s only slightly less hard with network administration. Things to keep in mind:\nNames need to be memorable This is the point of using names. Trust me, you don\u0026rsquo;t want to be surfing over to your DNS server to see what you called something every time you want to open it in a browser.\nNames need to be specific It\u0026rsquo;s probably not obvious now, but you are going to find that you have servers and you have services. While each service is hosted by a server, they are not the same thing. And sometimes you\u0026rsquo;ll have several services hosted by the same server. When you start out naming things in your network, you are probably going to be defining servers, not services. So name your servers as servers.\nNames need to follow a scheme This because you will forget the names. But, if you follow a scheme, then you can probably figure out the name you want by re-applying the scheme. A bit of a pain, but still easier than looking it up in the Technitium GUI.\nNames need to be a short as possible Address bars are small, and typing stuff into a phone browser is always painful.\nNames need to be consistent Compose your names the same way each time. If you have 3 Proxmox backup servers then name them pb1, pb2 and pb3, or pb0, pb1 and pb2 (if you are a programmer). Or name them firstpb, secondpb and thirdpb if you hate yourself. Then if you decide to add more name servers, use the exact same convention. Don\u0026rsquo;t call them ns_1, ns_2 and ns_3 if you used pb1, pb2 and pb3. And don\u0026rsquo;t call them ns1, ns2 and ns3 if you used pb0, pb1 and pb2. You get the idea.\nNames can be hard to change Just try and change a name after you\u0026rsquo;ve used it on 4 people\u0026rsquo;s phone apps, referenced it for an API call on 3 different servers, and imbedded it into your smart TV. Eventually you\u0026rsquo;ll have to do this, and then you\u0026rsquo;ll wish you listened to me. Try to get your naming scheme figured out at the beginning, so you\u0026rsquo;re not changing names over and over.\nYou\u0026rsquo;ll have to trust me. These things are important. Also, remember that you can use CNAME entries if you want shortcut names for particular servers or services. You can also use them to help transition through changes to your naming scheme.\nMoving on with this screen: I wouldn\u0026rsquo;t mess with the TTL unless you are planning to change the address as part of a setup process or something. In that case, set it really low, like 30, so that your workstation won\u0026rsquo;t cache the lookups and cause grief. Once you do settle in on a final address, change the TTL back to 3600.\nThe \u0026ldquo;Add reverse (PTR) record\u0026rdquo;, and the \u0026ldquo;Create reverse zone for PTR record\u0026rdquo;, are potentially useful. IFF the address is unique to the name, then just click these two boxes on. This will cause Technitium to create a reverse lookup entry (and potentially the zone to put it in) which will let you find the name from the IP address.\nWhen you need this, you need this. And it costs almost nothing.\nIf you\u0026rsquo;ve got 5 names that all point to the same address, then you\u0026rsquo;ll only want to check off these two boxes when you create what you would consider to be the \u0026ldquo;primary\u0026rdquo; name for that address.\nI find it useful to put something in comments. At least explain what the name is in plain English. Is it a server or a service? Also, if it\u0026rsquo;s a service and it has a particular port that you need to use to access it, I\u0026rsquo;d put it in the comments. It saves having to look through you browser history, or checking the GitHub pages for the project to find how to access the service.\nThat\u0026rsquo;s click \u0026ldquo;Save\u0026rdquo;. Rinse and repeat for all of the other names you want to create in your network.\nConditional Forwarding # This is a bit advanced in concept, but easy in practice.\nThere are lots of good reasons for registering your domain name, and using a publicly accessible DNS server for that domain. For instance, you\u0026rsquo;ve set up your email so that you can have your own email addresses in that domain. Something like fred@myfamily.org. In order to be able to receive email for that domain, other mail servers need to be able to find the MX records for myfamily.org. And that means you\u0026rsquo;ll be using some DNS service for you domain out there in the Internet somewhere, something like CloudFlare.\nBut now you\u0026rsquo;ve just set up an authoritative DNS server for myfamily.org inside your home network. Nobody outside your network can see it or use it, which is good. But anybody inside your network will never see entries that are in that CloudFlare DNS server. And that can be a problem.\nWhat you want is for your local, private DNS server to answer any lookup for myfamily.org from its own records if it can, and if that lookup fails, then to forward the request out to the Internet and let the CloudFlare DNS server answer it.\nThis is called \u0026ldquo;Conditional Forwarding\u0026rdquo;, and it\u0026rsquo;s pretty powerful and easy to set up. All you have to do is to change the type of your zone from \u0026ldquo;Primary\u0026rdquo; to \u0026ldquo;Conditional Forwarding\u0026rdquo;.\nGo into your zone, and at the top of the screen will be a button called \u0026ldquo;Options\u0026rdquo; with triangle, click it and you\u0026rsquo;ll get a drop-down meun. Select \u0026ldquo;Convert Zone\u0026rdquo; and you\u0026rsquo;ll get a screen like this:\n]\nMake sure that \u0026ldquo;Conditional Forwarder Zone\u0026rdquo; is selected and click on \u0026ldquo;Convert Zone\u0026rdquo;. All done.\nAd Blocking # If you\u0026rsquo;re not coming from the PiHole/Adguard world, you may wonder how a DNS server can block ads.\nNobody embeds actual IP addresses in anything, not even for ads. If you look at the source for any web page that has ads, you\u0026rsquo;ll see URL\u0026rsquo;s for the ad content buried in the source. Things like https:\\\\clickme.spammersrus.org\\adcontent8873923. You browser has to perform a lookup to find the address for clickme.spammersrus.org, in order to then connect to that server and download the ad content.\nBut what if your DNS server refused to return a result for clickme.spammersrus.org?\nThat\u0026rsquo;s exactly how DNS ad blocking works. The DNS server has a list of servers or domains that it knows are used for ad content, and it simply won\u0026rsquo;t resolve them. For anyone. This means that the ad content is completely blocked from you network. Not just browsers, but apps on any phone on your WiFi. No more ads at the bottom of your Sudoku app. No more ads baked into any servers that you host. None.\nAnd since everybody hates ads, there are lots of people and organizations out there who compile and update lists of all of the sites known to serve up ad content. And you can tell Technitium which lists you want to use.\nSetting up ad blocking in Technitium is really easy. In the \u0026ldquo;Settings\u0026rdquo; tab there is a sub-tab called \u0026ldquo;Blocking\u0026rdquo;. It\u0026rsquo;s all there.\nThe first check boxes turn it on.\nThen next allows you to disable it temporarily, and allow you to specify some address that you\u0026rsquo;ll exempt from the blocking. Maybe you\u0026rsquo;ve gotten one of those \u0026ldquo;Please disable your ad blocker\u0026rdquo; pop-ups that blocks the content and you really want to see that content? Not for me, but this is how you would temporarily turn off the ad blocking.\n\u0026ldquo;Blocking Type\u0026rdquo;, tells Technitium how to respond when it blocks an address. I just stick with the NXDOMAIN response, which seems to work well.\nThen we get to the the lists and list management. There are quite a few lists pre-loaded into Technitium, but if you want to you can links to whatever lists you can find. You\u0026rsquo;ll also notice that these won\u0026rsquo;t just block ads, but will also block content of various types. For instance, you can get lists with gambling sites, adult content, or malicious content.\nThe best recommendations are to start off with a limited amount of blocking and see how it works. Some lists are much more aggressive than others, and will include sites that might block content that you actually want to let through. If you are finding that this is happening, then shift to a less aggressive list. If too much is still getting through, then add more lists or switch to more aggressive lists.\nOne other thing: You can also use an upstream public DNS that also does some of this filtering and blocking as well. I\u0026rsquo;ve used some of these with my Android phone, and they do work quite well. But Technitium does a good job in this regard, so I don\u0026rsquo;t use them for my network.\nEncrypted Lookups # Obviously, all of your traffic goes out through your ISP to the Internet, and this includes your DNS lookups. It\u0026rsquo;s highly likely that your ISP is analyzing your DNS lookups in order to monetize them through ad revenue. At least this is the accepted belief.\nTechnitium makes it trivial to encrypt your DNS queries via DNS-over-TLS, so just do it. The one caveat is that not all upstream DNS servers support DNS-over-TLS. However, CloudFlare, Google and Quad9 all support it, so this shouldn\u0026rsquo;t be an issue.\nAlso, remember that there\u0026rsquo;s no point encrypting DNS queries to your ISP\u0026rsquo;s DNS servers since they can always see what\u0026rsquo;s going on at the DNS server end.\nTo turn it on, go back to this screen:\nAnd click on \u0026ldquo;DNS-over-TLS\u0026rdquo;. Make sure that the forwarders that you have selected support this protocol.\nConclusion # Although DNS servers are the backbone of so much of the Internet, and they can do some really, really complicated stuff, setting up your own DNS server for you homelab is fairly easy.\nRealistically speaking, this should probably be the first server that you set up on your homelab. For one, it\u0026rsquo;s very straight-forward and just works. This makes it a good learning experience and will introduce you to a lot of homelab concepts. On top of that, you\u0026rsquo;re going to want to start using names right away, so you\u0026rsquo;ll need a DNS server to do that.\n","date":"23 June 2026","externalUrl":null,"permalink":"/homelab/dns-concepts/","section":"Homelabs","summary":"Everything you need to know about DNS if you are self-hosting.  How to use Technitium to host a private DNS server in your homelab.","title":"Managing DNS Services","type":"homelab"},{"content":"","date":"22 June 2026","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"22 June 2026","externalUrl":null,"permalink":"/series/snapcast/","section":"Series","summary":"","title":"SnapCast","type":"series"},{"content":" Introduction # For a number of years now, we\u0026rsquo;ve been using our Google Home/Nest devices to stream music around the entire house. It\u0026rsquo;s good when it works, but we\u0026rsquo;ve found that the music will intermittently just stop playing for no apparent reason. This is guaranteed to happen several times a day. I\u0026rsquo;ve seen that others have reported the same problem.\nWe play a station from the SomaFM streaming service called \u0026ldquo;Groove Salad\u0026rdquo; pretty much all day long. It\u0026rsquo;s ambient electronic music which is a good background to day-to-day living for us. Just as you would expect, we don\u0026rsquo;t like every single song that they play, but some we like a lot, and most just fade into the background.\nGoogle does a pretty good job of synchronizing the various devices, so if you do find yourself in a spot where you can hear two of them, it doesn\u0026rsquo;t sound like a weird echo. I\u0026rsquo;ve even created a custom command to start the music in the group that we\u0026rsquo;ved defined for the whole-house music.\nThat turns out to be worthwhile, because it stops so often.\nAdditionally, there was a time in early 2024 when our audio chromecast device stopped working for a while. It turns out that the security certificate that they set up for in on Google\u0026rsquo;s servers expired after 10 years. It took a few days for them to sort that out, but the incident showed how dependent we were on Google\u0026rsquo;s infrastructure and ongoing support for the devices we were using. It\u0026rsquo;s not like Google has never suddenly cut support for a product before, either.\nI wanted some way to stream the music with the same high quality synchronization without using Google. I also didn\u0026rsquo;t want to trade Google for a lock-in on some other proprietary solution like Sonos. I was hoping to find something that was open source, and would work with a variety of hardware.\nIt turns out that there is a software option out there called \u0026ldquo;SnapCast\u0026rdquo; that will do exactly this.\nThis is a 3 Part Series # I started out writing this as a single article, but it quickly became a monster that Jekyll said would take over half an hour to read. I found myself reluctant to break things down into more sections or to add more information because it was so long already.\nI realize that breaking it up into parts doesn\u0026rsquo;t make it any shorter, but it does make it a little less daunting to read.\nLet\u0026rsquo;s get started\u0026hellip;\n","date":"22 June 2026","externalUrl":null,"permalink":"/homelab/snapcast/","section":"Homelabs","summary":"Introduction # For a number of years now, we’ve been using our Google Home/Nest devices to stream music around the entire house. It’s good when it works, but we’ve found that the music will intermittently just stop playing for no apparent reason. This is guaranteed to happen several times a day. I’ve seen that others have reported the same problem.\n","title":"Whole Home Audio With SnapCast","type":"homelab"},{"content":" Introduction # When I initiated my project to build a homelab, I didn\u0026rsquo;t want a huge up-front expense. But I did have a need for whatever I built to be as reliable as possible. After all, I\u0026rsquo;m planning on replacing a fair number of cloud services - services that are generally pretty reliable - with home-hosted alternatives.\nTo me, this means redundancy.\nI\u0026rsquo;ve looked at lots of photos of people\u0026rsquo;s homelab setups and good number of them, probably even the majority of them, seem to have one server doing most of the work. This is, of course, a single point of failure. In my book, that\u0026rsquo;s just not something that you do.\nProxmox is a great help here. While setting up high availability - where you set up a cluster of hosts, designate vital containers and VM\u0026rsquo;s, and then have them fail over to different hosts when their host fails - is difficult without having lots of fast disk space and multi-gigabit networking, you can still use backups to restore vital servers when a host fails.\nBut, how do you create an effective cluster of servers on a shoestring budget without turning your paycheck over to the electic company?\n1 Litre PC\u0026rsquo;s # It wasn\u0026rsquo;t long after I started thinking about building a homelab that I started seeing videos, posts, and articles about \u0026ldquo;mini\u0026rdquo; PC\u0026rsquo;s that people were using. These were tiny little desktop computers that idled at about 7W and could handle a surprising amount of load.\nIt turns out that there are three basic product lines. HP has an EliteDesk series that they call \u0026ldquo;Mini\u0026rdquo;, Dell has an Optiplex line called \u0026ldquo;Micro\u0026rdquo; and Lenovo has a ThinkCentre line called \u0026ldquo;Tiny\u0026rdquo;. I\u0026rsquo;m not going to say that they are the same, but, they are all pretty close. These are all what are called \u0026ldquo;1 Litre\u0026rdquo; form factor PC\u0026rsquo;s, and they all use external power bricks instead of internal power supplies. A common term referring to these computers collectively is \u0026ldquo;Tiny Mini Micro\u0026rdquo;.\nHere\u0026rsquo;s what the Lenovo version looks like, with a pen so that you can see the scale:\nYou can see that it\u0026rsquo;s pretty darned small.\nThese units were incredibly popular with corporate clients. They bought gazillions of these things because they just worked well for office applications, they were sturdy, and they were cheap. A lot of them were leased.\nAnd when those leases ended\u0026hellip;\nThe leasing companies sold them off in bulk at wholesale prices to recover some of the value, and the companies that bought them have refurbished them and are reselling them. The result is that they are readily available at reasonable prices.\nI was told that the advent of Windows 11 has caused a shortage of these units, and the prices have gone up a fair bit. Even so, they are still a great value.\nWhy Lenovo # In truth, you probably cannot go wrong with either of the three brands. These computers were all designed for the same, commercial, market and they are all executed well. In my area, I was seeing a lot of Dells and Lenovos available, but not so many HPs. I went with Lenovo because they seemed just a little bit more solid, and there was a bit more variety in the models available.\nOne thing I really wanted was to have all of the units in the cluster as similar as possible. I wanted them to be fungible. As I was planning to purchase them over time, I needed the supply to be consistent for at least a while.\nYou should also know that these computers, are still manufactured today and available to purchase new, although these are newer versions with more power and a higher cost..\nThe Hardware # Let\u0026rsquo;s take a look at these boxes and see what you get.\nThese have been in production for quite some time. You\u0026rsquo;ll be able to find models like the M73Q which have 4th gen Intel Core processors for peanuts, but those are probably not going to have enough computing power to be useful. I decided on the Lenove M910Q which has 6th or 7th generation Core processors.\nYou do need to be a little careful. All of these computers look identical, so you have to make sure that you are getting what you think you are getting and that you know what kind of processors go in which models. This product line is also still in production, so you can buy more recent versions with 10-12th generation Intel processors - although at a higher price.\nBeyond that, they all come with a variety of different available processors. So, once again, be careful to make sure that you are purchasing what you think you are. Don\u0026rsquo;t pay an i7 price for an i3 equipped unit.\nM910Q vs M710Q # If you look into these, you\u0026rsquo;ll see that there are two models which look and seem to be very, very similiar; the M910Q and the M710. What\u0026rsquo;s the difference?\nYou can see for yourself the spec-sheets:\nM910 Specs\nM710 Specs\nAs far as I can tell, these two units are nearly the same, except that the M910Q uses the Intel Q270 chipset instead of the B250 chipset in the M710Q. This means that the M910Q supports VPRO, while the M710Q does not.\nBoth models support the same set of processors and memory.\nThe place where I bought mine made no price distinction between the M710Q and the M910Q because the average customer wasn\u0026rsquo;t building homelabs. What they cared about was the CPU. The 7th gen Core i7 cost about double the 6th gen Core i5 models. The price of a system with the same CPU was the identical between the M710Q and the M910Q.\nThe Processors # The M910Q will take a variety of Intel laptop CPU\u0026rsquo;s from the 6th and 7th generations. It will support Core i3, i5 and i7 processors from both generations. Let\u0026rsquo;s take a look at the differences, while ignoring the i3.\nThe i5-7500T is about 10% faster than the the i5-6500T for multithreaded applications, and 8% faster for single-core applications. Both support 4 cores and 4 threads. You see the slightly smaller differences between the i7-6700T and the i7-7700T.\nOf course the i7-6700T supports 8 threads, but the i7-6700T is just 16% faster than the i5-6500T for single-core performance. Multi-threaded performance is significantly higher.\nThe biggest difference is between the i5-6500T and the i7-7700T. Even here, the scores for Cinebench Single-Core come in at 132cb vs 168cb, while Cinebench Mulit-core is 464cb v 805cb. This last comparison is the biggest of all and probably significant for Proxmox host systems.\nI\u0026rsquo;m not saying these performance differences are insignificant, but a Core i5-6500T went for about $50 CAD on eBay, while a Core i7-7700T from the same seller went for $120 CAD at the time that I bought my systems. I was seeing this difference in the prices of the M910Q\u0026rsquo;s in the store as well.\nAbout 9 months later, you can buy i7-7700T CPU\u0026rsquo;s, the most powerful CPU\u0026rsquo;s compatible with these systems, for between $50-$70 (CAD) on eBay. That\u0026rsquo;s less than a stick of 16GB RAM for one of these. This means that upgrading is a very reasonable possibility.\nMemory # The M910Q takes DDR4 laptop style, non-ECC SODIMM\u0026rsquo;s. There are two slots, and each can take up to 16GB of RAM, for a total of 32GB.\nMost of the systems that I have seen for sale, on line or in person, are loaded with a single 8GB stick of RAM. I upgraded all of mine to 16GB when I bought them.\nI\u0026rsquo;m kicking myself now, because that extra 8GB cost me about $25 and I could have taken them up to 32GB for about $50. As I write this, each additional stick of 16GB DDR4 is going to cost me about $75.\nOf all of the resources that I worry about being a constraint after implementing quite a few services, it\u0026rsquo;s RAM. I\u0026rsquo;m nowhere close to getting critical, but I expect it will be the first thing to cause a problem.\nA Look Inside # One of the cool things about the M910Q is that it just needs a single screw on the outside removed in order to gain access to the inside, and most maintenance thereafter is tooless. The top cover slips off, and then this allows a plate on the bottom to also be removed.\nLet\u0026rsquo;s look at the view of the inside from the top:\nThe CPU is socketed, and hidden away under the duct extending back from the fan. From the videos I\u0026rsquo;ve seen, I believe that you might need to undo some screws to remove the heat sink and access the CPU socket.\nThe black plastic construct to the right is the SATA drive cage. Squeeze that semi-circular plastic bar towards the cage and the whole thing pops out. The ribbon cable for the drive is held onto the cage with that yellow tape. On mine, it was zip-tied - which I think is the standard.\nThe cage will accept a 2.5\u0026quot; drive with a height of 7mm. You can install an SSD or an HDD.\nUnderneath it is an M.2 slot for a WiFi card, or something compatible with it. Some models come with the WiFi card and the antenna.\nPull off the plate on the bottom, and this is what you\u0026rsquo;ll see:\nDon\u0026rsquo;t be fooled by the text on the PCB that says, \u0026ldquo;SATA/NVMU/SSD2\u0026rdquo;. It\u0026rsquo;s unpopulated on the M910Q and the M710Q. I believe that the M920Q and the M720Q might have that slot. In this picture, we have an SSD installed in slot #1.\nThe only other things under here are slots for the memory.\nThat\u0026rsquo;s pretty much it. There are a small number of options that might be installed in these. Mine have two DB9 serial ports, but others have additional DisplayPort ports. Presumably, you could swap these out, but the choices are fairly limited.\nFrom what I have seen, some of the later HP EliteDesk Mini\u0026rsquo;s have a optional 2.5GB adapter on a daughterboard that can just be slotted in. But there\u0026rsquo;s nothing like that on the Lenovo\u0026rsquo;s.\nIn one of my units, I\u0026rsquo;ve installed a Coral TPU in the WiFi M.2 slot. This is for some AI processing in my NVR server.\nPorts on the Back/Front # Lenovo sold these in all kinds of configurations, but they all have a pantload of USB 3.1 Gen 1 ports on the back and front.\nMost of them seem to have 2 or more DisplayPort ports. No HDMI. You might miss this if you\u0026rsquo;re not paying attention, and you\u0026rsquo;ll have to make sure that you have a DisplayPort compatible monitor (or a converter) in order to do the initial setup.\nThey use a standard Lenove square power port. This is the same as what you\u0026rsquo;d find on a Lenovo laptop. They use power bricks that come in 60W and 90W variants. You\u0026rsquo;ll probably be fine with the 60W versions for most purposes.\nThere is but a single 1GB ethernet port.\nMine must have come from some kind of lab or engineering environment. They are festooned with DB9 serial ports, which I assume were used to connect to test equipment or other stuff that pre-dated USB. I don\u0026rsquo;t think I\u0026rsquo;ll ever use them.\nLimitations # Without a doubt, the biggest limitation of these systems, if you are going to use them as Proxmox hosts, is that they only have a single 1GB Ethernet adapters. There\u0026rsquo;s no upgrade potential here that doesn\u0026rsquo;t involve soldering and 3D printing.\nThis means that Ceph, and therefore HA clustering in Proxmox, is probably a no-go. However, I am planning on seeing how they perform with 2.5Gbs or 5Gbs USB NICs forming a isolated network for the cluster. It seems that these adapters are getting more stable now.\nIn general, however, beyond adding memory, a SATA drive and possibly putting in a larger SSD in the NVME slot, there\u0026rsquo;s not a lot of room for upgrading or enhancing these systems. There\u0026rsquo;s just no room inside them.\nIf you really, really, really feel like you need the ability to add a PCI card to do something specific, then you might consider the M920Q or the M720Q. These have a PCI slot, although you\u0026rsquo;ll need an additional riser card to be able to use it. Note that you\u0026rsquo;ll probably have to spend about $100-$150 more to get one of these tricked out with the riser card.\nPricing # This is where these little units really shine. The price.\nThe outfit I bought my units from was selling them with a standard configuration of 8GB RAM and a 256GB M.2 SSD. The first two ones I ended up with had DB9 serial connectors, but no WiFi.\nThe basic model with a 6th gen i5 was $100, while the i7 (not sure of the generation) was close to $200. I did end up with one 7th gen i5, and that was $110, the other two were 6th gen i5 processors. I increased the memory in all of them to 16GB, because I strongly suspected that 8GB wasn\u0026rsquo;t going to cut it for Proxmox servers. When I bought the first two, I simply bought an extra 16GB DIMM and them moved one of the 8GB DIMMs to the other system. For the third one, I just paid to swap out the 8GB DIMM for a 16GB one.\nThis is going to make upgrading them to 32GB a bit more expensive, because I\u0026rsquo;ll have to by 4 16GB DIMMs and hope I can trade in the two 8GB DIMMS.\nWith the memory upgrade, I paid about $115 CAD each for the first two, and then about $145 CAD for the third. So, about $375 CAD together. They came with a six month warranty.\nValue Compared to Raspberry Pi\u0026rsquo;s # I checked, and a RPi 5 with 4GB RAM goes for about $105 CAD. The performance of the RPi is significantly lower than that of the Core i5-6500T, especially when it comes to multi-core performance, where the RPi scores about 60% of that of the i5-6500T.\nIt\u0026rsquo;s almost certain that the M910Q draws more power than the RPi 5, especially when idle. So if you do have an application that needs fairly low single-core processing power which is idle for most of the time, an RPi might be a more appropriate solution. Also, if you do have a need for the GPIO capabilities of the RPi, then it\u0026rsquo;s clearly the way to go.\nPerformance # Performance issues are a significant difference between running a corporate data centre and a homelab.\nIn the corporate world, you can reasonbly assume that all of your various servers are going to be running under a consistent and considerable load all day. With hundreds of users and services exposed to the Web, you can be pretty sure that just about everything is doing something at any given time. Especially between 9am and 5pm. This means that you have to provision and configure your data centre to meet that fairly predicatible load across a number of services active at the same time, and then also to handle peaks that happen from time to time.\nBut homelabs aren\u0026rsquo;t like that. You probably have more virtual servers than users, to start with. Some of your services are likely to be 99% idle most of the time.\nThat\u0026rsquo;s what it\u0026rsquo;s like with my homelab. I have a server that streams whole-home audio 24/7, but it only uses 2% of 1 core, and about 60MB of RAM. I have a couple of servicers that wake up once every 5 minutes and check my two ISP connection speeds, log the results and send me a notification if either one runs really slow. I have a service that scans my network for active devices every few minutes.\nAll of the other servers support \u0026ldquo;on demand\u0026rdquo; services. JellyFin is mostly active when someone is watching a video. Immich when someone is uploading, downloading or viewing pictures. Vaultwarden when someone is synching passwords. The backup servers do all of their work in the middle of the night, around 3:00am. You get the idea.\nThis has a very different impact on how your servers perform\u0026hellip;\nCPU # One of the biggest surprises for me was just how little CPU the various Proxmox containers actually consume. I have about 28 VM\u0026rsquo;s and LXC\u0026rsquo;s running right now and the Proxmox dashboard says I\u0026rsquo;m using 4% of my CPU. These containers include HomeAssistant, JellyFin, SnapCast, NetAlertX, Open Media Vault, Immich, an *arr suite, NGINX and two Technitium DNS servers. A couple of containers are running Docker.\nOnce in a while I can peg the CPU on one of the servers. If I upload a ton of images into Immich, it\u0026rsquo;ll run hard for a while as it does its facial recognition and processing. Open Media Vault can get pretty heavy at times, too.\nThe key thing here is that any given virtual service, running full tilt, is only going to be able to occupy 33% of my homelab\u0026rsquo;s CPU, because it only has access to the CPU on its own host. On the other hand, it\u0026rsquo;s probably a good idea to spread your potentially CPU intensive virtual servers across your Proxmox nodes. Maybe even consider which ones are likely to be running at the same time, and keep them on different nodes.\nMemory # Overall, the cluster seems to be running with about 30% of its memory in use. This can vary quite a bit if any of the services gets busy. Most of my servers are deployed as LXC\u0026rsquo;s which are much more dynamic in the way that they consume and release memory than traditional VM\u0026rsquo;s. I think that this has helped to keep the memory requirements fairly low.\nOnce again, virtual servers cannot access resources not on their host node. Looking to keep memory intensive applications spread across all the nodes is probably a good idea.\nPower Consumption # Eventually I got around to installing a UPS for my HomeLab and it has a display that shows how much power it\u0026rsquo;s drawing. With all of the essential components to stay \u0026ldquo;up\u0026rdquo; connected to the UPS, it\u0026rsquo;s pulling about 80 watts. This is 3 M910Q\u0026rsquo;s, the HP T740 hosting the firewall, the external drive bay, the switch and the two modems.\n80 watts. I\u0026rsquo;m impressed.\nThe UPS predicts it can last for a couple of hours with that load, and it\u0026rsquo;s not a big UPS.\nApplication Performance # In terms of user experience, there are no performance issues. These servers are 100% capable of meeting every demand placed on them in response to user activity.\nOne of the things I did when setting up Immich for my wife was to use Google TakeOut to download about 60GB of photos from her Google account. Then I uploaded all of them into Immich. I found that it took about 15 minutes for Immich to fully digest about 1000 photos, which included its facial recognition. This also seemed to line up with the maximum number of files that the upload selection window could handle, which is more of a UI issue than anything else. So it did take some hours of going back and uploading the next batch of 1000 every 15 minutes or so. During those 15 minutes, the Immich server was running at about 90% CPU use, so I was too chicken to try to upload another batch of 1000 before it was done.\nThat was the only time I\u0026rsquo;ve noticed any impact of the server power on user experience.\nAfter Installing Frigate # I have recently installed a Frigate NVR virtual server processing two cameras outside my house. I\u0026rsquo;m going to be adding a video doorbell to the load on this server. I added a Coral TPU to the node it\u0026rsquo;s running on to assist with some of the object detection and recognition that it does. Additionally, I installed Frigate on the only node that I had that has an internal SATA drive, and that\u0026rsquo;s an HDD, not an SSD. I assume that the HDD was idle and not consuming much of any power prior to configuring Frigate to use it as storage for its recordings.\nThis was the one application that I was a little concerned about with servers this low powered. However, it turns out not to be a problem.\nI now see my entire cluster running at about 20% CPU, with the node hosting Frigate running at about 50% most of the time. Memory usage on both the cluster and the Frigate node is averaging about 50%.\nMore than anything else, my UPS is now reporting around 100W consumption most of the time. That\u0026rsquo;s an increase of around 20W, which I assume can be attributed to the increased CPU usage and the HDD drive constantly spinning now.\nI\u0026rsquo;m not noticing any peformance degredation in any of my services. I do however occasionally hear some fan noise coming from that node, that I never heard before. Otherwise, these servers are virtually silent.\nConclusion # It\u0026rsquo;s been about 9 months since I bought these servers, and I\u0026rsquo;ve been constantly adding more and more services to my homelab throughout that time. At no point in time have I encountered any application where they weren\u0026rsquo;t up to the task. These servers are now doing real work all day, and have replaced a bunch of services that I used to have outsourced in the cloud.\nThat being said, I don\u0026rsquo;t think that these units are going to be a good fit for hosting my own AI services, which is something I think I\u0026rsquo;ll probably be looking to do at some point in the future. But even then I would probably keep my existing cluster and add on something specifically for AI processing.\nThe one thing that they simply won\u0026rsquo;t work for, due to the single 1Gb ethernet port, is as a firewall. You need two ethernet adaptors for that. So, for a firewall I implemented an HP T740. It\u0026rsquo;s very similar to the M910Q\u0026rsquo;s, but a little bit bigger, has an AMD processor and comes with a PCI slot with a riser card. This means that I was able to add a 2 port ethernet card to it.\nIf you are just starting out with self-hosting, I think these are an amazing place to begin. You can get a single server for less than $200, even with today\u0026rsquo;s RAM prices, and it will do whatever you need. Add a second or third, and you have a real data centre with all the possiblities of redundancy that you get along with that.\nFor myself, I\u0026rsquo;m really pleased at how these servers have worked out. I had the goal of building out my homelab over time and within our monthly discretionaly budget. The low cost of these servers has meant that I have been able to do this and I don\u0026rsquo;t feel that I\u0026rsquo;ve sacrificed anything with this approach.\n","date":"20 June 2026","externalUrl":null,"permalink":"/homelab/lonovo_tiny/","section":"Homelabs","summary":"A look at the Lenovo M910Q Tiny computers I’ve chosen as homelab servers.","title":"Lenovo M910Q Tiny Homelab Server","type":"homelab"},{"content":" Introduction # Back in 2003 I was doing some work in the company\u0026rsquo;s accounting department when all of the PC\u0026rsquo;s in the area flicked off and rebooted again. It was so fast and simultaneous that I thought that we\u0026rsquo;d had some kind of electrical fault that hit that part of the office, and I didn\u0026rsquo;t think too much about it. Comparing notes with one of the other IT guys much later on, he had also noticed the same thing in another part of the office.\nSometime later, our Internet connection was down. Or so I thought.\nI got in touch with our ISP and they checked and told me that, no, the connection was up and there was actually a LOT of traffic going through it. So I went into the computer room to check.\nBack in those days, our connection between the our firewall and the T3 modem was through an old 10Base-T hub. Not a switch, but a hub. This was okay, because these were the only two devices on the hub, and the 10Mbps, was still faster than our Internet connection.\nThe hub had a little red light on it that flashed whenever it detected a collision when two nodes try to transmit at exactly the same time and interfered with each other. During normal operations, you\u0026rsquo;d expect that light to flicker once or twice a minute. Any more, and you likely had a problem brewing.\nWell, I went into the computer room and that collision light was solid.\nNot flickering, just on.\nThere was so much traffic going down that hub that it was jammed up into constant collisions and nothing was getting through. Back in those days, we hosted our own web sites on premises, and this meant that we had essentially been kicked off the web.\nOur firewall logs had gotten so big in that short time that it had run out of disk space and stopped logging. I had to flush the logs and restart it just to find out what that traffic was.\nIt turns out that we had been hit with a worm. Probably the \u0026ldquo;Welchia\u0026rdquo;, or the \u0026ldquo;Blaster\u0026rdquo; worm. One of the characteristics of it was that, after installing itself, it caused the system to reboot. After that, each infected computer would start scanning like crazy to find more Windows systems that it could infect.\nIt was those scanning probe packets that were jamming up our internet connection. That simultaneous reboot that I had seen was actually a handful of computers getting infected at nearly exactly the same time.\nWe lost of few days of our lives to investigating this, working around it, and cleaning and patching the workstations.\nAt some point in our investigations, we determined that the virus was spread from a laptop that one of our sales/marketing guys was using. He had taken it home or to a client site, and plugged it into a network and got infected. Then he came into the office and plugged it in - behind our firewall.\nIt was like the babysitter late at night in the horror movie. The phone call is coming from the basement - inside the house!\nWe had no defence against this.\nSmart Home Devices # None of us have no problem going to the electronics store or the hardware store or the appliance store and coming back with some new \u0026ldquo;smart\u0026rdquo; device. A fridge. A robot vaccuum. Even a smart plug.\nAnd when we get it home, we take it out of the box, connect to it from our phones and give it our wifi password.\nNow it\u0026rsquo;s in our networks. Behind the firewall\u0026hellip;inside the house!\nYeah. But what can a smart plug do?\nIndeed. What if it opens a VPN tunnel back to some hacker den in North Korea? Really, I have no doubt that you could stuff enough brains inside a smart plug to do that. And once that\u0026rsquo;s done, the hackers half-way around the world can start poking around inside your entire home network. And you would probably never know.\nI\u0026rsquo;m seeing articles on-line that some refrigerators with display screens on them are starting to show ads. What if you don\u0026rsquo;t want ads on your fridge? What if I don\u0026rsquo;t want my printer reporting back to HP about how many pages I print each month? And let\u0026rsquo;s not talk about \u0026ldquo;Smart\u0026rdquo; TVs.\nI did a survey on my network, and between smart plugs and switches, robot vaccuums, garage door openers, streaming devices, smart speakers, printers and a few other things, I counted up about 45 devices.\nWhat do I know about any of them?\nHonestly, not much. So why trust them?\nZero Trust # When it comes to this stuff, \u0026ldquo;trust\u0026rdquo; is a very misguided concept.\nDo you need to trust these things? The answer is, \u0026ldquo;No!\u0026rdquo;\nWhat you need is a \u0026ldquo;Zero Trust\u0026rdquo; network design behind your firewall that lets you sequester these devices such that they can do no harm. Then you don\u0026rsquo;t care if they are malicious, because they\u0026rsquo;ve been neutralized.\nLet\u0026rsquo;s look at how to do that\u0026hellip;\nTCP/IP Subnets and Routing # A quick note to start: This article is about IPv4 routing, not IPv6. So if you are using IPv6, then you won\u0026rsquo;t need the information here.\nBefore we can go any further, we\u0026rsquo;ll need to have a basic understanding about how devices find each other and communicate over TCP/IP.\nIf you\u0026rsquo;ve ever manually set up a device on a network without DHCP, you\u0026rsquo;ll know that you need to provide four pieces of information:\nThe IP Address This is the literally the address of the computer on the network. It\u0026rsquo;s going to be in the format of ###.###.###.### where each ### is a number between 1-255 (ie. one byte). An IP address is always 4 bytes long.\nThe NetMask This tells the device the address space of the subnet on which it sits. It will use this information to determine if a device that it wants to communicate with is on its own subnet or not. We\u0026rsquo;re going to use a netmask of 255.255.255.0 or /24 in CIDR notation for all the subnets in this article which means that the first 3 bytes of the address (or the first 24 bits of the address) represent the subnet while the last byte (8 bits) represents the device\u0026rsquo;s unique place in that subnet.\nThe Default Gateway This should be considered to be a feature of the subnet, rather than the device that you are configuring (for purposes of this discussion). It tells the device the IP address of where to send its packets if it wants to communicate with a device that it has determined is NOT on its own subnet.\nThe DNS Server Addresses This is where to send requests for DNS lookups. It\u0026rsquo;s not relevant to this discussion.\nHow Devices Determine Routing # For purposes of this discussion, it\u0026rsquo;s probably best if you consider TCP/IP to be a \u0026ldquo;broadcast\u0026rdquo; protocol. This means, especially from the viewpoint of each individual device, that any packets it sends out are distributed throughout the subnet with an address attached to them. Only the device that has that address will pay any attention to it. In reality, modern switches and networking equipment handle the packet delivery without the need to actually send every packet to every device - but the devices don\u0026rsquo;t see that.\nEach device knows its own IP address and its netmask so that it can determine if any other device with which it wants to communicate has an address on the same subnet as itself. If so, then it just broadcasts its packets on its subnet with the destination IP address on it. The other device will see its own address and get the packet.\nIf it determines that the other device is NOT on the same subnet, then it still broadcasts it on its own subnet, but this time addressed to the subnet\u0026rsquo;s default gateway - with the actual address of the destination somehow encapsulated inside it. The gateway will get the packet, and then relay it off in some manner so that it gets to its destination.\nHere\u0026rsquo;s an example of what that would look like:\nIn this diagram, the top two PC\u0026rsquo;s belong to the 192.168.1.0 network, which is addresses 192.168.1.0 to 192.168.1.255. This is because they have the netmask 255.255.255.0 which you can see from the CIDR notation /24 in the addresses. These two computers can talk to each other directly.\nIf they want to talk to Server1, with address 10.196.212.116, then they will determine that it is NOT on their own subnet, so they have to send their packets to their default gateway, which is 2811 Router with the address 192.168.1.1. That router will then send it on to 10.196.212.116 through its second network interface, which has the address 10.196.212.1.\nWhat\u0026rsquo;s very important to understand here is that all three PC\u0026rsquo;s are connected to the same switch! Even so, only PC1 and PC2 can talk to each other directly, and any other communication requires relaying through the Router.\nThis is because the physical topology of the network is irrelevant to the TCP/IP routing.\nRouting With Firewalls # For this discussion, your really big takeaway should be this\u0026hellip;\nThe router in the diagram with the name 2811 Router could be a firewall. As a firewall, it would have rules that control if and how devices communicate between subnets.\nAs a matter of fact, firewalls can only control traffic between subnets and not between nodes on the same subnet. For instance, in the diagram above, there is no way that the firewall at 2811 Router could control traffic between PC1 and PC2 because they can communicate directly without involving the router/firewall. On the other hand, rules in the firewall at 2811 Router will control communication between Server 1 and either of PC1 or PC2.\nFrom this, you can deduce that the IP subnet is the basic building block of home network security.\nIntroducing VLAN\u0026rsquo;s # If you do some web searches about VLAN\u0026rsquo;s, you\u0026rsquo;ll find something like this introduction from Wikipedia:\nA virtual local area network (VLAN) is a local area network broadcast domain that is partitioned and isolated in a virtual network at the data link layer (OSI layer 2). A VLAN behaves like a virtual network switch or network link that can share the same physical structure with other VLANs while staying logically separate from them.\nVLANs work by applying tags to network frames that are forwarded within the broadcast domain, creating the appearance and functionality of network traffic that behaves as if it were split between separate networks. In this way, VLANs can keep network applications separate despite being connected to the same physical network, and without requiring multiple sets of cabling and networking devices to be deployed.\nEssentially this is describing something that looks like this:\nYou can see that although all of the ports are on the same switch, ports 1-4 cannot communicate with ports 5-8. Essentially, this turns the single switch into two switches.\nYou can also split a VLAN across switches, like this:\nThis divides both switches into 3 parts, and the ports in both switches can communicate with all of the ports of the same colour across both switches.\nAnd that\u0026rsquo;s about all you\u0026rsquo;ll get before you\u0026rsquo;re really deep, deep into the weeds. You\u0026rsquo;ll quickly find out about trunks and IEEE 802.1Q and lots of other complicated stuff.\nThis seems like a cool idea\u0026hellip;until you start wondering about how something on one VLAN can communicate with something on different VLAN.\nAfter all, if you can\u0026rsquo;t do that, then your VLAN is 100% sequestered and inaccessible to and from the rest of the world. And that is quite a bit less than useful.\nManaged Switches # Before we get into the details, we need to talk about one more thing\u0026hellip;\nYour standard, consumer grade network switch that you can buy for about $10 on any street corner probably won\u0026rsquo;t be able to handle VLAN\u0026rsquo;s. You\u0026rsquo;ll need something called a \u0026ldquo;Managed Switch\u0026rdquo;, which is generally marketted at corporate customers. They\u0026rsquo;re a bit more expensive than unmanaged switches, but you should be able to pick them up for $30 or less. You will, however, have to ditch any unmanaged switches that you already have - you could find a way to use them, but it\u0026rsquo;s not worth the hassle.\nA managed switch will generally allow you to do a whole bunch of things in addition to VLAN\u0026rsquo;s, things like \u0026ldquo;spanning trees\u0026rdquo; and trunking. We\u0026rsquo;re not going to talk about that stuff here, though. Just VLAN\u0026rsquo;s.\nKinds of Port Membership in VLAN\u0026rsquo;s # If you read the Wikipedia snippet from above, you\u0026rsquo;ll have noticed that VLAN\u0026rsquo;s work by attaching \u0026ldquo;tags\u0026rdquo; (whatever they are) to network packets before they are broadcast around the network. The \u0026ldquo;tag\u0026rdquo; is just an numeric ID - generally between 2 and 1000 - and switch ports that understand VLAN\u0026rsquo;s will be programmed to only transmit and receive packets with particular tags.\nTag 1 is special. It\u0026rsquo;s the default \u0026ldquo;no VLAN\u0026rdquo; tag, and get\u0026rsquo;s slapped on any untagged packet that travels around VLAN aware network equipment that hasn\u0026rsquo;t been configured to do anything different. You cannot use tag 1 for anything else.\nFrom my experience, when you configure a managed switch to use VLAN\u0026rsquo;s you need to define each VLAN tag within the switch, and then configure how each port interacts with each VLAN that you have defined. Let\u0026rsquo;s look at how this works\u0026hellip;\nPorts on managed switches can handle VLAN\u0026rsquo;s in two ways:\nUntagged Packets This is what you see in most of the introductory articles and descriptions. In this case, the port is configured to add a VLAN tag to any packets that come into it that aren\u0026rsquo;t already tagged. This means that if you have some device that is not VLAN aware, the port on the switch will assign the device to a VLAN for it. The default setting for this is almost always to add VLAN 1.\nTagged Packets This is a mode where the switch port is assigned membership of one or more VLAN\u0026rsquo;s and it will allow incoming packets tagged with those VLAN id\u0026rsquo;s to pass through. Packets tagged with any other VLAN\u0026rsquo;s will be blocked. This means that if you plug a VLAN aware device into the port, that device can decide which VLAN\u0026rsquo;s it wants to communicate on, assuming the port is a member of those VLAN\u0026rsquo;s.\nConfiguring the Switch # Obviously, the interface for configuring each brand of switch is going to be different. Generally, though, you\u0026rsquo;ll be looking for a section with a name like \u0026ldquo;802.1Q VLAN\u0026rdquo;.\nIn my D-Link switch the interface looks like this:\nIf you click on one of the VLAN number links, you get a screen like this:\nThe method with this switch is that you define each VLAN, give it a name and then define how each port interacts with the VLAN.\nIn this switch, the default VLAN 1 remains the VLAN to which all untagged packets are attached. Then all of the ports are configured as tagged members of all of the custom VLAN\u0026rsquo;s that I created. Essentially, every port will be able to communicate on every VLAN if the packets are already tagged, and will leave the untagged packets untagged.\nI should point out that this is not the final configuration that I intend for this switch. I\u0026rsquo;m still in the process of sorting out all of my network security and I\u0026rsquo;m leaving this wide open while I configure everything else and test it. I don\u0026rsquo;t want something to fail and waste lots of time looking for setup issues in my firewall whilst the actual problem was the wrong VLAN on a port on this switch. This switch is the one closest to my entertainment centre, and has most of the \u0026ldquo;VLAN unaware\u0026rdquo; wired devices on my network. Things like my XBox and my IPTV box. Eventually, I\u0026rsquo;ll be reconfiguring this switch such that those devices go into the IOT VLAN (I think).\nUnderstanding Tagged VLAN Membership # You might find it a little hard to picture how this second case is used at first.\nHowever, if you are using Proxmox for virtualization, you\u0026rsquo;ll have seen this screen for configuring the networking on a Proxmox host:\nHere the Linux Bridge vmbr0, which defines how the VM\u0026rsquo;s on the node will connect to the ethernet adapter, is defined. Note the checkbox beside \u0026ldquo;VLAN aware:\u0026rdquo;, and the range of \u0026ldquo;2-4094\u0026rdquo; that is assigned to \u0026ldquo;VLAN IDs:\u0026rdquo;.\nThen, when you define the networking for a VM on that Proxmox host, you\u0026rsquo;ll see this screen:\nIn this screen eth0 is being defined as connecting to vmbr0 and having VLAN tag 20 attached.\nWhat\u0026rsquo;s important to note is that if we define another Linux container or VM on this same node, attached to the same bridge, but assigned to VLAN tag 30, then it won\u0026rsquo;t be able to communicate directly with the first VM. Even though they are sharing the exact same hardware.\nHowever, these VM\u0026rsquo;s will not be able to communicate with anything unless the port on the switch that the host is plugged into is a member of both VLAN\u0026rsquo;s 20 and 30 as \u0026ldquo;tagged\u0026rdquo;.\nFirewalls and Routers # The second, and probably most important place you\u0026rsquo;ll encounter a \u0026ldquo;VLAN aware\u0026rdquo; device is a firewall or a router. For sure, both OPNsense and pfSense are very VLAN aware.\nUnderstand that a firewall is always, by definition, a router, and that a router is designed to transfer traffic between ports. Essentially, firewalls use VLAN\u0026rsquo;s to define multiple network interfaces on the same hardware port. Each VLAN becomes a \u0026ldquo;device\u0026rdquo; which can have its own ethernet address.\nIn OPNsense, you define a VLAN as a device, and attach it to a physical ethernet port and connect it logically to a VLAN defined on the switch. From that point on, you can treat your VLAN device just like any other network port. You can assign it an IP address, you can create firewall rules for it, and you can create a DHCP server for it.\nThis is what enables you to use VLAN\u0026rsquo;s to control security inside your network. Once you\u0026rsquo;ve done this, you\u0026rsquo;ve created the authoritative connection between a VLAN and a TCP/IP subnet.\nLet\u0026rsquo;s say that you have that VLAN 20 from the screenshot above. On your firewall, you define a network interface called VLAN_DMZ and assign it to your inside ethernet port using VLAN 20. You\u0026rsquo;re going to use VLAN_DMZ as the gateway for the network 10.10.10.0/24, so you give it the address 10.10.10.1.\nNow, if any other device inside your network is on VLAN 20, and if it wants to talk to anything outside VLAN 20 it is going to have to be on 10.10.10.0/24. Otherwise it cannot communicate with the gateway at 10.10.10.1.\nYou can see from this that the firewall configuration \u0026ldquo;locks in\u0026rdquo; VLAN 20 to the 10.10.10.0/24 subnet, and the firewall rules will define how it can behave as a \u0026ldquo;DMZ\u0026rdquo;.\nSo far, none of this particularly uses the isolation qualities of VLAN\u0026rsquo;s. It really just enables us to have just about as many subnets as we want on our network without having to stuff a new NIC inside our firewall for each subnet.\nIn fact, you could just ignore the security aspects of the separation qualities of VLAN\u0026rsquo;s and configure every single port on every single switch that you own to accept traffic from every single VLAN that you create and you probably wouldn\u0026rsquo;t sacrifice too much actual security. I\u0026rsquo;m not suggesting that you do this, but it wouldn\u0026rsquo;t be the end of the world if you did.\nDHCP # The one place where it really is useful to have separation between the VLAN\u0026rsquo;s is the one commonly used TCP/IP service which is truly treated as a broadcast - DHCP.\nWhen a device is attached to the network that has been configured to use DHCP to get its network information it doesn\u0026rsquo;t initially have an IP address or even any knowledge about the network that it\u0026rsquo;s plugging into. How does it communicate with the DHCP server on the network without an IP address?\nThe answer is that it uses pure local broadcast on the IP network. Essentially, all of the communication is performed by using the address 255.255.255.255 which will be seen by every host on the local network. Most importantly, packets addressed to 255.255.255.255 will never pass through a router - they are strictly local.\nFirst, the device broadcasts a \u0026ldquo;discover\u0026rdquo; message to the entire network that essentially says, \u0026ldquo;I\u0026rsquo;m here! Can anybody give me an address?\u0026rdquo; Every DHCP server that sees that broadcast will respond by broadcasting an \u0026ldquo;offer\u0026rdquo; of an IP address. The new device then picks one of the offers, and \u0026ldquo;requests\u0026rdquo; that address be assigned to it (once again by broadcast). The DHCP server that made the offer then broadcasts an \u0026ldquo;acknowledgement\u0026rdquo; of the assignment.\nThis process is known as \u0026ldquo;DORA\u0026rdquo;, which means Discover-Offer-Request-Acknowledge.\nThe key point for us is the part that says, \u0026ldquo;Every DHCP server that sees that broadcast\u0026hellip;\u0026rdquo;. Now go back and look at the first VLAN picture in this article. See how it says, \u0026ldquo;Broadcast message in\u0026rdquo;, and \u0026ldquo;Broadcast message out\u0026rdquo;? And see how the VLAN\u0026rsquo;s prevent the broadcasts from crossing between them?\nWhat this means is that every single VLAN on your network needs its own DHCP server if you want to use DHCP. In our example, we would create a DHCP service for VLAN_DMZ, which listens and broadcasts on VLAN 20.\nOnce again OPNSense works great here. Using \u0026ldquo;Dnsmasq DNS and DHCP\u0026rdquo; - which is the DHCP service that you should be using now - you don\u0026rsquo;t set up multiple DHCP servers, but you do create up rule sets for different interfaces. OPNSense will then listen on each interface that you\u0026rsquo;ve configured, and provide an address, mask, gateway and DNS server tailored for that VLAN.\nThis is the piece that puts it all together.\nOnce you\u0026rsquo;ve done this, you can now connect a device to your network configured to use DHCP and assigned to a VLAN - either through the device itself or through the switch that it\u0026rsquo;s plugged into - and it will be attached to a particular subnet that you have defined for that VLAN.\nWiFi # But what about all those devices that connect via WiFi? They\u0026rsquo;re generally not VLAN aware, and they don\u0026rsquo;t plug into a switch port that you can configure for untagged packets.\nThe answer is that most WiFi access points allow you to associate a WiFi SSID with a VLAN. Even consumer grade WiFi routers allow you to set up a \u0026ldquo;Guest\u0026rdquo; SSID and associate it with a VLAN.\nI\u0026rsquo;ve been using D-Link DBR-X3000-AP access points, and they have the ability to set up 6 \u0026ldquo;Guest\u0026rdquo; zones, each with an SSID and VLAN tag:\nNow, when I\u0026rsquo;m setting up a new smart plug or other device, I just configure it to connect to my \u0026ldquo;IOT\u0026rdquo; SSID, and to use DHCP. Everything else just works. You\u0026rsquo;ll also have to make sure that whatever switch port you plug the AP into is configured to recognize the VLAN\u0026rsquo;s that you use for all of the SSID\u0026rsquo;s that you create.\nThe Default Setup with ISP Equipment # Chances are that you started out with, and might still have, the WiFi router that your ISP sent you when you signed up. It might, or it might not be a separate component from your Internet modem - you\u0026rsquo;re better off if it is separate.\nLet\u0026rsquo;s look at what your network looks like with this equipment:\nThe problem is twofold:\nThe WiFi router is integrated with the internet router. The consumer grade, plug and play, equipment cannot be configured adequately. These two items mean that the equipment is great for people who don\u0026rsquo;t have the technical knowledge to do much more that plug the bits together, and who can\u0026rsquo;t figure out how to even change the WiFi password. And that\u0026rsquo;s probably the vast, vast majority of customers that the ISP\u0026rsquo;s are dealing with.\nBut if you are building your own, self-hosted, services, you\u0026rsquo;ll need something different. For instance, with a standard, consumer, WiFi router, you\u0026rsquo;ll probably have a single \u0026ldquo;guest\u0026rdquo; SSID avaliable, and it might even allow you to associate it with a VLAN. But if you really want to use VLAN\u0026rsquo;s and subnets properly, you\u0026rsquo;ll need a few more SSID\u0026rsquo;s. Also, if you are implementing your own firewall, you\u0026rsquo;ll probably need to turn off all of the routing in your WiFi router, along with things like NAT and DHCP. You\u0026rsquo;ll probably need to put it into \u0026ldquo;Bridge\u0026rdquo; or \u0026ldquo;AP\u0026rdquo; mode if that\u0026rsquo;s possible.\nThe WiFi router that my latest IP sent me is so simple it doesn\u0026rsquo;t even have a \u0026ldquo;Bridge\u0026rdquo; mode. I didn\u0026rsquo;t even bother with it so it\u0026rsquo;s just stayed in the box in a closet.\nGenerally speaking, you\u0026rsquo;ll want to replace your WiFi router with a WiFi \u0026ldquo;Access Point\u0026rdquo; or \u0026ldquo;AP\u0026rdquo;. As soon as you go looking for these, you\u0026rsquo;ll discover that you\u0026rsquo;re not in the consumer marketplace any more. Marketting bumpf will talk about how good the devices are for conference rooms or multi-unit dwellings. Access points are fairly cheap, too.\nSetting up OPNSense to Use VLAN\u0026rsquo;s # Let\u0026rsquo;s take a quick survey of how you would set up OPNSense to enable VLAN\u0026rsquo;s and subnets on your network with just two physical ethernet ports on your server, one of which is the WAN port - and we won\u0026rsquo;t talk about that. This isn\u0026rsquo;t a step-by-step guide, but really just an overview of the things that you need to achieve in order to implement VLAN\u0026rsquo;s and subnets. If you want step-by-step instructions, you can find tons of them on YouTube.\nCreating VLAN Interfaces # When you start out, you\u0026rsquo;ll have an \u0026ldquo;Interface\u0026rdquo; configured for the physical internal ethernet port. This is generally called \u0026ldquo;LAN\u0026rdquo;. You\u0026rsquo;ll neet to create an Interface for each VLAN that you are going to set up.\nCreating the VLAN Device # Before you can create an Interface, you\u0026rsquo;ll need to set up a VLAN device. In the \u0026ldquo;Interfaces\u0026rdquo; menu there is a submenu called \u0026ldquo;Devices\u0026rdquo; and an item in that called \u0026ldquo;VLAN\u0026rdquo;. Click on that option and you\u0026rsquo;ll get a list of all of the VLAN devices that have already been defined, and then click on the \u0026ldquo;+\u0026rdquo; button to add a new one.\nYou\u0026rsquo;ll get a screen that looks like this:\nThe device name is a bit tricky, it needs to start with \u0026ldquo;vlan0\u0026rdquo; and then have some more numeric stuff after that. The parent should be the device associated with your LAN interface. Then you give it whatever VLAN tag that you are going to use in your switches. Give it a description that makes sense to you.\nAssigning the VLAN Device to an Interface # The next step seems like something that should probably happen automatically, but doesn\u0026rsquo;t. In the \u0026ldquo;Interfaces\u0026rdquo; menu there is an item called \u0026ldquo;Assignments\u0026rdquo;. Click on that and you\u0026rsquo;ll get a screen with a list of devices that have been assigned to interfaces. Down at the bottom is a little section to add new assignments:\nThere\u0026rsquo;s not much to it. Just a dropdown and a textbox for the description. Note that this description is what you are going to see all the time when you deal with just about anything else in OPNSense. So pick a name that makes sense to you.\nAt this point, you will have created an Interface for your VLAN! But it still needs to be configured.\nConnecting Your VLAN to a Subnet # Once you\u0026rsquo;ve hit \u0026ldquo;Apply\u0026rdquo;, you\u0026rsquo;ll get a new item in the \u0026ldquo;Interfaces\u0026rdquo; menu with the name of your Interface. Go ahead and click on it:\nNot shown here is the checkbox to enable the Interface. Be sure to click on that to enable the Interface.\nI\u0026rsquo;m just using IPv4 here, so you I\u0026rsquo;m not going to do any IPv6. Select \u0026ldquo;Static\u0026rdquo; for the IPv4 configuration type and then you\u0026rsquo;ll see the box at the bottom with address textbox.\nThat address textbox is where you connect the VLAN to the IP subnet. You are not just giving the Interface an address, you are defining the subnet to which it belongs. That\u0026rsquo;s what the dropdown box to the right is for. I picked \u0026ldquo;24\u0026rdquo; because I want 10.122.73.0 to be the subnet, with a mask of 255.255.255.0.\nThat\u0026rsquo;s all you need to specify: enable, static IPv4, address and mask. Hit \u0026ldquo;Save\u0026rdquo; and then \u0026ldquo;Apply\u0026rdquo;.\nNow your Interface and VLAN is set up and ready to go. You\u0026rsquo;ll see that new Interface name pop up all over the OPNSense UI whenever it\u0026rsquo;s an option. Most importantly, it will be an option in the Firewall --\u0026gt; Rules menu. Don\u0026rsquo;t forget that at this point, it has no rules except the default, \u0026ldquo;Deny Everything\u0026rdquo; rule, meaning that this subnet is 100% sequestered for outgoing traffic.\nSetting Up DHCP # The last step is to configure the OPNSense DHCP service to listen on your new Interface/VLAN.\nAt the time that I\u0026rsquo;m writing this, the older DHCP services are being phase out, and KEA is probably overkill for simple home networks. Which means that you should be using Dnsmasq DHCP. So that\u0026rsquo;s what I\u0026rsquo;ll show here.\nThe DHCP configuration is in the \u0026ldquo;Services\u0026rdquo; menu. The first thing you\u0026rsquo;ll need to do is some basic configuration for the service. Click on Services --\u0026gt; Dnsmasq DNS \u0026amp; DHCP to open up the submenu and then click on General. You\u0026rsquo;ll get a screen that will allow you to enable the service, and then select all of the Interfaces that you want it to work with. Save this and you\u0026rsquo;re ready to set up your subnets.\nSetting Up DHCP Options # Just as when you set up static IP on a device where you need to specify subnet and mask, gateway and DNS servers along with device IP address, DHCP has to provide the same information. The subnet and mask are handled automatically, but you need to configure the rest.\nIn OPNSense Dmasq the gateway and the DNS servers are treated as \u0026ldquo;options\u0026rdquo;. You\u0026rsquo;ll need to configure these for each Interface. There\u0026rsquo;s actually a fair number of options available, and you\u0026rsquo;ll have to pick the correct two.\nIn the DMasq screen in OPNSense there\u0026rsquo;s a tab (and a menu item) called \u0026ldquo;DHCP Options\u0026rdquo;. This will show a list of the options that you have configured for each Interface. There\u0026rsquo;s the usual \u0026ldquo;+\u0026rdquo; button at the bottom right. Click it and you\u0026rsquo;ll see a dialogue like this:\nPick the Interface for your VLAN and select \u0026ldquo;set\u0026rdquo; as the action. You need to do this twice. Once for \u0026ldquo;router [3]\u0026rdquo; as default gateway address, and once for \u0026ldquo;dns-server [6]\u0026rdquo;. It\u0026rsquo;s probably a good idea to specify a description so that you can see what they are at a glance from the main listing.\nThat\u0026rsquo;s It # At this point, you have your firewall configured to support your VLAN\u0026rsquo;s. It\u0026rsquo;s beyond the scope of this article to talk about firewall rules, but just remember that each of these subnets is automatically created in a fully sequestered mode. Nothing can get out, and nothing can get in. You\u0026rsquo;ll have to add the rules to allow devices on these subnets to communicate outside the subnet.\nDon\u0026rsquo;t Forget the Switch # Whatever switch port that LAN on your OPNSense firewall is plugged into needs to be configured to allow traffic tagged with every VLAN that you set up in the firewall to pass through. Otherwise, none of this will work.\nIgnoring VLAN\u0026rsquo;s # The one caveat to all of this is that once a subnet has been connected to a VLAN, nothing outside that VLAN can directly communicate with any address on that subnet that is on the VLAN.\nLet\u0026rsquo;s say that you have three devices on ports 1, 2 and 3 of a managed switch. Ports 1 and 2 are configured to use VLAN 77 for untagged packets while port three is left at the deault configuration, essentially meaning no VLAN processing. The devices are all configured statically to have IP addresses 10.10.10.10, 10.10.10.11 and 10.10.10.12 in order in ports 1, 2 and 3. In this scenario, the devices in ports 1 and 2 can communicate, but the device in port 3 cannot communicate with either one of them, even though it is on the same subnet.\nFurthermore, that device on port 3 can only communicate with devices that are on the same subnet and also untagged. This is because the gateway - presumably at 10.10.10.1 - is going to also be on VLAN 77 if everything is configured correctly. This means that the device on port 3 cannot communicate with the default gateway for its subnet, and therefore cannot communicate with any device on another subnet.\nAt the same time, the devices on ports 1 and 2 can communicate with any device on any other subnet reachable by the firewall, regardless of what VLAN they are on. This is because the traffic leaving the firewall Interface on the other subnet is going to be tagged with the correct VLAN id for that subnet.\nYou can see that it\u0026rsquo;s important that once you start using VLAN\u0026rsquo;s to then use them everywhere, especially if you are configuring devices on subnets that aren\u0026rsquo;t associated with VLAN1, the default \u0026ldquo;No VLAN\u0026rdquo; VLAN.\nConclusion # The first thing to remember is that network security behind your firewall is every bit as important as security between your firewall and the outside world.\nIt\u0026rsquo;s incredibly easy to hear about VLAN\u0026rsquo;s and how important they are to your network security, but it\u0026rsquo;s much harder to find out how they do that. I\u0026rsquo;m hoping that this article helps to clear that up. But to recap:\nThe core of network security is based on controlling TCP/IP traffic. That control is virtually always achieved via a firewall to regulate traffic between IP subnets. VLAN\u0026rsquo;s work inside your network switches by restricting whether or not data packets will be delivered to particular switch ports and each port on a managed switch is configured such that it will accept packets (incoming or outgoing) with particular VLAN \u0026ldquo;tags\u0026rdquo;. Each port can also be configured to assign a specific tag to incoming packets that have no tags on them.\nYour firewall is the component that connects VLAN tagging with TCP/IP subnets and TCP/IP traffic control, which, once again is the main way that you implement your security. Each VLAN effectively becomes synonymous with a TCP/IP subnet.\nYou use this technology to divide your internal network up into subnets that represent different \u0026ldquo;zones\u0026rdquo;. Those zones, in turn, hold devices that share a similar trust profile and they are isolated from other zones via your firewall rules.\n","date":"6 February 2026","externalUrl":null,"permalink":"/homelab/vlans/","section":"Homelabs","summary":"How to secure your home network from threats from inside the house.","title":"VLAN's and Subnets For Home Networks","type":"homelab"},{"content":" Introduction # Recently, I posted an article titled, Should You Use FXML?. In that article, I stated that I felt that well written and organized code would always be easier to maintain than any corresponding FXML/FXML Controller would be. I did not emphasize this, as the article was intended to be a discussion about the merits and costs of using FXML more than as a \u0026ldquo;is this better than this?\u0026rdquo;, exploration.\nIn the article, I included a sample of a large (462 line) FXML file that I grabbed somewhat randomly from GitHub. Some readers expressed the opinion that the FXML was \u0026ldquo;bad\u0026rdquo;, and that they write better FXML by hand. Some expressed concern that I had cherry-picked bad FXML to use as an example, while others seemed upset at the idea of randomly picking FXML as an example.\nI wasn\u0026rsquo;t sure about how to respond to these comments because I realized that the frame of reference between myself and these commentors was just too different. It really didn\u0026rsquo;t matter to me if the FXML example that I picked was good, bad or average.\nIt didn\u0026rsquo;t matter because I understand that the way that I write layout code is astronomically better than FXML can ever be.\nBut I couldn\u0026rsquo;t say that. Who would believe me? What makes me think my coding is so much better?\nThis article is my attempt to explain my position. I\u0026rsquo;m going to take that example FXML and its FXML Controller code (and other stuff it turns out it needs to work), and re-write it as purely coded layout in Kotlin.\nMy hope is that you can look at the original FXML and code, and then look at my version, and you will see the potential for writing your layouts by hand.\nAnd this is not about me or my coding skill. There\u0026rsquo;s nothing in my version that anyone reading this article couldn\u0026rsquo;t do themselves, or learn to do themselves.\nApproach # Generally speaking, the goal is to reproduce the layout as close as possible to the original, so that it becomes clear just how much easier it is to understand and maintain a hand coded layout. However, there are secondary considerations that need to be taken into account:\nImproving the Layout I was torn about this at first. Eventually, I decided that there might be aspects of layout design that are adversely affected by using SceneBuilder to create the FXML file. It might be obvious, when hand-coding, that the same look and feel can be achieved through a better design. I have chosen to implement these improvements as these are generally problems that wouldn\u0026rsquo;t arise coding the layout by hand.\n{% include notice type=\u0026ldquo;primary\u0026rdquo; content = \u0026ldquo;This shows that the impact of hand-coding goes beyond being simply a matter of clarity and maintainability, but also affects the application design.\u0026rdquo; %}\nImplementing a Framework The orginal design, like most FXML implementations, has too much functionality in the FXML Controller. This is probably through the misplaced belief that the FXML Controller acts as an MVC Controller. In order to understand how a hand-coded layout is better in this respect, a MVCI framework has been applied. This results in the creation of a Presentation Model, and moving all of the application logic found in the FXML Controller into the Interactor.\nChanging to a Reactive Design It\u0026rsquo;s clear to me that JavaFX is intended to be used as a Reactive framework, and that it just works better that way. Systems built with a Reactive approach are simpler and cleaner, and easier to understand.\nThreading and the FXAT The FXML Controller doesn\u0026rsquo;t have any code that attempts to perform potentially blocking (like file access) operations off the FXAT. There\u0026rsquo;s no point in creating bad code for conversion, so I\u0026rsquo;ve organized the code such that thread handling is performed as it should be.\nWidgetsFX Since this is intended as an example of how a real application would be put together, I have chosen to use my own WidgetsFX library to implement many of the builders, extension functions and helper classes that I would ordinarily use to build layouts. I\u0026rsquo;m not including any of that code here, but you can easily tell what it would do.\nI also freely admit that I worked with the WidgetsFX project open, and that I added new functionality to it as required. This is the way that I would ordinarily work, and it\u0026rsquo;s also how the WidgetsFX library grows organically over time. {: .notice\u0026ndash;primary}\nStyling I\u0026rsquo;ve chosen to move any styling in the FXML or the FXML Controller into an external style sheet. Then I\u0026rsquo;m not going to create that style sheet because\u0026hellip;why bother. The result is that the screens in my project, while having an identical effective layout, don\u0026rsquo;t look like the originals\u0026hellip;but that\u0026rsquo;s not the point.\nKotlin # I\u0026rsquo;m writing this all in Kotlin for two reasons. Firstly, I find Java painful and unsatisfying to code with after several years of writing mostly Kotlin. Secondly, Kotlin just makes it so much easier to write clear, easy to understand layout code.\nI think that, even if you don\u0026rsquo;t fully understand the syntax, the Kotlin code is easy enough to understand for most Java programmers. Take a look at this:\nVBox(10.0).apply { padding = Insets(25.0) . . . children += intSpinnerOf(1, 50, \u0026#34;standard-spinner\u0026#34;, model.connectorThickness.asObject()) .setStep(1) .bindDisable(model.selectedDistance.isNull) .withInitialDelay(Duration.millis(500.0)) .withRepeatDelay(Duration.millis(500.0)) } } You don\u0026rsquo;t really need to know the details of how the extension function .apply{} works to understand that the code inside the {} configures the VBox. You can see that the padding is set to 25px, even though you don\u0026rsquo;t understand that Kotlin allows you to refer directly to values of fields that have getters and setters and that it will still call those getters and setters. Then, children += is clearly using the += operator on a List to do the equivalent of getChildren().add().\nIt\u0026rsquo;s also clear that setStep(), bindDisable(), withInitialDelay() and withRepeatDelay() are all configuration methods for the Spinner. Since they are chained, all of these methods are designed as decorators.\nIt\u0026rsquo;s obviously not clear what the parameters of intSpinnerOf() are, but Intellij displays what the parameter names are. So when you really are working with this code, there is no question what the parameters mean.\nFor purposes of this article, pretty much any Java programmer can look at this snippet of code and understand that it defines a VBox with a particular padding and that it contains a Spinner\u0026lt;Integer\u0026gt; that has been configured in a particular way and bound to some Property in the Model. That\u0026rsquo;s probably enough to get the point.\nThe Original Version # You can find this project on GitHub here.\nI was concerned that the project might be deleted or change beyond recognition over time, so I placed a snapshot of the FXML file, the FXML Controller and a few supporting files in this article. I\u0026rsquo;m not going to include all the code in this article itself, because it\u0026rsquo;s just going to be too big.\nWhen the program is running, the screen looks like this, although this version from the read.me page appears to be out of date and doesn\u0026rsquo;t quite match what the code does:\nThe code we are working on is the control panel in the upper left corner, so here it is close-up:\nUnfortunately, we don\u0026rsquo;t have screen shots of all of the Tabs in the control panel, but you can get a sense of the design from this.\nThere\u0026rsquo;s obviously some pretty strong styling going on here, it looks like maybe they used AtlantaFX? I didn\u0026rsquo;t go looking, and I\u0026rsquo;m not going to make any attempt to duplicate it. There is zero code or FXML that applies any of this styling, so I\u0026rsquo;m deeming it \u0026ldquo;out of scope\u0026rdquo;.\nNotes While Performing the Conversion # As I went through the process of performing the conversion, I made a point of taking some notes about issues that I encountered and ideas that occured to me\u0026hellip;\nThis Was Probably Generated From SceneBuilder # I came across this in the FXML file:\n\u0026lt;HBox alignment=\u0026#34;CENTER\u0026#34; spacing=\u0026#34;10.0\u0026#34; GridPane.columnSpan=\u0026#34;2147483647\u0026#34; GridPane.rowIndex=\u0026#34;8\u0026#34;\u0026gt; I cannot see any human entering \u0026ldquo;2147483647\u0026rdquo; as a columnSpan value. So this is either generated from SceneBuilder, or copypasta from some section that was generated by SceneBuilder.\nWhy does this matter? If the contention is that FXML is clear and easy to read, and yet we can blame most of the strange structures that make this file difficult to understand on SceneBuilder - then that\u0026rsquo;s an issue.\nThe Design is Difficult to Understand # It\u0026rsquo;s monolithic. 462 lines of FXML, and you need to scan all of it to understand that it\u0026rsquo;s basically just 4 Tabs in a TabPane. The GridPanes were a particular chore, as the components were not organized in the FXML file at all, just jumbled up willy-nilly and difficult to locate.\n{% include notice type=\u0026ldquo;primary\u0026rdquo; content = \u0026ldquo;All in all, I spent more time trying to understand the FXML and FXML Controller than I did writing my own version.\u0026rdquo; %}\n\u0026ldquo;Bad\u0026rdquo; FXML Doesn\u0026rsquo;t Appear Be More Verbose # It has become clear that you wouldn\u0026rsquo;t write FXML like this by hand. It\u0026rsquo;s just too jumbled up for that, and probably crosses into the realm of \u0026ldquo;bad\u0026rdquo; FXML. However, there really isn\u0026rsquo;t anything in its jumbled-upness that makes it longer or more verbose. As a matter of fact, there are things about it - like the lack of \u0026ldquo;row\u0026rdquo; or \u0026ldquo;column\u0026rdquo; specifications for GridPane elements in row or column 0 - that make it a bit less verbose.\nThe inescapable truth seems to be that if you are going to create a layout of this complexity, then you are going to need 450+ lines of FXML to do it. No matter how good your FXML writing skills are.\nDealing With FileChooser # In most of my programming, I don\u0026rsquo;t use FileChooser much at all, but this project and this screen do. So I had to grapple with the question of \u0026ldquo;Where does it go in the MVCI structure?\u0026rdquo;. It can be argued that FileChooser is a GUI element, and therefore belongs to the View, and that\u0026rsquo;s were I initially placed it. But then I ended up with references to File objects, and that caused me to look closer at this approach.\nFirst off, File is most distinctly not a front end class. It seemed worrying to have this data type handled by the GUI code.\nSecondly, the File had to be passed back to the Interactor somehow, because it\u0026rsquo;s the Interactor that was going to use it. This made it really clear to me that there was an issue. Why does the View get to know anything about the back-end structure. Furthermore, the View doesn\u0026rsquo;t actually use the File object, it just passes it back to the Controller.\nNow, what happens if the storage is changed from JSON files to a database? Or the application is changed to facilitate sharing designs between different users by email? Would that mean that you would have to change the View in order to make this change?\nFor sure, that FileChooser would need to change. Maybe it becomes a Dialog that allows for load and save to a database, or to select an email with attachments (or to send an email). But there\u0026rsquo;s no reason that you should have to change the View for that.\nIn the end, I moved the FileChooser calls into the Controller, which is where I think they really belong. It\u0026rsquo;s now integrated with the thread handling and invocation of Interactor methods. The File object is no longer tramp data passing from the View through the Controller to the Interactor.\nUnused? Elements # In the Tab labeled \u0026ldquo;PCA\u0026rdquo; there are two RadioButtons, \u0026ldquo;Use Hyperspace\u0026rdquo; and \u0026ldquo;Use Hypersurface\u0026rdquo; that do NOT have their isSelected values used in any code in the FXML Controller.\nHowever, the ToggleGroup to which they belong, pcahyperSourceGroup is NOT private to the FXML Controller. So it is possible that some other class with a reference to this FXML Controller is accessing this ToggleGroup, getting a reference to the RadioButton that is selected, and (Gasp!!!) checking it\u0026rsquo;s text value to find out which RadioButton it is.\nDon\u0026rsquo;t try this at home.\nNeedless to say, I\u0026rsquo;m not sure if these RadioButtons do or do not do anything - and that should be immediately apparent from reading the code. I\u0026rsquo;ve connected these RadioButtons to some elements in the Model that are clearly named as some kind of \u0026ldquo;dummy\u0026rdquo; value.\nIt may be that I grabbed the code in the middle of development and it just wasn\u0026rsquo;t yet complete. There were several new releases of this project between writing and publishing this article. On the other hand, this may just be something that somehow got lost and forgotten in the hundreds of lines of code here.\nConnecting to the Rest of the Application # When you look at the screen snaps on the project Read.me page, it\u0026rsquo;s clear that this screen is supposed to be a control/input screen for a real-time display elsewhere in the application. As such, there needs to be a way to communicate with the rest of the application in real-time.\nWe see that the original code implements this communication through EventHandlers, like this:\nscene.addEventHandler(ManifoldEvent.DISTANCE_CONNECTOR_SELECTED, e -\u0026gt; { } for incoming Events, and this:\nscene.getRoot().fireEvent(new ManifoldEvent(ManifoldEvent.DISTANCE_CONNECTOR_WIDTH, item.getDistance())); for outgoing Events.\nThis is probably one of the most insidious issues that can occur when you forego implementing a proper framework. The code you are writing is Event centric, so then everything is now an Event. Even when it really isn\u0026rsquo;t. I\u0026rsquo;ll discuss this more when we look at the new design.\nChanges to the Design # It was clear when doing the conversion that there were \u0026ldquo;issues\u0026rdquo; with the original design. Furthermore, it was clear that it would not make sense to leave these issues unaddressed when performing the conversion.\nIssues With the Original Design # Most of these issues, as far as I can tell, are a direct result of utilizing FXML in the way that most of the tutorials, including those from Oracle, tell you to. From what I can see, these issues build on each other. Let\u0026rsquo;s take a look at them:\nThere is no Framework # The original implementation assumed that the FXML file was the \u0026ldquo;View\u0026rdquo; and the FXML Controller was the \u0026ldquo;Controller\u0026rdquo; and the domain objects collectively comprised the \u0026ldquo;Model\u0026rdquo;. The results is that virtually all of the code resides in the FXML Controller and it does way, way too much while doing none of it well.\nThis is, in my opinion, a big problem with the design of this application, and this clearly shows how the automatic \u0026ldquo;separation of concerns\u0026rdquo; claimed for FXML is just a myth. There is no separation here, you have application logic, file handling and communication with other parts of the application muddled up with the configuration of individual Nodes. Within the layout itself, you have extensive coupling of Nodes all over the layout.\nThere is no Presentation Model # All of the data is stored in the screen Nodes, and there is no external data storage outside of the value Properties of the Nodes themselves. This means that all of the data must to be scraped out of the Nodes when it\u0026rsquo;s needed, which, in turn, means that all of the Node variables have to be globally scoped so that their data is available when required. Globally scoping those variables is a massive source of coupling within the FXML Controller.\nOf course, the design of FXML requires that all of the Nodes from the layout that are going to be accessed from the FXML Controller are instantiated as fields in the FXML Controller, which means that they are globally scoped. This makes it harder to see the benefits of attempting to limit the scope of these variables by implementing a Presentation Model.\nFinally, without a Presentation Model, it\u0026rsquo;s much more difficult to share the data with the application logic. This is obscured in this application by including the application logic inside the FXML Controller.\nIt is an \u0026ldquo;Action\u0026rdquo; Based Design # Without a Presentation Model to act as a data representation of the \u0026ldquo;State\u0026rdquo; of the GUI, it is very difficult to create a Reactive design. Instead of linking GUI elements together through the Presentation Model, they are directly referenced by other GUI elements.\nThe effect of this is that the application becames Node-centric, instead of data centric, and this shift, in turn, leads to an \u0026ldquo;Action\u0026rdquo; based design.\nUsing Events to Communicate Between Screens # The elements in this screen are used to control the manner in which data in other windows and screens is displayed. This means that this screen needs to communicate somehow with other windows and screens that are external to this layout. The programmers have decided to use JavaFX Events to do this. Consider this snippet of code:\nconnectorThicknessSpinner.valueProperty().addListener(e -\u0026gt; { DistanceListItem item = distancesListView.getSelectionModel().getSelectedItem(); if (null != item) { Integer width = (Integer) connectorThicknessSpinner.getValue(); item.getDistance().setWidth(width); scene.getRoot().fireEvent( new ManifoldEvent(ManifoldEvent.DISTANCE_CONNECTOR_WIDTH, item.getDistance())); } }); Here we have a Spinner configured such that every time its value changes a Listener is triggered and that Listener will eventually fire a ManifoldEvent of a certain type, in this case ManifoldEvent.DISTANCE_CONNECTOR_WIDTH type.\nPresumably, some external EventHandler is configured to respond to this ManifoldEvent and do something.\nOn the surface, this seems like a reasonable approach: The programmers needed a reliable messaging facility to communicate between components, and the JavaFX Event system seems to fit the bill. But there are some problems with this:\nSequencing can be a problem. Events invoke EventHandlers which are submitted to the FXAT. If a system is very active, it\u0026rsquo;s possible that there\u0026rsquo;s a lot of items in the FXAT queue, and they may have changed the environment in which the EventHandler will run. You cannot control this.\nIt deals in domain objects. That call to item.getDistance() returns a Distance which is a domain object.\nRelated to the previous item, the domain objects are stored in singleton Lists in Distance and Manifold. This leads to timing issues where domain objects might exist in the singleton lists but not in the ListViews yet. There is code that defends against this and generates errors when this happens.\nDelivery is not guaranteed. There\u0026rsquo;s nothing to say that some screen element couldn\u0026rsquo;t filter an Event before it gets to its intended target. This might be hard to debug. Essentially, the Event system is global and couples everything that uses it.\nIt\u0026rsquo;s not clear who the recipient is. Events are, by definition, broadcast entities. To find out how this Event is handled, you\u0026rsquo;ll have to search through the entire application to see which classes have an EventHandler for the ManifoldEvent.DISTANCE_CONNECTOR_WIDTH subtype.\nPersonally, I think that Events and EventHandlers are best used for very localized things. This would mean adding an EventHandler onto the Button that generates the Event (like a click ActionEvent). Using the JavaFX Event system as a general communication bus feels like a \u0026ldquo;code smell\u0026rdquo; to me.\nThe ListViews # This is the only part of the design which is objectively \u0026ldquo;wrong\u0026rdquo; from a technical JavaFX respect.\nThere are two classes for each ListView, let\u0026rsquo;s look at the one for manifolds. We have the class ManifoldListItem which extends VBox, and the class Manifold which we should probably consider to be a \u0026ldquo;Domain Object\u0026rdquo;. Every ManifoldListItem contains a reference to a Manifold.\nThe biggest problem is that the ListView is defined as ListView\u0026lt;ManifoldListItem\u0026gt; and then has no ListCell defined anywhere. This violates one of the principal rules of ListView and TableView: Do NOT put Nodes as the items.\nI don\u0026rsquo;t even know how this works. Honestly, this was the one thing that took me the closest to actually downloading the whole project and building it to see if it actually works.\nThe Manifold class has a static HashMap field that stores all of the Manifolds that have been created - essentially a singleton that can be accessed through the whole application. Whenever a new Manifold is created, there should also be an ManifoldEvent fired that should trigger an EventHandler added to the Scene. That EventHandlercreates a new ManifoldListItem and adds it to the ListView.\nIf the manifold HashMap is cleared, there should be an accompanying ManifoldEvent fired which trigger a different EventHandler set on the Scene.\nThere are three ColorPickers outside the ListView that display colours associated with the item selected in the ListView. There is code that updates the value in these ColorPickers when an ManifoldEvent indicating that a new Manifold has been selected. Also, when the value in the Spinners is changed, it fires a ManifoldEvent that presumably gets handled externally and updates the Manifold.\nIf all of this seems very confusing and roundabout, that\u0026rsquo;s because it is. This is one of those cases where doing it wrong is so, so much more complicated than doing it right. We\u0026rsquo;ll see how this works in the next section.\nThe New Design # The new version uses MVCI as a framework to implement a Reactive design. There is a Presentation Model composed of Observable data classes which are then bound (mostly bi-directionally) to the value Properties of the screen Nodes. This means that the layout elements are instantiated, configured, bound to the Presentation Model and then added to the layout and forgotten. There is no need to ever reference them again, as everything important about them has been bound to Properties in the Presentation Model.\nThere is no need to \u0026ldquo;scrape\u0026rdquo; data out of the GUI Nodes.\nAll of the application logic has been moved out of the layout code and into the Interactor. The Interactor is suprisingly small, because much of complexity of communication with the external screen has now been eliminated through shared data.\nThere is a SharedModel class which contains JavaFX Observable objects that are supplied by whatever element of the overall application contains this screen.\nThe Controller, as usual, handles instantiation, threading and connectivity to the rest of the application\u0026rsquo;s GUI.\nThe ListViews # This was the biggest architectural change to the design. The Manifold and Distance domain objects were transformed into Presentation Objects and all of the singleton related stuff was removed. Now they are simple JavaFX Observable POJO`s.\nThe ListViews were changed to have custom ListCells that mirrored the structure in the ManifoldListItem and DistanceListItem classes. These ListCells contain interactive Nodes like TextField and CheckBox who\u0026rsquo;s values are bi-directionally bound to the corresponding Properties in the item currently loaded into the ListCell.\nTo handle the values inside Manifold and Distance that are updated from outside the ListView, the Model has two Properties:\nval selectedDistance: ObjectProperty\u0026lt;Distance\u0026gt; = SimpleObjectProperty() val selectedManifold: ObjectProperty\u0026lt;Manifold\u0026gt; = SimpleObjectProperty() These need to be synchronized with the current selection in the ListViews. Ordinarily you could do this:\ninfix fun \u0026lt;T : Any\u0026gt; ListView\u0026lt;T\u0026gt;.bindSelection(boundProperty: ObjectProperty\u0026lt;T\u0026gt;): ListView\u0026lt;T\u0026gt; = apply { boundProperty.bind(selectionModel.selectedItemProperty()) } But there\u0026rsquo;s a twist here\u0026hellip;\nThe selected item needs to be synchronized with an external selection of those objects from some other part of the application. This needs to be in both directions. That external component needs to be able to select these objects, and react to changes in the selection from these ListViews. Ordinarily, you\u0026rsquo;d just make the bind bi-directional, but there\u0026rsquo;s an issue there:\nfinal ReadOnlyObjectProperty\u0026lt;T\u0026gt; selectedItem Oh no! The selectedItem property in SelectionModel is read-only! The only way to change the value programmatically is to call SelectionModel.selectItem(). This means we\u0026rsquo;ll have to use subscriptions to handle the changes:\ninfix fun \u0026lt;T : Any\u0026gt; ListView\u0026lt;T\u0026gt;.connectSelection(connectedProperty: ObjectProperty\u0026lt;T\u0026gt;): ListView\u0026lt;T\u0026gt; = apply { selectionModel.selectedItemProperty().subscribe { newItem -\u0026gt; connectedProperty.value = newItem } connectedProperty.subscribe { newItem -\u0026gt; selectionModel.select(newItem) } } If you think about it, you\u0026rsquo;ll realize that triggering the subscription on one Property due to a change in the other won\u0026rsquo;t cause an infinite loop because the result of that subscription will bring the two values in sync and won\u0026rsquo;t trigger the second subscription.\nYou can now see that this screen doesn\u0026rsquo;t even need to know about the domain objects Manifold and Distance any more. These elements - if they exist at all - are created somewhere else and then presumably used to create Manifold and Distance presentation data objects.\n\u0026ldquo;Drag \u0026rsquo;n Drop\u0026rdquo; File Import # Right near the top of the FXML Controller, there is some code that initialized the \u0026ldquo;root\u0026rdquo; of the Scene as the destination for a drag \u0026rsquo;n drop operation to load a configuration file. The idea being, I assume, that you can just drag a file from a file manager application into this layout and it will load it in if it is, in fact, a configuration file.\nI wrestled with the idea of putting the EventHandlers for drag and drop into the layout itself, but then realized that I\u0026rsquo;d have to supply an action handler for this to the ViewBuilder from the Controller. The end result would be that I would have a File object floating around in my layout code, even if indirectly. This part bothered me.\nI came to the conclusion that the drag and drop didn\u0026rsquo;t have anything to do with the layout as a layout. This would be different if there was a box in the layout that said \u0026ldquo;Drop Files Here\u0026rdquo; and only that box would respond to the drag and drop operation. In this situation, we don\u0026rsquo;t have anything like this, and the entire layout can just be considered as a Node (in this case a Region, but we only care about it as a subclass of Node) and dealt with from the outside.\nThis means that it makes sense to add the drag and drop handling as a decorator onto the layout directly from the Controller. Now, we don\u0026rsquo;t have to pass any handlers over to the ViewBuilder, since it isn\u0026rsquo;t involved. In fact, neither the ViewBuilder or the layout itself has any knowledge that it is a drag and drop destination.\nIn the Controller, the getView() method now looks like this:\nfun getView(): Region = viewBuilder.build() asFileDrop { loadUmap(it[0]) } It\u0026rsquo;s worth looking at the WidgetsFX implementation of this, since it is a great example of how the boilerplate can be stripped out of your application code:\ninfix fun \u0026lt;T : Node\u0026gt; T.asFileDrop(handler: (List\u0026lt;File\u0026gt;) -\u0026gt; Unit): T = apply { addEventHandler(DragEvent.DRAG_OVER) { event -\u0026gt; event.acceptTransferModes(TransferMode.COPY) } addEventHandler(DragEvent.DRAG_DROPPED) { event -\u0026gt; with (event.dragboard) { if (hasFiles()) { handler.invoke(files) } } } } Everything in here is boilerplate and every time you implement this you would need those 11 lines of code. But now it just becomes asFileDrop {do something}.\nThe Large Number of Action Handlers # There\u0026rsquo;s a lot of Buttons in this screen, and all of them trigger some kind of action within the application logic.\n{% include notice_question type=\u0026ldquo;primary\u0026rdquo; content = \u0026ldquo;How do we provide all these action handlers to the View?\n\u0026rdquo; %}\nThe first thing to understand is that the Reactive nature of the new design means that all of the data that might be relevant to any action is already represented in the Model and is always fully up to date.\nThis means that no action handler ever has to supply data from the GUI. The only information that is required is which Button was clicked.\n{: .notice\u0026ndash;primary :}\nThe few actions that need to open FileChoosers need to pass the Window (Stage) that triggered it as FileChooser needs this to work. I chose to implement these such that the Button would grab this element as part of its EventHandler that runs at click-time. These actions do need to pass that element back to the action handler.\nTwo Enums were created, one for the FileChooser operations, called FileOperation and the other for all the other operations, called GeneralOperation. \u0026ldquo;Operation\u0026rdquo; seems less likely to get confused with \u0026ldquo;Action\u0026rdquo;. Naming things is hard! The action handler is a Consumer\u0026lt;GeneralOperation\u0026gt;, or in Kotlin notation (GeneralOperation) -\u0026gt; Unit, and each Button will call the handler\u0026rsquo;s invoke(GeneralOperation) method, passing the appropriate GeneralOperation value to it.\nIt is the Controller\u0026rsquo;s job to define this action handler and provide it to the ViewBuilder. Unless there is some thread handling required in the nature of any of these actions, the Controller has no business getting involved in the execution of these actions, which is the domain of the Interactor. This action handler is essential a \u0026ldquo;dispatch routine\u0026rdquo;, which invokes an appropriate Interactor method for each operation type via the Kotlin version of switch:\nprivate fun generalOperation(operation: GeneralOperation) { when (operation) { GeneralOperation.BUILD_CLUSTER -\u0026gt; interactor.buildCluster() GeneralOperation.GENERATE -\u0026gt; interactor.generate() GeneralOperation.CLEAR_ALL -\u0026gt; interactor.clearAll() GeneralOperation.EXPORT_ALL -\u0026gt; interactor.exportAll() GeneralOperation.CLEAR_DISTANCES -\u0026gt; interactor.clearDistances() GeneralOperation.PROJECT -\u0026gt; interactor.project() GeneralOperation.EXPORT_MATRIX -\u0026gt; interactor.exportMatrix() GeneralOperation.SAVE_PROJECTIONS -\u0026gt; interactor.saveProjections() GeneralOperation.RUN_PCA -\u0026gt; interactor.runPca() } } For all practical purposes, most of these operations do not have any real application logic associated with them that resides inside of this MVCI framework. They instead are triggering actions elsewhere in the application. We\u0026rsquo;ll look at that next\u0026hellip;\nConnecting to the Rest of the Application # There is a SharedElements class which contains JavaFX Observable objects that are supplied by whatever element of the overall application contains this screen. This is the main source of coupling to the rest of the application\u0026rsquo;s GUI. Sharing these data elements has the magic effect of making all the Event firing and EventHandlers in the original version totally redundant.\nLet\u0026rsquo;s look at how this works\u0026hellip;\nIn a situation where this screen is part of a larger application, whatever GUI element of that application that \u0026ldquo;owns\u0026rdquo; this screen would pass a SharedElements object to the Controller via its constructor. This SharedElements object is then passed to the Model via its constructor. At this point, the Controller\u0026rsquo;s involvement with the SharedElements is done.\nSharedElements is a set of Properties which are defined externally. These are used to instantiate the Property fields in the Model. Like this:\nval showWireFrame: BooleanProperty = sharedElements.showWireFrame val showControlPoints: BooleanProperty = sharedElements.showControlPoints It\u0026rsquo;s important to note that since these elements are incorporated into the Model, just like any other elements, the fact that these are Properties defined externally is never exposed to the View or the Interactor. Any changes to the manner in which these are implemented, connected or related to the Properties in the Model are never going to ripple through to be changes in the View or the Interactor.\nIn a similar manner an object of the class SharedFunctions is passed to the constructor of the Controller, which calls it externalFunctions. This is, in turn, passed to the Interactor via its constructor. At this point, the Controller\u0026rsquo;s involvement with SharedFunctions is complete.\nThe Interactor then invokes these external functions as part of the application logic related to performing various actions related to Button clicks in the View.\nOn the surface this seem very round-about but it actually isolates the coupling nicely. The View has a Button that triggers an EventHandler. That EventHandler invokes an action Consumer that is defined by the Controller. That Consumer invokes an corresponding method in the Interactor which, in turn, invokes a Runnable provided to the Interactor from outside the MVCI construct through the Controller.\nInformation about what these pieces do is available on a strict \u0026ldquo;need to know\u0026rdquo; basis throughout the framework. There is an Enum which is shared between the Controller and the ViewBuilder that defines what operations can be invoked from the View, but the View has no idea what those operaitons do. The Controller knows how to invoke corresponding methods in the Interactor, but has no idea what those methods do. The Interactor knows which external operations to call, but it has no idea what they do. Finally, the Controller passes the list external operation handlers to the Interactor, but doesn\u0026rsquo;t know what they are.\nCounting Lines of Code/FXML # I\u0026rsquo;m not generally a fan of counting code, but it can be an indicator of the complexity, readability and maintainability of a system.\nLet\u0026rsquo;s look at the original:\nElement Lines FXML 462 FXML Controller 752 ManifoldListItem 76 DistanceListItem 55 Manifold 130 Distance 190 Total 1665 Now, let\u0026rsquo;s look at the hand-coded version:\nElement Lines Model 60 Controller 48 Interactor 80 ViewBuilder 230 ManifoldListCell 21 DistanceListCell 24 SharedElements 12 SharedFunctions 12 Total 487 The hand-coded version is less than 1/3 the amount of code in the original version - although I\u0026rsquo;m not sure how compare FXML to lines of code. In any event, the entire hand-coded version is only slightly larger than the FXML file itself.\nIs it Easier to Understand? # I always feel that the most common use case for someone performing maintenance or enhancement to a layout is going to start out by looking at the actual running screen. Then they are going to want to get a feel for how some specific section of the layout is designed.\nIn this case, we have a bunch of Tabs and the programmer is probably going to want to drill down into the code for a specific Tab.\nLet\u0026rsquo;s take a look at the top of the layout code, were the root elements are defined:\noverride fun build(): Region = TabPane( createUmapTab(), createPcaTab(), createDistancesTab(), createHullTab() ) withClosingPolicy TabPane.TabClosingPolicy.UNAVAILABLE private fun createPcaTab() = Tab(\u0026#34;PCA\u0026#34;) withContents (VBox(10.0, pcaGridPane(), pcaButtonBox()) padWith 25.0) private fun createUmapTab() = Tab(\u0026#34;UMAP\u0026#34;) withContents VBox(10.0) .padWith(25.0) .addChild(umapGridPane()) .addChild(umapDistanceThresholdBox()) .addChild(umapControlBox()) private fun createDistancesTab() = Tab(\u0026#34;Distances\u0026#34;) withContents HBox(10.0, distanceLeft(), distanceRight()) private fun createHullTab() = Tab(\u0026#34;Hull Geometry\u0026#34;) withContents BorderPane() .withTop(hullTop()) .withLeft(manifoldPropertyBox()) .withCenter(manifoldBox()) We can see right away that the entirety of the layout is a single TabPane with 4 Tabs that cannot be closed. We have a short builder method for each Tab with an name that mirrors the title of each Tab. No matter which Tab you are interested in you can quickly find it, without even having to scroll down through the code.\nWe can quickly get an idea about the structure of the contents of these Tabs. Two are VBoxes, one is an HBox and the other is a BorderPane. There are builders for every element contained in these Regions in the Tabs, and we can click-through on them to get to them.\nYou can also see here that all of the configuration elements such setPadding() have been implemented as extension decorator functions that have also been implemented as \u0026ldquo;infix\u0026rdquo; functions. This means that they can be used without the . and () and can, in some cases, increase readability.\nIn some cases, it looks cleaner if the dot notation is used instead. This allows the decorators to be stacked vertically when they start to add up. However, when the composition is trivial, then the infix notation keeps the coding trivial. Compare the \u0026ldquo;PCA\u0026rdquo; Tab to the \u0026ldquo;UMAP\u0026rdquo; Tab.\nI\u0026rsquo;ve tried to avoid naming the builders with positional names whenever possible. However, I really don\u0026rsquo;t know what this application does, so it was hard to guess at good names for some builders. I gave up with the builder for the layout in BorderPane.top in the \u0026ldquo;Hull\u0026rdquo; Tab, and I just called it hullTop().\nI find that GridPanes are always clumsy to deal with, no matter what, and the row/column locations never jump out at you when scanning the code. However, you can organize the code to make it easier to find things. I tried this with the \u0026ldquo;UMAP\u0026rdquo; GridPane:\nprivate fun umapGridPane() = GridPane().apply { columnConstraints += ColumnConstraints(10.0, 200.0, 288.0).apply { hgrow = Priority.SOMETIMES } columnConstraints += ColumnConstraints(10.0, 200.0, 380.0).apply { hgrow = Priority.SOMETIMES } addRowConstraints(10, stdRowConst) umapSpinnerColumn() umapSliderColumn() } private fun GridPane.umapSliderColumn() { stackedSlider(\u0026#34;Repulsion Strength\u0026#34;, model.repulsionStr, 0.0, 2.0, 0.1, 1, 0) ticksOn true addStyle \u0026#34;std-slider\u0026#34; stackedSlider(\u0026#34;Minimum Distance\u0026#34;, model.minimumDistance, 0.0, 0.6, 0.1, 1, 2) ticksOn true addStyle \u0026#34;std-slider\u0026#34; stackedSlider(\u0026#34;Spread\u0026#34;, model.spread, 0.5, 1.5, 0.1, 1, 4) ticksOn true addStyle \u0026#34;std-slider\u0026#34; stackedSlider(\u0026#34;Op Mix Ration\u0026#34;, model.opMixRatio, 0.0, 1.0, 0.1, 1, 6) ticksOn true addStyle \u0026#34;std-slider\u0026#34; stackedSlider(\u0026#34;Target Weight\u0026#34;, model.targetWeight, 0.0, 1.0, 0.1, 1, 8) ticksOn true addStyle \u0026#34;std-slider\u0026#34; } private fun GridPane.umapSpinnerColumn() { stackedIntSpinner(\u0026#34;Number of Components\u0026#34;, model.numberOfComponents, 2, 5, 1, 0, 0) addStyle \u0026#34;std-spinner\u0026#34; stackedIntSpinner(\u0026#34;Number of Epochs\u0026#34;, model.numberOfEpochs, 25, 500, 25, 0, 2) addStyle \u0026#34;std-spinner\u0026#34; stackedIntSpinner(\u0026#34;Nearest Neighbours\u0026#34;, model.nearestNeighbour, 5, 500, 5, 0, 4) addStyle \u0026#34;std-spinner\u0026#34; stackedIntSpinner(\u0026#34;Negative Sample Rate\u0026#34;, model.negativeSampleRate, 1, 250, 1, 0, 6) addStyle \u0026#34;std-spinner\u0026#34; stackedIntSpinner(\u0026#34;Local Connectivity\u0026#34;, model.localConnectivity, 1, 250, 1, 0, 8) addStyle \u0026#34;std-spinner\u0026#34; } This GridPane has two columns, the one on the right has a bunch of Sliders with their Labels, and the one on the left has Spinners and their Labels. In both columns, the Label sits on the row above its corresponding input Control. It\u0026rsquo;s now easy to see the structure of the GridPane at a glance. In contrast, translating these GridPanes from the FXML took more time than any other part because you couldn\u0026rsquo;t just look at it quickly and understand the structure.\nIt\u0026rsquo;s not clear when you see the code in these articles that the IDE that I use (Intellij IDEA) provides a lot of on-screen information that\u0026rsquo;s not seen here. For instance, this is what I see with umapSliderColumn():\nFrom here it is clear what all of those parameters do.\nOne further thing, which I think contributes greatly to the \u0026ldquo;easier to understand\u0026rdquo; aspect of this discussion. You can see that ALL of the parameters related to these input Controls are included here in these 10 method calls. There\u0026rsquo;s no need to go running off somewhere else see how one of the Spinners is configured. Each one is also bi-directionally bound to a Property field in the Model, so there\u0026rsquo;s no need to mess about with initial values either - as that is handled in the Model or the Interactor.\nThe New Code # Oh, wow! That\u0026rsquo;s a lot of discussion and preamble, and not a lot of coding. Let\u0026rsquo;s take a look at the completed redesign.\nThe ViewBuilder # This is the bulk of the code\u0026hellip;\nclass FromFxmlViewBuilder( private val model: FromFxmlModel, private val fileOp: (Window, FileOperation) -\u0026gt; Unit, private val genOp: (GeneralOperation) -\u0026gt; Unit ) : Builder\u0026lt;Region\u0026gt; { private val stdRowConst = RowConstraints().apply { minHeight = 10.0 prefHeight = 30.0 vgrow = Priority.NEVER } override fun build(): Region = TabPane(createUmapTab(), createPcaTab(), createDistancesTab(), createHullTab()) .withClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE) private fun createPcaTab() = Tab(\u0026#34;PCA\u0026#34;) withContents (VBox(10.0, pcaGridPane(), pcaButtonBox()) padWith 25.0) private fun createUmapTab() = Tab(\u0026#34;UMAP\u0026#34;) .withContents(VBox(10.0, umapGridPane(), umapDistanceThresholdBox(), umapControlBox()).padWith(25.0)) private fun createDistancesTab() = Tab(\u0026#34;Distances\u0026#34;) withContents HBox(10.0, distanceLeft(), distanceRight()) private fun createHullTab() = Tab(\u0026#34;Hull Geometry\u0026#34;) withContents BorderPane() .withTop(hullTop()) .withLeft(manifoldPropertyBox()) .withCenter(manifoldBox()) private fun umapGridPane() = GridPane().apply { columnConstraints += ColumnConstraints(10.0, 200.0, 288.0).apply { hgrow = Priority.SOMETIMES } columnConstraints += ColumnConstraints(10.0, 200.0, 380.0).apply { hgrow = Priority.SOMETIMES } addRowConstraints(10, stdRowConst) umapSpinnerColumn() umapSliderColumn() } private fun GridPane.umapSliderColumn() { stackedSlider(\u0026#34;Repulsion Strength\u0026#34;, model.repulsionStr, 0.0, 2.0, 0.1, 1, 0) ticksOn true addStyle \u0026#34;std-slider\u0026#34; stackedSlider(\u0026#34;Minimum Distance\u0026#34;, model.minimumDistance, 0.0, 0.6, 0.1, 1, 2) ticksOn true addStyle \u0026#34;std-slider\u0026#34; stackedSlider(\u0026#34;Spread\u0026#34;, model.spread, 0.5, 1.5, 0.1, 1, 4) ticksOn true addStyle \u0026#34;std-slider\u0026#34; stackedSlider(\u0026#34;Op Mix Ration\u0026#34;, model.opMixRatio, 0.0, 1.0, 0.1, 1, 6) ticksOn true addStyle \u0026#34;std-slider\u0026#34; stackedSlider(\u0026#34;Target Weight\u0026#34;, model.targetWeight, 0.0, 1.0, 0.1, 1, 8) ticksOn true addStyle \u0026#34;std-slider\u0026#34; } private fun GridPane.umapSpinnerColumn() { stackedIntSpinner(\u0026#34;Number of Components\u0026#34;, model.numberOfComponents, 2, 5, 1, 0, 0) addStyle \u0026#34;std-spinner\u0026#34; stackedIntSpinner(\u0026#34;Number of Epochs\u0026#34;, model.numberOfEpochs, 25, 500, 25, 0, 2) addStyle \u0026#34;std-spinner\u0026#34; stackedIntSpinner(\u0026#34;Nearest Neighbours\u0026#34;, model.nearestNeighbour, 5, 500, 5, 0, 4) addStyle \u0026#34;std-spinner\u0026#34; stackedIntSpinner(\u0026#34;Negative Sample Rate\u0026#34;, model.negativeSampleRate, 1, 250, 1, 0, 6) addStyle \u0026#34;std-spinner\u0026#34; stackedIntSpinner(\u0026#34;Local Connectivity\u0026#34;, model.localConnectivity, 1, 250, 1, 0, 8) addStyle \u0026#34;std-spinner\u0026#34; } private fun umapDistanceThresholdBox() = HBox(10.0).apply { alignment = Pos.TOP_CENTER children += VBox(10.0, promptOf(\u0026#34;Distance Metric\u0026#34;), ChoiceBox(generateDefaultMetrics()).firstSelected()) children += VBox( 10.0, promptOf(\u0026#34;Threshold (if applicable)\u0026#34;), doubleSpinnerOf(0.01, 1.0, \u0026#34;standard-spinner\u0026#34;, model.threshold.asObject()).setStep(0.01) ) } private fun umapControlBox() = HBox(15.0, umapConfigButtonBox(), umapHyperBox(), umapRunExportButtonBox()) private fun umapConfigButtonBox() = VBox(10.0).apply { children += buttonOf(\u0026#34;Load New Config\u0026#34;) { println(\u0026#34;Hello\u0026#34;) } addStyle \u0026#34;standard-button\u0026#34; children += buttonOf(\u0026#34;Save Current Config\u0026#34;) { fileOp.invoke(this.scene.window, FileOperation.UMAP_SAVE) } .addStyle(\u0026#34;standard-button\u0026#34;) } private fun umapHyperBox() = VBox(10.0).apply { with(ToggleGroup()) { addChild(RadioButton(\u0026#34;Use Hypersurface\u0026#34;) inToggleGroup this) addChild(RadioButton(\u0026#34;Use Hyperspace\u0026#34;) inToggleGroup this setSelected true) } addChild(CheckBox(\u0026#34;Progress Output\u0026#34;)) } private fun umapRunExportButtonBox() = VBox( 10.0, buttonOf(\u0026#34;Run UMAP\u0026#34;) { genOp.invoke(GeneralOperation.PROJECT) } addStyle \u0026#34;standard-button\u0026#34;, buttonOf(\u0026#34;Export TMatrix\u0026#34;) { genOp.invoke(GeneralOperation.EXPORT_MATRIX) } addStyle \u0026#34;standard-button\u0026#34;, buttonOf(\u0026#34;Export Projections\u0026#34;) { genOp.invoke(GeneralOperation.SAVE_PROJECTIONS) } addStyle \u0026#34;standard-button\u0026#34; ) private fun pcaButtonBox() = HBox( 15.0, buttonOf(\u0026#34;Project Data\u0026#34;) { genOp.invoke(GeneralOperation.RUN_PCA) } addStyle \u0026#34;standard-button\u0026#34;, buttonOf(\u0026#34;Export Projections\u0026#34;) { genOp.invoke(GeneralOperation.SAVE_PROJECTIONS) } addStyle \u0026#34;standard-button\u0026#34; ) alignTo Pos.CENTER private fun pcaGridPane() = GridPane().apply { columnConstraints += ColumnConstraints(10.0, 200.0, 288.0).apply { hgrow = Priority.SOMETIMES } columnConstraints += ColumnConstraints(10.0, 200.0, 380.0).apply { hgrow = Priority.SOMETIMES } addRowConstraints(10, stdRowConst) pcaLeftColumn() pcaRightColumn() } private fun GridPane.pcaRightColumn() { add(promptOf(\u0026#34;Component Analysis Type\u0026#34;), 1, 0) with(ToggleGroup()) { add(radioButtonOf(\u0026#34;PCA (EigenValue)\u0026#34;, model.analysisMethodPca, this, \u0026#34;std-radio\u0026#34;), 1, 1) add( radioButtonOf(\u0026#34;Singular Value Decomposition\u0026#34;, model.analysisMethodSvd, this, \u0026#34;std-radio\u0026#34;), 1, 2 ) } add(promptOf(\u0026#34;Input Data Source\u0026#34;), 1, 3) with(ToggleGroup()) { add(radioButtonOf(\u0026#34;Use Hypersurface\u0026#34;, model.dummyBoolean1, this, \u0026#34;std-radio\u0026#34;), 1, 4) add(radioButtonOf(\u0026#34;Use Hyperspace\u0026#34;, model.dummyBoolean2, this, \u0026#34;std-radio\u0026#34;), 1, 5) } } private fun GridPane.pcaLeftColumn() { stackedIntSpinner(\u0026#34;Number of Components\u0026#34;, model.numberOfPcaComponents, 2, 5, 1, 0, 0) addStyle \u0026#34;std-spinner\u0026#34; add(checkBoxOf(\u0026#34;Enabled Ranged Fitting (Experimental)\u0026#34;, model.rangeFitting, \u0026#34;std-checkbox\u0026#34;), 0, 2) stackedIntSpinner(\u0026#34;Fit Start Index\u0026#34;, model.fitStartIndex, 0, 500, 5, 0, 3) .bindDisable(model.rangeFitting.not()) addStyle \u0026#34;std-spinner\u0026#34; stackedIntSpinner(\u0026#34;Fit End Index\u0026#34;, model.fitEndIndex, 5, 2000, 5, 0, 5) .bindDisable(model.rangeFitting.not()) addStyle \u0026#34;std-spinner\u0026#34; stackedIntSpinner(\u0026#34;Output Scaling Factor\u0026#34;, model.pcaScalingFactor, 1, 1000, 0, 10, 7) addStyle \u0026#34;std-spinner\u0026#34; } private fun distanceLeft() = VBox(10.0).apply { padding = Insets(25.0) children += h3Of(\u0026#34;Distance Metric\u0026#34;) children += dataOf(StringExpression.stringExpression(model.selectedDistance.flatMap { it.metric } .orElse(\u0026#34;Select Distance\u0026#34;))) with(ToggleGroup()) { children += radioButtonOf(\u0026#34;Point to Point\u0026#34;, model.pointToPoint, this, \u0026#34;std-radio\u0026#34;) children += radioButtonOf(\u0026#34;Point to Group\u0026#34;, model.pointToGroup, this, \u0026#34;std-radio\u0026#34;) } children += h3Of(\u0026#34;Connector Thickness\u0026#34;) children += intSpinnerOf(1, 50, \u0026#34;standard-spinner\u0026#34;, model.connectorThickness.asObject()) .setStep(1) .bindDisable(model.selectedDistance.isNull) .withInitialDelay(Duration.millis(500.0)) .withRepeatDelay(Duration.millis(500.0)) children += promptOf(\u0026#34;Connector Colour\u0026#34;) children += ColorPicker().apply { promptText = \u0026#34;Change the colour of the 3D connector\u0026#34; valueProperty().bindBidirectional(model.connectorColour) bindDisable(model.selectedDistance.isNull) } } private fun distanceRight() = VBox(10.0).apply { children += HBox( 10.0, h3Of(\u0026#34;Collected Distances\u0026#34;), buttonOf(\u0026#34;Clear All\u0026#34;) { genOp.invoke(GeneralOperation.CLEAR_DISTANCES) }) alignTo Pos.CENTER children += (ListView\u0026lt;Distance\u0026gt;() withItems model.distanceList withCellFactory Callback { DistanceListCell() } connectSelection model.selectedDistance).apply { model.externallySelectedDistance.subscribe { newValue -\u0026gt; this.selectionModel.select(newValue) } } } private fun manifoldPropertyBox(): Region = VBox(10.0).apply { children += promptOf(\u0026#34;Selected Manifold Properties\u0026#34;) children += titledPaneOf(\u0026#34;Material\u0026#34;) { TwoColumnGridPane().addColorPickerRow(\u0026#34;Diffuse Colour\u0026#34;, model.manifoldDiffuseColour, \u0026#34;std-color-picker\u0026#34;) .addColorPickerRow(\u0026#34;Wire Mesh Colour\u0026#34;, model.manifoldWireMeshColour, \u0026#34;std-color-picker\u0026#34;) .addColorPickerRow(\u0026#34;Specular Colour\u0026#34;, model.manifoldSpecularColour, \u0026#34;std-color-picker\u0026#34;) } withCollapsable false children += meshViewPane() } private fun meshViewPane() = titledPaneOf(\u0026#34;MeshView\u0026#34;) { VBox(5.0).apply { children += radioButtonHBox( \u0026#34;Cull Face\u0026#34;, listOf( Pair(\u0026#34;Front\u0026#34;, model.frontCullFace), Pair(\u0026#34;Back\u0026#34;, model.backCullFace), Pair(\u0026#34;None\u0026#34;, model.noneCullFace) ), 5.0, \u0026#34;std-radio-button\u0026#34; ) children += radioButtonHBox( \u0026#34;Draw Mode\u0026#34;, listOf(Pair(\u0026#34;Fill\u0026#34;, model.fillDrawMode), Pair(\u0026#34;Lines\u0026#34;, model.linesDrawMode)), 5.0, \u0026#34;std-radio-button\u0026#34; ) children += HBox( 10.0, checkBoxOf(\u0026#34;Show Wire Frame\u0026#34;, model.showWireFrame, \u0026#34;std-checkbox\u0026#34;), checkBoxOf(\u0026#34;Show Control Points\u0026#34;, model.showControlPoints, \u0026#34;std-checkbox\u0026#34;) ) } } withCollapsable false private fun hullTop(): Region = HBox(10.0).apply { padWith(10.0) children += hullTopGridPane() children += VBox( 20.0, buttonOf(\u0026#34;Generate\u0026#34;) { genOp.invoke(GeneralOperation.GENERATE) }, buttonOf(\u0026#34;Cluster Tools\u0026#34;) { genOp.invoke(GeneralOperation.BUILD_CLUSTER) } ) } private fun hullTopGridPane() = TwoColumnGridPane() .addRadioButtonHBoxRow( \u0026#34;Point Set\u0026#34;, listOf(Pair(\u0026#34;Visible\u0026#34;, model.useVisible), Pair(\u0026#34;All\u0026#34;, model.useAll)), 5.0, \u0026#34;std-radio-button\u0026#34; ) .addRow(\u0026#34;Distance Tolerance\u0026#34;) { HBox( 10.0, checkBoxOf(\u0026#34;Auto\u0026#34;, model.toleranceAuto, \u0026#34;std-checkbox\u0026#34;), doubleSpinnerOf(0.1, 1.0, \u0026#34;std-spinner\u0026#34;, model.toleranceManual.asObject()).setStep(0.1) .bindDisable(model.toleranceAuto) ) } .addChoiceBoxRow(\u0026#34;Find by Label\u0026#34;, model.factorLabelList, model.selectedFactorLabel, \u0026#34;std-choice-box\u0026#34;, true) private fun manifoldBox(): Region = VBox(5.0).apply { children += h3Of(\u0026#34;Generated Manifolds\u0026#34;) children += HBox(20.0).apply { children += buttonOf(\u0026#34;Clear All\u0026#34;) { genOp.invoke(GeneralOperation.CLEAR_ALL) } children += buttonOf(\u0026#34;Export All\u0026#34;) { genOp.invoke(GeneralOperation.EXPORT_ALL) } } children += ListView\u0026lt;Manifold\u0026gt;() withItems model.manifoldList withCellFactory Callback { ManifoldListCell() } connectSelection model.selectedManifold } } One of the first things you should notice is that except for the container classes, none of the Nodes are instantiated directly using their constructors. All of them are instantiated via builder methods of some sort, and those builders are generic enough that they are included in WidgetsFX.\n{% include notice type=\u0026ldquo;primary\u0026rdquo; content = \u0026ldquo;This points out one of the biggest problems with the standard JavaFX library - a lack of constructors that allow a parameter for value binding.\u0026rdquo; %}\nFor those layout classes, I\u0026rsquo;ve used three standard techniques for populating them:\nProviding the children as constructor parameters. Using getChildren().add() via children += inside .apply{}. Using the extension function Pane.addChild() In practice, I found that Pane.addChild() outside of apply{} was no better than just including the children as constructor parameters. It\u0026rsquo;s also not clear if Pane.addChild() is any clearer than children += inside of an apply{} block.\nThere are a fair number of GridPanes in this layout. GridPane is fine when there is a strict need to keep columns and rows locked together in some fashion, but that is rarely the case in this layout. Particularly in the UMAP GridPane, where the Labels and Controls are stacked in successive rows, with the Spinner inputs in one column and the Slider inputs in another column. Is there really any need to keep the elements aligned by row?\nWhile I don\u0026rsquo;t think I would use a GridPane in this case (two VBoxes in an HBox would be simpler), I did create the extension functions GridPane.stackedSlider, and GridPane.stackedIntSpinner to get the repeated elements out of the GridPane configuration.\nFor the infix decorator functions, I\u0026rsquo;ve used them as infix when only one or two functions were called, and they would fit onto a single line. When more functions were called, it was more clear to use the regular notation and stack them one per line in the code.\nI am aware that the infix notation and the extension functions are difficult to get used to at first. A couple of years ago, I would have shied away from using them and simply put all of this functionality into apply{} blocks. Today, I find the apply{} approach to be overly verbose in many cases.\nThe net result of the extension functions and builder methods is to strip virtually all of the configuration details and boilerplate out of the layout code leaving something where you can understand the effect of that configuration without obscuring the layout itself.\nI do feel that this:\nprivate fun manifoldPropertyBox(): Region = VBox(10.0).apply { children += promptOf(\u0026#34;Selected Manifold Properties\u0026#34;) children += titledPaneOf(\u0026#34;Material\u0026#34;) { TwoColumnGridPane() .addColorPickerRow(\u0026#34;Diffuse Colour\u0026#34;, model.manifoldDiffuseColour, \u0026#34;std-color-picker\u0026#34;) .addColorPickerRow(\u0026#34;Wire Mesh Colour\u0026#34;, model.manifoldWireMeshColour, \u0026#34;std-color-picker\u0026#34;) .addColorPickerRow(\u0026#34;Specular Colour\u0026#34;, model.manifoldSpecularColour, \u0026#34;std-color-picker\u0026#34;) } withCollapsable false children += meshViewPane() } is far easier to understand than:\n\u0026lt;VBox spacing=\u0026#34;10.0\u0026#34; BorderPane.alignment=\u0026#34;CENTER\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Selected Manifold Properties\u0026#34;/\u0026gt; \u0026lt;TitledPane collapsible=\u0026#34;false\u0026#34; text=\u0026#34;Material\u0026#34; VBox.vgrow=\u0026#34;ALWAYS\u0026#34;\u0026gt; \u0026lt;content\u0026gt; \u0026lt;VBox spacing=\u0026#34;5.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER_LEFT\u0026#34; spacing=\u0026#34;10.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label prefWidth=\u0026#34;125.0\u0026#34; text=\u0026#34;Diffuse Color\u0026#34;/\u0026gt; \u0026lt;ColorPicker fx:id=\u0026#34;manifoldDiffuseColorPicker\u0026#34; editable=\u0026#34;true\u0026#34; prefHeight=\u0026#34;50.0\u0026#34; prefWidth=\u0026#34;150.0\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER_LEFT\u0026#34; spacing=\u0026#34;10.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label prefWidth=\u0026#34;125.0\u0026#34; text=\u0026#34;Wire Mesh Color\u0026#34;/\u0026gt; \u0026lt;ColorPicker fx:id=\u0026#34;manifoldWireMeshColorPicker\u0026#34; editable=\u0026#34;true\u0026#34; prefHeight=\u0026#34;50.0\u0026#34; prefWidth=\u0026#34;150.0\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER_LEFT\u0026#34; spacing=\u0026#34;10.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label prefWidth=\u0026#34;125.0\u0026#34; text=\u0026#34;Specular Color\u0026#34;/\u0026gt; \u0026lt;ColorPicker fx:id=\u0026#34;manifoldSpecularColorPicker\u0026#34; editable=\u0026#34;true\u0026#34; prefHeight=\u0026#34;50.0\u0026#34; prefWidth=\u0026#34;150.0\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;/content\u0026gt; \u0026lt;/TitledPane\u0026gt; . . . \u0026lt;/VBox\u0026gt; Especially when you take into consideration the ~40 lines of code that configure these ColorPickers in the FXML Controller. In the Kotlin code, these 3 lines completely configue the ColorPickers and they are never referenced again\u0026hellip;anywhere.\nListView Cells # The original design didn\u0026rsquo;t properly handle the two ListViews properly at all. These two classes provide Cell layouts that emulate what the original code did:\nclass DistanceListCell : ListCell\u0026lt;Distance\u0026gt;() { private val label = Label() private val distanceValueLabel = Label() private val visibleCB = CheckBox(\u0026#34;Visible\u0026#34;) private val layout = HBox(5.0, visibleCB, label, distanceValueLabel) override fun updateItem(newItem: Distance?, isEmpty: Boolean) { item?.let { label.textProperty().unbind() distanceValueLabel.textProperty().unbind() visibleCB.selectedProperty().unbindBidirectional(it.visible) } super.updateItem(newItem, isEmpty) graphic = null text = null if (!isEmpty) { newItem?.let { label.textProperty().bind(it.label) visibleCB.selectedProperty().bindBidirectional(it.visible) distanceValueLabel.textProperty().bind(Bindings.concat(it.metric, \u0026#34;: \u0026#34;, it.distance.asString())) graphic = layout } } } } class ManifoldListCell : ListCell\u0026lt;Manifold\u0026gt;() { private val label = TextField().apply { focusedProperty().subscribe { newVal -\u0026gt; if (newVal) listView.selectionModel.select(item) } } private val visibleCB = CheckBox(\u0026#34;Visible\u0026#34;).apply { focusedProperty().subscribe { newVal -\u0026gt; if (newVal) listView.selectionModel.select(item) } } private val layout = HBox(5.0, visibleCB, label) override fun updateItem(newItem: Manifold?, isEmpty: Boolean) { item?.let { label.textProperty().unbindBidirectional(it.label) visibleCB.selectedProperty().unbindBidirectional(it.visible) } super.updateItem(newItem, isEmpty) graphic = null text = null if (!isEmpty) { newItem?.let { label.textProperty().bindBidirectional(it.label) visibleCB.selectedProperty().bindBidirectional(it.visible) graphic = layout } } } } The Controller # Here\u0026rsquo;s the code for the Controller:\nclass FromFxmlController(sharedElements: SharedElements, externalFunctions: SharedFunctions) { private val model = FromFxmlModel(sharedElements) private val viewBuilder = FromFxmlViewBuilder(model, this::dataOperation, this::generalOperation) private val interactor = FromFxmlInteractor(model, externalFunctions) fun getView(): Region = viewBuilder.build() asFileDrop { runStandardVoidTask({ interactor.loadUmap(it[0]) }, { interactor.completeLoadUmap() }) } private fun dataOperation(window: Window, operation: FileOperation) { when (operation) { FileOperation.UMAP_SAVE -\u0026gt; saveUmap(window) FileOperation.UMAP_LOAD -\u0026gt; chooseAndloadUmap(window) } } private fun generalOperation(operation: GeneralOperation) { when (operation) { GeneralOperation.BUILD_CLUSTER -\u0026gt; interactor.buildCluster() GeneralOperation.GENERATE -\u0026gt; interactor.generate() GeneralOperation.CLEAR_ALL -\u0026gt; interactor.clearAll() GeneralOperation.EXPORT_ALL -\u0026gt; interactor.exportAll() GeneralOperation.CLEAR_DISTANCES -\u0026gt; interactor.clearDistances() GeneralOperation.PROJECT -\u0026gt; interactor.project() GeneralOperation.EXPORT_MATRIX -\u0026gt; interactor.exportMatrix() GeneralOperation.SAVE_PROJECTIONS -\u0026gt; interactor.saveProjections() GeneralOperation.RUN_PCA -\u0026gt; interactor.runPca() } } private fun saveUmap(window: Window) { FileChooser().apply { title = \u0026#34;Choose UMAP Config file output..\u0026#34; initialFileName = \u0026#34;UmapConfig.json\u0026#34; initialDirectory = model.latestDir.value ?: File(\u0026#34;.\u0026#34;) }.showSaveDialog(window)?.let { runStandardVoidTask({ interactor.saveUmap(it) }, { interactor.completeSaveUmap() }) } } private fun chooseAndloadUmap(window: Window) { FileChooser().apply { title = \u0026#34;Choose UMAP Config to load...\u0026#34; initialDirectory = model.latestDir.value ?: File(\u0026#34;.\u0026#34;) }.showOpenDialog(window)?.let { runStandardVoidTask({ interactor.loadUmap(it) }, { interactor.completeLoadUmap() }) } } } This is pretty simple. There\u0026rsquo;s the standard instantiation of the other elements, and then two dispacth methods to handle actions triggered by the View. Additionally, we have two methods to invoke FileChooser as part of a workflow to handle the file operations.\nThe Model # Here is the Model code:\nclass FromFxmlModel(sharedElements: SharedElements) { val externallySelectedDistance: ObjectProperty\u0026lt;Distance\u0026gt; = sharedElements.selectedDistance val externallySelectedManifold: ObjectProperty\u0026lt;Manifold\u0026gt; = sharedElements.selectedManifold val toleranceManual: DoubleProperty = sharedElements.toleranceManual val numberOfComponents: IntegerProperty = SimpleIntegerProperty(3) val numberOfEpochs: IntegerProperty = SimpleIntegerProperty(200) val nearestNeighbour: IntegerProperty = SimpleIntegerProperty(15) val negativeSampleRate: IntegerProperty = SimpleIntegerProperty(5) val localConnectivity: IntegerProperty = SimpleIntegerProperty(1) val repulsionStr: DoubleProperty = SimpleDoubleProperty(1.0) val spread: DoubleProperty = SimpleDoubleProperty(1.0) val minimumDistance: DoubleProperty = SimpleDoubleProperty(0.1) val opMixRatio: DoubleProperty = SimpleDoubleProperty(0.5) val targetWeight: DoubleProperty = SimpleDoubleProperty(0.5) val threshold: DoubleProperty = SimpleDoubleProperty(0.1) val numberOfPcaComponents: IntegerProperty = SimpleIntegerProperty() val dummyBoolean1: BooleanProperty = SimpleBooleanProperty(false) val dummyBoolean2: BooleanProperty = SimpleBooleanProperty(false) val pcaScalingFactor: IntegerProperty = SimpleIntegerProperty(100) val fitStartIndex: IntegerProperty = SimpleIntegerProperty(0) val fitEndIndex: IntegerProperty = SimpleIntegerProperty(50) val rangeFitting: BooleanProperty = SimpleBooleanProperty(false) val dummyBoolean4: BooleanProperty = SimpleBooleanProperty(false) val analysisMethodSvd: BooleanProperty = SimpleBooleanProperty(false) val analysisMethodPca: BooleanProperty = SimpleBooleanProperty(false) val connectorThickness: IntegerProperty = SimpleIntegerProperty(17) val connectorColour: ObjectProperty\u0026lt;Color\u0026gt; = SimpleObjectProperty() val pointToPoint: BooleanProperty = SimpleBooleanProperty(false) val pointToGroup: BooleanProperty = SimpleBooleanProperty(false) val latestDir: ObjectProperty\u0026lt;File?\u0026gt; = SimpleObjectProperty() val manifoldDiffuseColour: ObjectProperty\u0026lt;Color\u0026gt; = SimpleObjectProperty(Color.CYAN) val manifoldWireMeshColour: ObjectProperty\u0026lt;Color\u0026gt; = SimpleObjectProperty(Color.BLACK) val manifoldSpecularColour: ObjectProperty\u0026lt;Color\u0026gt; = SimpleObjectProperty(Color.BLACK) val frontCullFace: BooleanProperty = SimpleBooleanProperty(false) val backCullFace: BooleanProperty = SimpleBooleanProperty(false) val noneCullFace: BooleanProperty = SimpleBooleanProperty(false) val fillDrawMode: BooleanProperty = SimpleBooleanProperty(false) val linesDrawMode: BooleanProperty = SimpleBooleanProperty(false) val showWireFrame: BooleanProperty = sharedElements.showWireFrame val showControlPoints: BooleanProperty = sharedElements.showControlPoints val useAll: BooleanProperty = sharedElements.useAll val useVisible: BooleanProperty = sharedElements.useVisible val toleranceAuto: BooleanProperty = SimpleBooleanProperty(false) val distanceList: ObservableList\u0026lt;Distance\u0026gt; = sharedElements.distanceList val manifoldList: ObservableList\u0026lt;Manifold\u0026gt; = sharedElements.manifoldList val factorLabelList: ObservableList\u0026lt;String\u0026gt; = FXCollections.observableArrayList() val selectedFactorLabel = sharedElements.selectedFactorLabel val selectedDistance: ObjectProperty\u0026lt;Distance\u0026gt; = SimpleObjectProperty() val selectedManifold: ObjectProperty\u0026lt;Manifold\u0026gt; = SimpleObjectProperty() init { sharedElements.manifoldCullFace.bind(Bindings.createObjectBinding({ if (frontCullFace.value) return@createObjectBinding CullFace.FRONT if (backCullFace.value) return@createObjectBinding CullFace.BACK return@createObjectBinding CullFace.NONE }, frontCullFace, backCullFace, noneCullFace)) } } This is just a POJO of JavaFX Observable classes. The fields that are tied to the external application are instantiated as references to the corresponding field in SharedElements. SharedElements.manifoldCullFace corresponds to whichever of three BooleanProperties is true, and is bound that way.\nSharedElements is not exposed to any other component of the MVCI construct, and looks like this:\nclass SharedElements { val distanceList: ObservableList\u0026lt;Distance\u0026gt; = FXCollections.observableArrayList() val manifoldList: ObservableList\u0026lt;Manifold\u0026gt; = FXCollections.observableArrayList() val selectedDistance: ObjectProperty\u0026lt;Distance\u0026gt; = SimpleObjectProperty() val selectedManifold: ObjectProperty\u0026lt;Manifold\u0026gt; = SimpleObjectProperty() val toleranceManual: DoubleProperty = SimpleDoubleProperty() val useAll: BooleanProperty = SimpleBooleanProperty(false) val useVisible: BooleanProperty = SimpleBooleanProperty(false) val manifoldCullFace: ObjectProperty\u0026lt;CullFace\u0026gt; = SimpleObjectProperty() val showWireFrame: BooleanProperty = SimpleBooleanProperty(false) val showControlPoints: BooleanProperty = SimpleBooleanProperty(false) val selectedFactorLabel: StringProperty = SimpleStringProperty() } In truth, I got fed up searching through all of the ManifoldEvents to find out what data was being passed back and forth to other parts of the application. So I\u0026rsquo;m sure that this SharedElements object is missing quite a few elements. There\u0026rsquo;s enough here to make the point, though, and without the rest of the application it doesn\u0026rsquo;t make any difference for this demonstration.\nThe Interactor # The last MVCI component is the Interactor:\nclass FromFxmlInteractor(private val model: FromFxmlModel, private val externalFunctions: SharedFunctions) { private var umapDto: UmapDto? = null init { createDummyData() model.selectedDistance.subscribe { oldValue, newValue -\u0026gt; oldValue?.let { model.connectorThickness.unbindBidirectional(it.width) model.connectorColour.unbindBidirectional(it.colour) } newValue?.let { model.connectorThickness.bindBidirectional(it.width) model.connectorColour.bindBidirectional(it.colour) } model.externallySelectedDistance.value = newValue } model.selectedManifold.subscribe { oldValue, newValue -\u0026gt; oldValue?.let { model.manifoldDiffuseColour.unbindBidirectional(it.diffuseColour) model.manifoldSpecularColour.unbindBidirectional(it.specularColour) model.manifoldWireMeshColour.unbindBidirectional(it.wireframeColour) } newValue?.let { model.manifoldDiffuseColour.bindBidirectional(it.diffuseColour) model.manifoldSpecularColour.bindBidirectional(it.specularColour) model.manifoldWireMeshColour.bindBidirectional(it.wireframeColour) } model.externallySelectedManifold.value = newValue } } private fun createDummyData() { model.distanceList.add(Distance(\u0026#34;Label 1\u0026#34;, \u0026#34;Millimetres\u0026#34;, 17.0, 8, Color.GREEN)) model.distanceList.add(Distance(\u0026#34;Label 2\u0026#34;, \u0026#34;Nanometres\u0026#34;, 22.0, 1, Color.CYAN)) model.distanceList.add(Distance(\u0026#34;Label 3\u0026#34;, \u0026#34;Millimetres\u0026#34;, 8.0, 10, Color.AZURE)) model.distanceList.add(Distance(\u0026#34;Label 4\u0026#34;, \u0026#34;Millimetres\u0026#34;, 45.0, 3, Color.RED)) model.manifoldList.add(Manifold(\u0026#34;Label 1\u0026#34;)) model.manifoldList.add(Manifold(\u0026#34;Label 2\u0026#34;)) model.manifoldList.add(Manifold(\u0026#34;Label 3\u0026#34;)) model.manifoldList.add(Manifold(\u0026#34;Label 4\u0026#34;)) } fun buildCluster() { externalFunctions.buildCluster.invoke() } fun generate() { externalFunctions.generate.invoke() } fun clearAll() { externalFunctions.clearAll.invoke() } fun exportAll() { externalFunctions.exportAll.invoke() } fun clearDistances() { externalFunctions.clearDistances.invoke() } fun project() { externalFunctions.project.invoke() } fun exportMatrix() { externalFunctions.exportMatrix.invoke() } fun saveProjections() { externalFunctions.saveProjections.invoke() } fun runPca() { externalFunctions.runPCA.invoke() } fun saveUmap(file: File) { model.latestDir.value = file } fun completeSaveUmap() {} fun loadUmap(file: File) { println(\u0026#34;Hey! Loading a file\u0026#34;) } fun completeLoadUmap() {} } The init{} section is much like a constructor, and here it creates all of the relationships between elements of the Model that would be considered \u0026ldquo;business/application logic\u0026rdquo;. In this case it is mostly dealing with the relationships between the actively selected Manifold or Distance and some of the other properties.\nThe rest of this feels much more like a skeleton than it really is. The file handling methods are just placeholders, as they would need to connect to a service of some sort which would do the heavy lifting.\nAll of the other methods, however, are pretty much in their final form. They just need to invoke the functional elements provide by ExternalFunctions, which looks like this:\nclass SharedFunctions { val buildCluster: () -\u0026gt; Unit = {} val generate: () -\u0026gt; Unit = {} val clearAll: () -\u0026gt; Unit = {} val exportAll: () -\u0026gt; Unit = {} val clearDistances: () -\u0026gt; Unit = {} val project: () -\u0026gt; Unit = {} val exportMatrix: () -\u0026gt; Unit = {} val saveProjections: () -\u0026gt; Unit = {} val runPCA: () -\u0026gt; Unit = {} } These would ordinarily be defined in some other part of the application which is actually going to do the work. These functions completely replace all of the ManifoldEvent firings in the original FXML Controller.\nConclusion # I\u0026rsquo;m not going to pretend that I understand what this project does, but my impression is that it involves really complicated and sophisticated analysis of some kind of AI processing. But when you look at this screen it\u0026rsquo;s really just a bunch of Controls and Buttons and Lists that manipulate some data and trigger some actions.\nThe original design leaks the complexity of the entire application into what should be a simple screen. You cannot change a data value without knowing how that will impact the rest of the application.\nI need to stress that the code that I\u0026rsquo;ve published here runs as a stand-alone application. It doesn\u0026rsquo;t connect to anything, but it works and can be integrated into the rest of the application simply by providing the shared data and functions in the Controller constructor.\nClearly, a lot more was done here than just replace the FXML with code, although it\u0026rsquo;s fairly clear that the layout code is much simpler than the FXML plus FXML Controller from the original.\nThe Layout # I deliberately put this project aside for a while so that I could come back to get a more objective sense of how easy it is to read and understand the 230 lines of layout code.\nOne thing that was immediately clear to me when I came back to it was that none of these Tabs have anything to do with each other except that they cohabit in the same TabPane. As such, they could all be defined in their own builders, and each one would, therefore, be a little bit easier to understand since they wouldn\u0026rsquo;t be encumbered with the code from the other Tabs.\nFurthermore, each of these Tabs could have their own, independent, MVCI structure associated with them. There could be a \u0026ldquo;master\u0026rdquo; MVCI structure associated with the TabPane itself, and its Controller could handle instantiation of all of the other MVCI structures. This would make each of the 4 separate MVCI constructs extremely simple and easy to understand.\n{% include notice type=\u0026ldquo;primary\u0026rdquo; content = \u0026ldquo;Even if a Button on one Tab required the external function it invoked to use data from another Tab this wouldn\u0026rsquo;t matter because the Button actions aren\u0026rsquo;t transferring any data - that\u0026rsquo;s already handled by the shared data elements.\u0026rdquo; %}\nUsing a Framework # I simply cannot imagine building anything like this without implementing a framework of some kind.\nOne of the things that became glaringly apparent after the conversion was that this screen, aside from some file handling, doesn\u0026rsquo;t actually do anything itself. You can see this just from looking at the Interactor. It doesn\u0026rsquo;t have much code that actually does anything. It just dispatches actions off to some other part of the application.\nCertainly, if you had written the application, or if you were very familiar with the entire application, you\u0026rsquo;d know that this screen didn\u0026rsquo;t actually do anything. But this is absolutely not clear from a casual glance at the original code.\nReactive vs Imperative Design # A much as I think the coded layout is a win compared to FXML, I think that this exercise really illustrates the wonderful simplicity that comes from implementing a Reactive design. There are literally hundreds of lines of code in the FXML Controller that just vanish away when a Reactive design is implemented.\nUsing a Reactive design also makes it trivial to connect to external elements of the application through a shared data model. This approach also greatly simplifies the understanding of the coupling between this screen and those external elements as it is all defined inside that single object.\nCoupling # Coupling in this new design is extremely controlled, and easy to understand.\nThe Model is the main source of coupling, but it also isolates as well. There\u0026rsquo;s no way for anything outside of the View to know if a Boolean value in the Model is presented to the user via a RadioButton, a ToggleButton, a CheckBox or some custom Control. But, no matter how it\u0026rsquo;s handled in the View, the Interactor can always simply deal with the Boolean value that it is bound to.\nIn a similar manner, the SharedElements is the main source of both coupling and isolation between this screen and the rest of the application.\n{% include notice type=\u0026ldquo;primary\u0026rdquo; content = \u0026ldquo;Coupling is usually the single biggest source of unecessary complexity in any application. Controlling coupling is the best way to improve code quality.\u0026rdquo; %}\nI simply cannot stress this too much. Virtually every good (or \u0026ldquo;clean\u0026rdquo;) coding technique, is designed to control and eliminate coupling as much possible. Looking at this \u0026ldquo;Trinity\u0026rdquo; project, excessive coupling is everywhere and it makes everything much more complicated than it needs to be. I\u0026rsquo;ve tried to limit coupling as much as possible in my version, and I think it is reflected in the lack of complexity.\nThe Kotlin # It\u0026rsquo;s really clear from this example just how much Kotlin lets you extract the boilerplate and verbosity out of the layout code. The infix extension decorator functions mean that you never have to instantiate any element of the layout as a variable. In most cases the instantiation, configuration, binding and addition to the layout of the Nodes is done in a single line.\nMaybe (probably?) you don\u0026rsquo;t want to learn Kotlin to do this. In Java, I think you\u0026rsquo;d have to create classic builders with the configuration elements included as decorators. But you could do a lot of this stuff that way.\nWhat I will say is that if you are looking for a tool to make layout creation and maintenance easier, you\u0026rsquo;ll get a lot more mileage out of learning Kotlin than you will by mastering SceneBuilder and FXML.\n","date":"22 July 2025","externalUrl":null,"permalink":"/javafx/techniques/nofxml/2025-07-22-fxml-to-code/","section":"JavaFX","summary":"Introduction # Recently, I posted an article titled, Should You Use FXML?. In that article, I stated that I felt that well written and organized code would always be easier to maintain than any corresponding FXML/FXML Controller would be. I did not emphasize this, as the article was intended to be a discussion about the merits and costs of using FXML more than as a “is this better than this?”, exploration.\n","title":"Converting FXML to Code","type":"javafx"},{"content":" Don\u0026rsquo;t Use FXML # ","date":"22 July 2025","externalUrl":null,"permalink":"/javafx/techniques/fxml/","section":"JavaFX","summary":"It seems that one of the initial selling points of JavaFX, back in 2014 was that you could use drag and drop to design your layouts, and this would be stored as something called FXML.  I don’t think you should use it, and you can read why right here.","title":"Don't Use FXML","type":"javafx"},{"content":"","date":"22 July 2025","externalUrl":null,"permalink":"/categories/javafx/","section":"Categories","summary":"","title":"Javafx","type":"categories"},{"content":" What\u0026rsquo;s JavaFX? # JavaFX is the modern API for creating interactive desktop applications in Java. While it doesn\u0026rsquo;t replace Swing, it represents a significant upgrade for the programmer as it uses reactive programming.\nIs it Hard to Learn? # It can be challenging. JavaFX has never been very popular, as desktop applications have largely gone out of style, so there simply isn\u0026rsquo;t a great wealth of resources out there to learn how to build applications using JavaFX. However, if you\u0026rsquo;ve had experience using frameworks like React or Jetpack Compose, which are also based on reactive principles, it should be easier for you to learn.\nDon\u0026rsquo;t use ScreenBuilder or FXML # Okay, so this is controversial and a lots of people disagree with me. I don\u0026rsquo;t use ScreenBuilder and FXML and I strongly recommend that beginners avoid it.\nWhat are these things?\n\u0026ldquo;ScreenBuilder\u0026rdquo; is the graphical, drag-and-drop, tool for creating screen layouts. The output from ScreenBuilder is a text file in an XML-like format called \u0026ldquo;FXML\u0026rdquo;. JavaFX contains objects and methods to load FXML files into your application and merge them with Java code.\nIt is much better to learn how to build your screen with code. It\u0026rsquo;s actually quite easy, and if you apply DRY (Don\u0026rsquo;t Repeat Yourself), you\u0026rsquo;ll quickly build up a library of builder and helper methods, as well as custom classes, that will strip about 90% of the boilerplate code out of your layout logic. And this is even easier if you\u0026rsquo;re using Kotlin! The resulting pure Java/Kotlin code is easier to understand and maintain than any FXML ever will be.\nReactive programming # From Wikipedia:\nReactive programming is a declarative programming paradigm concerned with data streams and the propagation of change\u0026hellip;.\nFor example, in an imperative programming setting, a := b + c would mean that a is being assigned the result of b + c in the instant the expression is evaluated, and later, the values of b and c can be changed with no effect on the value of a. On the other hand, in reactive programming, the value of a is automatically updated whenever the values of b or c change, without the program having to explicit re-execute the statement a := b + c to determine the presently assigned value of a.\nThis exactly describes how JavaFX is implemented, and how you should approach building JavaFX applications. Somehow, this seems to have been completely missed by the programming world in general, and you won\u0026rsquo;t find JavaFX included in the Wikipedia list of reactive frameworks, and no mention of the reactive nature of JavaFX is included in the Wikipedia page about JavaFX.\nReactive programming seems to be a challenge for many programmers.\nMuch of your GUI code is layout and configuration, and sprinkled in with that are code snippets that define the reactive elements. The layout code is executed immediately, but the reactive snippets are only triggered when the reactive data streams are modified.\nThe resulting program feels a bit like a Rube Goldberg machine in code, or like setting up dominoes to fall over.\nLearn More # If you want to learn how build desktop applications with JavaFX, this website has a lot of information that can help you get started. Check back often, as I plan to add a lot more content on this subject.\nThe Elements of JavaFX # First off, take a look at The Elements of JavaFX. This will give you an overview of all the things you\u0026rsquo;ll need to be familiar with in order to write good JavaFX applications. You don\u0026rsquo;t need to master them all at once before you start, but it\u0026rsquo;s a good idea to be at least aware of how all of theses things fit together.\nApplication Structure # Where do you start? What should a JavaFX application look like?\nCheck out my blog post: JavaFX Without FXML. This will give you step by step instructions for building a complete (but simple) application with data input, showing you some good practices to follow and how to keep everything clean and easy to read.\nNext, Using MVC with JavaFX, will show you how to add some structure to your application, using Model-View-Controller. MVC works really well with JavaFX, and is a natural fit to the Reactive nature of JavaFX.\n","date":"22 July 2025","externalUrl":null,"permalink":"/javafx/","section":"JavaFX","summary":"What’s JavaFX? # JavaFX is the modern API for creating interactive desktop applications in Java. While it doesn’t replace Swing, it represents a significant upgrade for the programmer as it uses reactive programming.\n","title":"JavaFX","type":"javafx"},{"content":" The JavaFX Techniques You Need to Master # Understanding Nodes and Properties and StyleSheets is one thing, but there are also techniques that you\u0026rsquo;ll need to learn in order to create JavaFX applications.\n","date":"22 July 2025","externalUrl":null,"permalink":"/javafx/techniques/","section":"JavaFX","summary":"These are the essential techniques you need to learn with JavaFX.","title":"JavaFX Techniques","type":"javafx"},{"content":" Introduction # From time to time the question about, \u0026ldquo;To FXML or not to FXML\u0026rdquo; comes up on Reddit and other social media, and I\u0026rsquo;m know as being firmly in the \u0026ldquo;Not FXML\u0026rdquo; camp. I\u0026rsquo;ve had a few people message me about this, so I thought it might be a good time to examine this topic in depth.\nFull disclosure: I haven\u0026rsquo;t used SceneBuilder and FXML since about 2015, so my only experience with it was as a beginner to JavaFX and I have no intention of ever using it again. While I\u0026rsquo;m not an expert user of FXML, I am, however, very much aware of most of its capabilities and quirks because I read about them all the time. I\u0026rsquo;ve also seen tons of FXML when looking at other programmers\u0026rsquo; projects on GitHub, and helping people with issues on-line. I wouldn\u0026rsquo;t hesitate to say that I know more about FXML and SceneBuilder than the average JavaFX beginner or intermediate programmer. I just don\u0026rsquo;t use it. True or False - Claims About FXML # There are a lot of claims about all of the wonderous benefits of using FXML. Are they true? Let\u0026rsquo;s take a look at them and see\u0026hellip;\nSeparation of Concerns # This is the claim you hear the most, from the Oracle tutorial for FXML we have this:\nFXML is an XML-based language that provides the structure for building a user interface separate from the application logic of your code. This separation of the presentation and application logic is attractive to web developers\nThere\u0026rsquo;s actually a lot to unpack in these two sentences. With FXML, we have two components, the FXML file and the FXML Controller, so any talk of \u0026ldquo;separation\u0026rdquo; has to speak to the relationship between these two elements. So let\u0026rsquo;s concentrate on that\u0026hellip;\nWhen we talk about \u0026ldquo;Separation of Concerns\u0026rdquo;, we are really talking about \u0026ldquo;coupling\u0026rdquo;, or rather, keeping low levels of coupling. And what this means is that you can make changes to one element of your application without having to make changes to another. Does FXML achieve this?\nSpecifically, for FXML, we are concerned about whether or not we can make changes to the FXML file without needing to make changes to the FXML Controller, and visa versa. What do these two components \u0026ldquo;know\u0026rdquo; about each other?\nMost FXML implementation that I see follow a basic pattern:\nEventHandler subroutines in the FXML Controller are exposed to the FXML file. Named Nodes are exposed to the FXML Controller through @FXML annotations. This is pretty easy. The FXML file needs to be able to invoke subroutines in the FXML Controller in order to actually do anything. So at a minimum, the FXML file is coupled to this aspect of the FXML Controller. You can make any changes that you like to the FXML Controller, as long as you don\u0026rsquo;t make critical changes to those subroutines which would impact the FXML file.\nThe other direction is tighter. If you want to get data in or out of any Node on the screen, you\u0026rsquo;ll need a typed reference to it declared as a field in your FXML Controller. This means that everything that isn\u0026rsquo;t purely static content in your GUI will need to be exposed to the FXML Controller. You have to have each one of those Nodes in your FXML file, and they have to be the correct type of Node or your Controller code will break. This also creates a dependency the other way, your FXML file now requires that the FXML Controller has some code that loads data into these Nodes.\nIs there some way to remove all this coupling? Obviously, no\u0026hellip; But can we do anything to make it \u0026ldquo;one way\u0026rdquo; coupling?\nIt turns out that we can. If we expose all of the elements that might generate Events that we want to handle in the FXML Controller to that Controller by giving them id\u0026rsquo;s in the FXML file, then we can configure them, including attaching EventHandlers to them, in the FXML Controller. If you do this then you don\u0026rsquo;t have any references from the FXML file to the FXML Controller.\nI haven\u0026rsquo;t looked at enough FXML systems to say definitively that this technique is never used, but\u0026hellip; I\u0026rsquo;ve never seen it used.\nWhat about in the other direction? Can we create an FXML Controller that doesn\u0026rsquo;t need any references to Nodes defined in the FXML file?\nThis turns out to be more complicated. It is possible to define data elements as Properties in your FXML Controller that are exposed to the FXML file, and can be used in bindings defined in the FXML file. The downside to this is that the FXML file is now even more tightly coupled to the FXML Controller, as it needs to know about all of the data elements available in the FXML Controller.\nExcept that this won\u0026rsquo;t work.\nFXML does not support bi-directional binding. This is something that apparently has been a \u0026ldquo;top priority\u0026rdquo; for over a decade, and it\u0026rsquo;s still not been implemented. Without bi-directional binding, you cannot update those Properties in the FXML Controller and you cannot get data out of your GUI. So this is a dead end.\nFinally, we haven\u0026rsquo;t even considered cases where you have dynamic layouts, or layouts that are dependent on data. These simply cannot be defined purely in FXML and will need code in the FXML Controller that performs this layout.\nNow, let\u0026rsquo;s go back to \u0026ldquo;Separation of Concerns\u0026rdquo;. What separation do we have?\nIn some cases, we have separation of most of the details of the actual layout, and the styling and configuration. For instance, the FXML file might be the only component that knows that a couple of Labels are displayed in a VBox or an HBox, but if those Labels have dynamic content, then the FXML Controller needs to know that they are Labels and what data needs to be stuffed into them.\nI highly doubt that this is the level of \u0026ldquo;separation\u0026rdquo; that programmers are looking for when they expect \u0026ldquo;Separation of Concerns\u0026rdquo;.\n{: .notice\u0026ndash;info}\nBut that Oracle tutorial doesn\u0026rsquo;t even seem to be talking about this, it says:\nprovides the structure for building a user interface separate from the application logic of your code\nApplication logic? This leads to the next claim about the benefits of FXML\u0026hellip;\nFXML Supports MVC # Also from that Oracle tutorial for FXML:\nFrom a Model View Controller (MVC) perspective, the FXML file that contains the description of the user interface is the view.\nIn MVC, the View isn\u0026rsquo;t just a layout, it\u0026rsquo;s a complete, independent user interface. That means it handles everything GUI related. This includes data binding and handling GUI events like resizing, mouse dragging and button clicks. When one of these events requires activity outside of the GUI, then it communicates with the Controller to initiate that activity.\nYou should understand that there are Events and there are \u0026ldquo;Actions\u0026rdquo;. Events are very specifically JavaFX GUI elements, and in FXML they are specified in the FXML file, and implemented in the FXML Controller (or they can be implemented and defined in the FXML Controller). \u0026ldquo;Actions\u0026rdquo; are a much more generic concept and aren\u0026rsquo;t necessarily GUI elements at all.\nI know that this sounds like nit-picking, but it\u0026rsquo;s an important practical consideration when talking about MVC. Let\u0026rsquo;s look at what typically happens when you click a Button that requires some business logic to be run\u0026hellip;\nThe first thing that happens is that the Button is disabled because you don\u0026rsquo;t want to be able to initiate a the action again before it completes. There may be other GUI set-up things you want to do, like clearing out some data from some Nodes, starting up a ProgressBar or putting up a \u0026ldquo;Please Wait\u0026rdquo; message. The next thing that happens is the action is run, often in a background thread. Once the action completes, the Presentation Model is updated as required. Finally, the Button is re-enabled, the ProgressBar is removed, and the \u0026ldquo;Please Wait\u0026rdquo; message is cleared. The first and last steps are 100% GUI activity and should belong solely to the View. Steps 2 and 3 are actions that should be taken by the Controller and the Model. However, if you consider FXML file alone to be the View, it has no way to actually perform steps 1 and 4. To implement steps 1 and 4, you\u0026rsquo;ll have to write code in the FXML Controller!\nWhat does this really mean?\nAs a result of the coupling - that cannot be removed - between the FXML file and the FXML Controller, these two elements have to be considered together as a single unit that comprises the MVC View! Not the MVC View and Controller.\nOnce you look at it this way, everything else makes much more sense and you don\u0026rsquo;t have the cognitive dissonance that results from treating the FXML and FXML Controller as View and MVC Controller.\nBut you don\u0026rsquo;t automatically get an MVC Controller, you\u0026rsquo;ll have to deliberately create one. We\u0026rsquo;ll look at this later.\nThis is the most dangerous of all of the false claims about FXML. That\u0026rsquo;s because, if you buy into this then you\u0026rsquo;ll actually architect your system incorrectly {: .notice\u0026ndash;warning}\nMultiple Layouts With One Controller # The idea that the layout is a self-contained element, separate from the GUI logic, suggests that it is possible to create a single FXML Controller that works with multiple FXML layouts. Also, that it\u0026rsquo;s possible to do the reverse, and create multiple FXML Controllers that all work with the same FXML layout file. Perhaps this second scenario is easier to picture. You could have a single layout for a CRUD type application, and have a different FXML Controller for \u0026ldquo;create\u0026rdquo;, \u0026ldquo;update\u0026rdquo;, \u0026ldquo;read\u0026rdquo; and \u0026ldquo;delete\u0026rdquo;.\nIn practice, I\u0026rsquo;m not sure that anyone does this, or that it would be a good idea. Essentially, you\u0026rsquo;d be coupling all of those FXML Controllers to each other through the FXML file. Meaning that if you needed to change the FXML file to accommodate some change to one of the FXML Controllers, it could impact all of the other FXML Controllers. The same thing happens in the reverse implementation where all of the FXML files are coupled through their shared FXML Controller.\nMy sense is that trying to do this makes your application more complicated, not simpler.\nBut the Layout is Separate From the Logic! # It\u0026rsquo;s important to recognize that \u0026ldquo;physically separate\u0026rdquo; and \u0026ldquo;uncoupled\u0026rdquo; are NOT the same thing. It should be clear that although the FXML file and the the FXML Controller are two distinctly separate things, they are, in fact, very, very, very tightly coupled. You literally cannot add any non-static element to your FXML file without making corresponding changes to the associated FXML Controller.\nSo what is the advantage to having the layout separate from the logic when both are still tightly coupled?\nI\u0026rsquo;m not sure that there is one.\nI guess you could argue that you can concentrate on the layout without worrying about the GUI logic. But is that actually an advantage? Personally, I prefer the exact opposite, and I\u0026rsquo;ll usually add one element at time to my layout while flushing out the support for it in the Model, Controller and Interactor as I go. In this way, my GUI is always fully operational in so far as the elements that have been implemented and any issues that arise from downstream concerns are addressed immediately. And that\u0026rsquo;s going a lot further than just building the GUI logic as I go.\nFXML`s Declarative Syntax # This is touted as a great advantage to FXML, that it has a declarative syntax with a hierarchical structure which reflects the structure of your scene graph.\nI think that this means that it is supposed to be easy to look at an FXML file and understand what it\u0026rsquo;s doing, and to quickly find the elements that you are interested in and to understand how they are configured and relate to each other.\nLet\u0026rsquo;s see if this is true\u0026hellip;\nWhile I would prefer not to include a huge FXML file, I think we need to look at one to understand this issue. So here\u0026rsquo;s the FXML file from Trinity on GitHub:\n\u0026lt;?import javafx.geometry.Insets?\u0026gt; \u0026lt;?import javafx.scene.control.*?\u0026gt; \u0026lt;?import javafx.scene.layout.*?\u0026gt; \u0026lt;AnchorPane fx:id=\u0026#34;root\u0026#34; style=\u0026#34;-fx-background-color: #00000000;\u0026#34; xmlns=\u0026#34;http://javafx.com/javafx\u0026#34; xmlns:fx=\u0026#34;http://javafx.com/fxml\u0026#34; fx:controller=\u0026#34;edu.jhuapl.trinity.javafx.controllers.ManifoldControlController\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;TabPane fx:id=\u0026#34;tabPane\u0026#34; tabClosingPolicy=\u0026#34;UNAVAILABLE\u0026#34;\u0026gt; \u0026lt;tabs\u0026gt; \u0026lt;Tab closable=\u0026#34;false\u0026#34; text=\u0026#34;UMAP\u0026#34;\u0026gt; \u0026lt;content\u0026gt; \u0026lt;BorderPane fx:id=\u0026#34;majorPane\u0026#34; minHeight=\u0026#34;200.0\u0026#34; minWidth=\u0026#34;400.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;top\u0026gt; \u0026lt;/top\u0026gt; \u0026lt;center\u0026gt; \u0026lt;GridPane hgap=\u0026#34;10.0\u0026#34; vgap=\u0026#34;5.0\u0026#34; BorderPane.alignment=\u0026#34;CENTER\u0026#34;\u0026gt; \u0026lt;columnConstraints\u0026gt; \u0026lt;ColumnConstraints hgrow=\u0026#34;SOMETIMES\u0026#34; maxWidth=\u0026#34;288.0\u0026#34; minWidth=\u0026#34;10.0\u0026#34; prefWidth=\u0026#34;206.0\u0026#34;/\u0026gt; \u0026lt;ColumnConstraints hgrow=\u0026#34;SOMETIMES\u0026#34; maxWidth=\u0026#34;380.0\u0026#34; minWidth=\u0026#34;10.0\u0026#34; prefWidth=\u0026#34;380.0\u0026#34;/\u0026gt; \u0026lt;/columnConstraints\u0026gt; \u0026lt;rowConstraints\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;/rowConstraints\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Number of Components\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;numComponentsSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Number of Epochs\u0026#34; GridPane.rowIndex=\u0026#34;2\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;numEpochsSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;3\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Nearest Neighbors\u0026#34; GridPane.rowIndex=\u0026#34;4\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Negative Sample Rate\u0026#34; GridPane.rowIndex=\u0026#34;6\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Local Connectivity\u0026#34; GridPane.rowIndex=\u0026#34;8\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;nearestNeighborsSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;5\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;negativeSampleRateSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;7\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;localConnectivitySpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;9\u0026#34;/\u0026gt; \u0026lt;HBox spacing=\u0026#34;10.0\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;10\u0026#34; GridPane.rowSpan=\u0026#34;2\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;VBox alignment=\u0026#34;CENTER_LEFT\u0026#34; spacing=\u0026#34;10.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Distance Metric\u0026#34;/\u0026gt; \u0026lt;ChoiceBox fx:id=\u0026#34;metricChoiceBox\u0026#34; prefWidth=\u0026#34;200.0\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;VBox alignment=\u0026#34;CENTER_LEFT\u0026#34; layoutX=\u0026#34;10.0\u0026#34; layoutY=\u0026#34;10.0\u0026#34; spacing=\u0026#34;10.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Threshold (if applicable)\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;thresholdSpinner\u0026#34; prefWidth=\u0026#34;150.0\u0026#34;\u0026gt; \u0026lt;editable\u0026gt;true\u0026lt;/editable\u0026gt; \u0026lt;valueFactory\u0026gt; \u0026lt;SpinnerValueFactory.DoubleSpinnerValueFactory min=\u0026#34;0.01\u0026#34; max=\u0026#34;1.0\u0026#34; initialValue=\u0026#34;0.1\u0026#34; amountToStepBy=\u0026#34;0.01\u0026#34;/\u0026gt; \u0026lt;/valueFactory\u0026gt; \u0026lt;/Spinner\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets top=\u0026#34;5.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;Label text=\u0026#34;Repulsion Strength\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Minimum Distance\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;2\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Spread\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;4\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Op Mix Ratio\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;6\u0026#34;/\u0026gt; \u0026lt;Slider fx:id=\u0026#34;repulsionSlider\u0026#34; blockIncrement=\u0026#34;0.1\u0026#34; majorTickUnit=\u0026#34;0.1\u0026#34; max=\u0026#34;2.0\u0026#34; showTickLabels=\u0026#34;true\u0026#34; showTickMarks=\u0026#34;true\u0026#34; snapToTicks=\u0026#34;true\u0026#34; value=\u0026#34;1.0\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;Slider fx:id=\u0026#34;minDistanceSlider\u0026#34; blockIncrement=\u0026#34;0.1\u0026#34; majorTickUnit=\u0026#34;0.1\u0026#34; max=\u0026#34;0.6\u0026#34; showTickLabels=\u0026#34;true\u0026#34; showTickMarks=\u0026#34;true\u0026#34; snapToTicks=\u0026#34;true\u0026#34; value=\u0026#34;0.1\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;3\u0026#34;/\u0026gt; \u0026lt;Slider fx:id=\u0026#34;spreadSlider\u0026#34; blockIncrement=\u0026#34;0.1\u0026#34; majorTickUnit=\u0026#34;0.1\u0026#34; max=\u0026#34;1.5\u0026#34; min=\u0026#34;0.5\u0026#34; showTickLabels=\u0026#34;true\u0026#34; showTickMarks=\u0026#34;true\u0026#34; snapToTicks=\u0026#34;true\u0026#34; value=\u0026#34;1.0\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;5\u0026#34;/\u0026gt; \u0026lt;Slider fx:id=\u0026#34;opMixSlider\u0026#34; blockIncrement=\u0026#34;0.1\u0026#34; majorTickUnit=\u0026#34;0.1\u0026#34; max=\u0026#34;1.0\u0026#34; showTickLabels=\u0026#34;true\u0026#34; showTickMarks=\u0026#34;true\u0026#34; snapToTicks=\u0026#34;true\u0026#34; value=\u0026#34;0.5\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;7\u0026#34;/\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER\u0026#34; spacing=\u0026#34;15.0\u0026#34; GridPane.columnSpan=\u0026#34;2147483647\u0026#34; GridPane.halignment=\u0026#34;CENTER\u0026#34; GridPane.rowIndex=\u0026#34;12\u0026#34; GridPane.rowSpan=\u0026#34;2\u0026#34; GridPane.valignment=\u0026#34;CENTER\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;VBox alignment=\u0026#34;CENTER\u0026#34; spacing=\u0026#34;10.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Button mnemonicParsing=\u0026#34;false\u0026#34; onAction=\u0026#34;#loadUmapConfig\u0026#34; prefWidth=\u0026#34;200.0\u0026#34; text=\u0026#34;Load New Config\u0026#34;/\u0026gt; \u0026lt;Button mnemonicParsing=\u0026#34;false\u0026#34; onAction=\u0026#34;#saveUmapConfig\u0026#34; prefWidth=\u0026#34;200.0\u0026#34; text=\u0026#34;Save Current Config\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;VBox alignment=\u0026#34;CENTER_LEFT\u0026#34; spacing=\u0026#34;10.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;useHypersurfaceButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; text=\u0026#34;Use Hypersurface\u0026#34;/\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;useHyperspaceButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; selected=\u0026#34;true\u0026#34; text=\u0026#34;Use Hyperspace\u0026#34;/\u0026gt; \u0026lt;CheckBox fx:id=\u0026#34;verboseCheckBox\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; selected=\u0026#34;true\u0026#34; text=\u0026#34;Progress Output\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;5.0\u0026#34; left=\u0026#34;5.0\u0026#34; right=\u0026#34;5.0\u0026#34; top=\u0026#34;5.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;VBox alignment=\u0026#34;CENTER\u0026#34; spacing=\u0026#34;10.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Button defaultButton=\u0026#34;true\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; onAction=\u0026#34;#project\u0026#34; prefWidth=\u0026#34;200.0\u0026#34; text=\u0026#34;Run UMAP\u0026#34;/\u0026gt; \u0026lt;Button mnemonicParsing=\u0026#34;false\u0026#34; onAction=\u0026#34;#exportMatrix\u0026#34; prefWidth=\u0026#34;200.0\u0026#34; text=\u0026#34;Export TMatrix\u0026#34;/\u0026gt; \u0026lt;Button mnemonicParsing=\u0026#34;false\u0026#34; onAction=\u0026#34;#saveProjections\u0026#34; prefWidth=\u0026#34;200.0\u0026#34; text=\u0026#34;Export Projections\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;2.0\u0026#34; left=\u0026#34;2.0\u0026#34; right=\u0026#34;2.0\u0026#34; top=\u0026#34;2.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;Label text=\u0026#34;Target Weight\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;8\u0026#34;/\u0026gt; \u0026lt;Slider fx:id=\u0026#34;targetWeightSlider\u0026#34; blockIncrement=\u0026#34;0.1\u0026#34; majorTickUnit=\u0026#34;0.1\u0026#34; max=\u0026#34;1.0\u0026#34; showTickLabels=\u0026#34;true\u0026#34; showTickMarks=\u0026#34;true\u0026#34; snapToTicks=\u0026#34;true\u0026#34; value=\u0026#34;0.5\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;9\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;25.0\u0026#34; left=\u0026#34;25.0\u0026#34; right=\u0026#34;25.0\u0026#34; top=\u0026#34;25.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/GridPane\u0026gt; \u0026lt;/center\u0026gt; \u0026lt;/BorderPane\u0026gt; \u0026lt;/content\u0026gt; \u0026lt;/Tab\u0026gt; \u0026lt;Tab closable=\u0026#34;false\u0026#34; text=\u0026#34;PCA\u0026#34;\u0026gt; \u0026lt;content\u0026gt; \u0026lt;BorderPane fx:id=\u0026#34;majorPane\u0026#34; minHeight=\u0026#34;200.0\u0026#34; minWidth=\u0026#34;400.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;top\u0026gt; \u0026lt;/top\u0026gt; \u0026lt;center\u0026gt; \u0026lt;GridPane hgap=\u0026#34;10.0\u0026#34; vgap=\u0026#34;5.0\u0026#34; BorderPane.alignment=\u0026#34;CENTER\u0026#34;\u0026gt; \u0026lt;columnConstraints\u0026gt; \u0026lt;ColumnConstraints hgrow=\u0026#34;SOMETIMES\u0026#34; maxWidth=\u0026#34;288.0\u0026#34; minWidth=\u0026#34;10.0\u0026#34; prefWidth=\u0026#34;200.0\u0026#34;/\u0026gt; \u0026lt;ColumnConstraints hgrow=\u0026#34;SOMETIMES\u0026#34; maxWidth=\u0026#34;380.0\u0026#34; minWidth=\u0026#34;10.0\u0026#34; prefWidth=\u0026#34;200.0\u0026#34;/\u0026gt; \u0026lt;/columnConstraints\u0026gt; \u0026lt;rowConstraints\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;/rowConstraints\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Number of Components\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;numPcaComponentsSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;HBox spacing=\u0026#34;20.0\u0026#34; GridPane.rowIndex=\u0026#34;3\u0026#34; GridPane.rowSpan=\u0026#34;2\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;VBox spacing=\u0026#34;15.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Fit Start Index\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;fitStartIndexSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;VBox spacing=\u0026#34;15.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Fit End Index\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;fitEndIndexSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;pcaRadioButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; selected=\u0026#34;true\u0026#34; text=\u0026#34;PCA (EigenValue)\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Component Analysis Type\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;svdRadioButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; text=\u0026#34;Singular Value Decomposition\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;2\u0026#34;/\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;pcaUseHypersurfaceButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; text=\u0026#34;Use Hypersurface\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;4\u0026#34;/\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;pcaUseHyperspaceButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; selected=\u0026#34;true\u0026#34; text=\u0026#34;Use Hyperspace\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;5\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Output Scaling Factor\u0026#34; GridPane.rowIndex=\u0026#34;5\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;pcaScalingSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;6\u0026#34;/\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER\u0026#34; spacing=\u0026#34;10.0\u0026#34; GridPane.columnSpan=\u0026#34;2147483647\u0026#34; GridPane.rowIndex=\u0026#34;8\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Button defaultButton=\u0026#34;true\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; onAction=\u0026#34;#runPCA\u0026#34; prefWidth=\u0026#34;175.0\u0026#34; text=\u0026#34;Project Data\u0026#34;/\u0026gt; \u0026lt;Button mnemonicParsing=\u0026#34;false\u0026#34; onAction=\u0026#34;#saveProjections\u0026#34; prefWidth=\u0026#34;175.0\u0026#34; text=\u0026#34;Export Projections\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;5.0\u0026#34; left=\u0026#34;5.0\u0026#34; right=\u0026#34;5.0\u0026#34; top=\u0026#34;5.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;Label text=\u0026#34;Input Data Source\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;3\u0026#34;/\u0026gt; \u0026lt;CheckBox fx:id=\u0026#34;rangedFittingCheckBox\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; text=\u0026#34;Enabled Ranged Fitting (Experimental)\u0026#34; GridPane.rowIndex=\u0026#34;2\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;15.0\u0026#34; left=\u0026#34;15.0\u0026#34; right=\u0026#34;15.0\u0026#34; top=\u0026#34;15.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/GridPane\u0026gt; \u0026lt;/center\u0026gt; \u0026lt;/BorderPane\u0026gt; \u0026lt;/content\u0026gt; \u0026lt;/Tab\u0026gt; \u0026lt;Tab closable=\u0026#34;false\u0026#34; text=\u0026#34;Distances\u0026#34;\u0026gt; \u0026lt;content\u0026gt; \u0026lt;BorderPane fx:id=\u0026#34;majorPane\u0026#34; minHeight=\u0026#34;200.0\u0026#34; minWidth=\u0026#34;400.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;top\u0026gt; \u0026lt;/top\u0026gt; \u0026lt;center\u0026gt; \u0026lt;GridPane hgap=\u0026#34;10.0\u0026#34; vgap=\u0026#34;5.0\u0026#34; BorderPane.alignment=\u0026#34;CENTER\u0026#34;\u0026gt; \u0026lt;columnConstraints\u0026gt; \u0026lt;ColumnConstraints hgrow=\u0026#34;SOMETIMES\u0026#34; maxWidth=\u0026#34;288.0\u0026#34; minWidth=\u0026#34;10.0\u0026#34; prefWidth=\u0026#34;206.0\u0026#34;/\u0026gt; \u0026lt;ColumnConstraints fillWidth=\u0026#34;false\u0026#34; hgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;ColumnConstraints hgrow=\u0026#34;SOMETIMES\u0026#34; maxWidth=\u0026#34;380.0\u0026#34; minWidth=\u0026#34;10.0\u0026#34; prefWidth=\u0026#34;380.0\u0026#34;/\u0026gt; \u0026lt;/columnConstraints\u0026gt; \u0026lt;rowConstraints\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;/rowConstraints\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Connector Thickness\u0026#34; GridPane.rowIndex=\u0026#34;5\u0026#34;/\u0026gt; \u0026lt;VBox alignment=\u0026#34;CENTER_LEFT\u0026#34; spacing=\u0026#34;5.0\u0026#34; GridPane.rowIndex=\u0026#34;7\u0026#34; GridPane.rowSpan=\u0026#34;2\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Connector Color\u0026#34;/\u0026gt; \u0026lt;ColorPicker fx:id=\u0026#34;connectorColorPicker\u0026#34; editable=\u0026#34;true\u0026#34; minHeight=\u0026#34;40.0\u0026#34; prefWidth=\u0026#34;200.0\u0026#34; promptText=\u0026#34;Change the color of the 3D connector\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;5.0\u0026#34; left=\u0026#34;5.0\u0026#34; right=\u0026#34;5.0\u0026#34; top=\u0026#34;5.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;connectorThicknessSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;6\u0026#34;/\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER\u0026#34; spacing=\u0026#34;20.0\u0026#34; GridPane.columnIndex=\u0026#34;2\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Collected Distances\u0026#34;/\u0026gt; \u0026lt;Button mnemonicParsing=\u0026#34;false\u0026#34; onAction=\u0026#34;#clearAllDistances\u0026#34; prefWidth=\u0026#34;150.0\u0026#34; text=\u0026#34;Clear All\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;VBox spacing=\u0026#34;5.0\u0026#34; GridPane.rowSpan=\u0026#34;2\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Distance Metric\u0026#34;/\u0026gt; \u0026lt;TextField fx:id=\u0026#34;distanceMetricTextField\u0026#34; minHeight=\u0026#34;40.0\u0026#34; promptText=\u0026#34;Select Distance From ListView\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;5.0\u0026#34; left=\u0026#34;5.0\u0026#34; right=\u0026#34;5.0\u0026#34; top=\u0026#34;5.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;ScrollPane fitToHeight=\u0026#34;true\u0026#34; fitToWidth=\u0026#34;true\u0026#34; pannable=\u0026#34;true\u0026#34; GridPane.columnIndex=\u0026#34;2\u0026#34; GridPane.rowIndex=\u0026#34;1\u0026#34; GridPane.rowSpan=\u0026#34;2147483647\u0026#34;\u0026gt; \u0026lt;content\u0026gt; \u0026lt;ListView fx:id=\u0026#34;distancesListView\u0026#34; prefHeight=\u0026#34;200.0\u0026#34; prefWidth=\u0026#34;200.0\u0026#34;\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;5.0\u0026#34; left=\u0026#34;5.0\u0026#34; right=\u0026#34;5.0\u0026#34; top=\u0026#34;5.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/ListView\u0026gt; \u0026lt;/content\u0026gt; \u0026lt;/ScrollPane\u0026gt; \u0026lt;Separator orientation=\u0026#34;VERTICAL\u0026#34; prefHeight=\u0026#34;200.0\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowSpan=\u0026#34;2147483647\u0026#34;/\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;pointToGroupRadioButton\u0026#34; disable=\u0026#34;true\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; text=\u0026#34;Point to Group\u0026#34; GridPane.rowIndex=\u0026#34;3\u0026#34;/\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;pointToPointRadioButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; selected=\u0026#34;true\u0026#34; text=\u0026#34;Point to Point\u0026#34; GridPane.rowIndex=\u0026#34;2\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;10.0\u0026#34; left=\u0026#34;10.0\u0026#34; right=\u0026#34;10.0\u0026#34; top=\u0026#34;10.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/GridPane\u0026gt; \u0026lt;/center\u0026gt; \u0026lt;/BorderPane\u0026gt; \u0026lt;/content\u0026gt; \u0026lt;/Tab\u0026gt; \u0026lt;Tab closable=\u0026#34;false\u0026#34; text=\u0026#34;Hull Geometry\u0026#34;\u0026gt; \u0026lt;content\u0026gt; \u0026lt;BorderPane\u0026gt; \u0026lt;center\u0026gt; \u0026lt;VBox spacing=\u0026#34;5.0\u0026#34; BorderPane.alignment=\u0026#34;CENTER\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER_LEFT\u0026#34; spacing=\u0026#34;20.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Generated Manifolds\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER\u0026#34; layoutX=\u0026#34;15.0\u0026#34; layoutY=\u0026#34;15.0\u0026#34; spacing=\u0026#34;20.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Button mnemonicParsing=\u0026#34;false\u0026#34; onAction=\u0026#34;#clearAll\u0026#34; prefWidth=\u0026#34;125.0\u0026#34; text=\u0026#34;Clear All\u0026#34;/\u0026gt; \u0026lt;Button layoutX=\u0026#34;100.0\u0026#34; layoutY=\u0026#34;10.0\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; onAction=\u0026#34;#exportAll\u0026#34; prefWidth=\u0026#34;125.0\u0026#34; text=\u0026#34;Export All\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;ScrollPane fitToHeight=\u0026#34;true\u0026#34; fitToWidth=\u0026#34;true\u0026#34; pannable=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;content\u0026gt; \u0026lt;ListView fx:id=\u0026#34;manifoldsListView\u0026#34;\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;5.0\u0026#34; left=\u0026#34;5.0\u0026#34; right=\u0026#34;5.0\u0026#34; top=\u0026#34;5.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/ListView\u0026gt; \u0026lt;/content\u0026gt; \u0026lt;/ScrollPane\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;5.0\u0026#34; left=\u0026#34;5.0\u0026#34; right=\u0026#34;5.0\u0026#34; top=\u0026#34;5.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;/center\u0026gt; \u0026lt;top\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER\u0026#34; BorderPane.alignment=\u0026#34;CENTER\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;GridPane maxHeight=\u0026#34;1.7976931348623157E308\u0026#34; maxWidth=\u0026#34;1.7976931348623157E308\u0026#34;\u0026gt; \u0026lt;columnConstraints\u0026gt; \u0026lt;ColumnConstraints halignment=\u0026#34;CENTER\u0026#34; hgrow=\u0026#34;ALWAYS\u0026#34; maxWidth=\u0026#34;-Infinity\u0026#34; minWidth=\u0026#34;10.0\u0026#34; prefWidth=\u0026#34;150.0\u0026#34;/\u0026gt; \u0026lt;ColumnConstraints halignment=\u0026#34;CENTER\u0026#34; hgrow=\u0026#34;ALWAYS\u0026#34; maxWidth=\u0026#34;216.0\u0026#34; minWidth=\u0026#34;10.0\u0026#34; prefWidth=\u0026#34;214.0\u0026#34;/\u0026gt; \u0026lt;ColumnConstraints halignment=\u0026#34;CENTER\u0026#34; hgrow=\u0026#34;ALWAYS\u0026#34; maxWidth=\u0026#34;145.0\u0026#34; minWidth=\u0026#34;10.0\u0026#34; prefWidth=\u0026#34;118.0\u0026#34;/\u0026gt; \u0026lt;ColumnConstraints halignment=\u0026#34;CENTER\u0026#34; hgrow=\u0026#34;ALWAYS\u0026#34; maxWidth=\u0026#34;145.0\u0026#34; minWidth=\u0026#34;10.0\u0026#34; prefWidth=\u0026#34;118.0\u0026#34;/\u0026gt; \u0026lt;/columnConstraints\u0026gt; \u0026lt;rowConstraints\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;ALWAYS\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;40.0\u0026#34; vgrow=\u0026#34;ALWAYS\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;40.0\u0026#34; vgrow=\u0026#34;ALWAYS\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; vgrow=\u0026#34;ALWAYS\u0026#34;/\u0026gt; \u0026lt;/rowConstraints\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label layoutX=\u0026#34;15.0\u0026#34; layoutY=\u0026#34;62.0\u0026#34; text=\u0026#34;Point Set\u0026#34;/\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.valignment=\u0026#34;CENTER\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;useVisibleRadioButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; prefWidth=\u0026#34;75.0\u0026#34; selected=\u0026#34;true\u0026#34; text=\u0026#34;Visible\u0026#34;/\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;useAllRadioButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; prefWidth=\u0026#34;75.0\u0026#34; text=\u0026#34;All\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;Label text=\u0026#34;Distance Tolerance\u0026#34; textAlignment=\u0026#34;CENTER\u0026#34; GridPane.rowIndex=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER\u0026#34; spacing=\u0026#34;10.0\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;1\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;CheckBox fx:id=\u0026#34;automaticCheckBox\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; selected=\u0026#34;true\u0026#34; text=\u0026#34;Auto\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;manualSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;75.0\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;Label text=\u0026#34;Find by Label\u0026#34; GridPane.rowIndex=\u0026#34;2\u0026#34;/\u0026gt; \u0026lt;ChoiceBox fx:id=\u0026#34;labelChoiceBox\u0026#34; maxWidth=\u0026#34;200.0\u0026#34; prefWidth=\u0026#34;150.0\u0026#34; GridPane.columnIndex=\u0026#34;1\u0026#34; GridPane.rowIndex=\u0026#34;2\u0026#34;/\u0026gt; \u0026lt;Separator prefWidth=\u0026#34;200.0\u0026#34; GridPane.columnSpan=\u0026#34;2147483647\u0026#34; GridPane.rowIndex=\u0026#34;3\u0026#34;/\u0026gt; \u0026lt;VBox alignment=\u0026#34;CENTER\u0026#34; spacing=\u0026#34;20.0\u0026#34; GridPane.columnIndex=\u0026#34;2\u0026#34; GridPane.columnSpan=\u0026#34;2\u0026#34; GridPane.halignment=\u0026#34;CENTER\u0026#34; GridPane.rowSpan=\u0026#34;3\u0026#34; GridPane.valignment=\u0026#34;CENTER\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Button mnemonicParsing=\u0026#34;false\u0026#34; onAction=\u0026#34;#generate\u0026#34; prefWidth=\u0026#34;175.0\u0026#34; text=\u0026#34;Generate\u0026#34;/\u0026gt; \u0026lt;Button mnemonicParsing=\u0026#34;false\u0026#34; onAction=\u0026#34;#clusterBuilder\u0026#34; prefWidth=\u0026#34;175.0\u0026#34; text=\u0026#34;Cluster Tools\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;10.0\u0026#34; left=\u0026#34;10.0\u0026#34; right=\u0026#34;10.0\u0026#34; top=\u0026#34;10.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/GridPane\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;/top\u0026gt; \u0026lt;left\u0026gt; \u0026lt;VBox spacing=\u0026#34;10.0\u0026#34; BorderPane.alignment=\u0026#34;CENTER\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label text=\u0026#34;Selected Manifold Properties\u0026#34;/\u0026gt; \u0026lt;TitledPane collapsible=\u0026#34;false\u0026#34; text=\u0026#34;Material\u0026#34; VBox.vgrow=\u0026#34;ALWAYS\u0026#34;\u0026gt; \u0026lt;content\u0026gt; \u0026lt;VBox spacing=\u0026#34;5.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER_LEFT\u0026#34; spacing=\u0026#34;10.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label prefWidth=\u0026#34;125.0\u0026#34; text=\u0026#34;Diffuse Color\u0026#34;/\u0026gt; \u0026lt;ColorPicker fx:id=\u0026#34;manifoldDiffuseColorPicker\u0026#34; editable=\u0026#34;true\u0026#34; prefHeight=\u0026#34;50.0\u0026#34; prefWidth=\u0026#34;150.0\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER_LEFT\u0026#34; spacing=\u0026#34;10.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label prefWidth=\u0026#34;125.0\u0026#34; text=\u0026#34;Wire Mesh Color\u0026#34;/\u0026gt; \u0026lt;ColorPicker fx:id=\u0026#34;manifoldWireMeshColorPicker\u0026#34; editable=\u0026#34;true\u0026#34; prefHeight=\u0026#34;50.0\u0026#34; prefWidth=\u0026#34;150.0\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER_LEFT\u0026#34; spacing=\u0026#34;10.0\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label prefWidth=\u0026#34;125.0\u0026#34; text=\u0026#34;Specular Color\u0026#34;/\u0026gt; \u0026lt;ColorPicker fx:id=\u0026#34;manifoldSpecularColorPicker\u0026#34; editable=\u0026#34;true\u0026#34; prefHeight=\u0026#34;50.0\u0026#34; prefWidth=\u0026#34;150.0\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;/content\u0026gt; \u0026lt;/TitledPane\u0026gt; \u0026lt;TitledPane collapsible=\u0026#34;false\u0026#34; layoutX=\u0026#34;10.0\u0026#34; layoutY=\u0026#34;396.0\u0026#34; text=\u0026#34;MeshView\u0026#34; VBox.vgrow=\u0026#34;ALWAYS\u0026#34;\u0026gt; \u0026lt;content\u0026gt; \u0026lt;GridPane\u0026gt; \u0026lt;columnConstraints\u0026gt; \u0026lt;ColumnConstraints hgrow=\u0026#34;SOMETIMES\u0026#34; maxWidth=\u0026#34;-Infinity\u0026#34; minWidth=\u0026#34;10.0\u0026#34; prefWidth=\u0026#34;300.0\u0026#34;/\u0026gt; \u0026lt;/columnConstraints\u0026gt; \u0026lt;rowConstraints\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;SOMETIMES\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;SOMETIMES\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;SOMETIMES\u0026#34;/\u0026gt; \u0026lt;/rowConstraints\u0026gt; \u0026lt;children\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER_LEFT\u0026#34; spacing=\u0026#34;15.0\u0026#34; GridPane.halignment=\u0026#34;CENTER\u0026#34; GridPane.valignment=\u0026#34;CENTER\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label prefWidth=\u0026#34;75.0\u0026#34; text=\u0026#34;Cull Face\u0026#34;/\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;frontCullFaceRadioButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; prefWidth=\u0026#34;70.0\u0026#34; selected=\u0026#34;true\u0026#34; text=\u0026#34;Front\u0026#34;/\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;backCullFaceRadioButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; text=\u0026#34;Back\u0026#34;/\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;noneCullFaceRadioButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; text=\u0026#34;None\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER_LEFT\u0026#34; spacing=\u0026#34;15.0\u0026#34; GridPane.halignment=\u0026#34;CENTER\u0026#34; GridPane.rowIndex=\u0026#34;1\u0026#34; GridPane.valignment=\u0026#34;CENTER\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;Label prefWidth=\u0026#34;75.0\u0026#34; text=\u0026#34;Draw Mode\u0026#34;/\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;fillDrawModeRadioButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; prefWidth=\u0026#34;70.0\u0026#34; selected=\u0026#34;true\u0026#34; text=\u0026#34;Fill\u0026#34;/\u0026gt; \u0026lt;RadioButton fx:id=\u0026#34;linesDrawModeRadioButton\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; text=\u0026#34;Lines\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;HBox alignment=\u0026#34;CENTER\u0026#34; spacing=\u0026#34;10.0\u0026#34; GridPane.rowIndex=\u0026#34;2\u0026#34;\u0026gt; \u0026lt;children\u0026gt; \u0026lt;CheckBox fx:id=\u0026#34;showWireframeCheckBox\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; selected=\u0026#34;true\u0026#34; text=\u0026#34;Show Wire Frame\u0026#34;/\u0026gt; \u0026lt;CheckBox fx:id=\u0026#34;showControlPointsCheckBox\u0026#34; mnemonicParsing=\u0026#34;false\u0026#34; selected=\u0026#34;true\u0026#34; text=\u0026#34;Show Control Points\u0026#34;/\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/HBox\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/GridPane\u0026gt; \u0026lt;/content\u0026gt; \u0026lt;/TitledPane\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;5.0\u0026#34; left=\u0026#34;5.0\u0026#34; right=\u0026#34;5.0\u0026#34; top=\u0026#34;5.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;/VBox\u0026gt; \u0026lt;/left\u0026gt; \u0026lt;/BorderPane\u0026gt; \u0026lt;/content\u0026gt; \u0026lt;/Tab\u0026gt; \u0026lt;/tabs\u0026gt; \u0026lt;/TabPane\u0026gt; \u0026lt;/children\u0026gt; \u0026lt;/AnchorPane\u0026gt; All right, maybe if you\u0026rsquo;re an FXML expert user you can look at these 462 lines of FXML and make some sense out of it at a glance\u0026hellip;but I can\u0026rsquo;t.\nI don\u0026rsquo;t really know anything about the project this came from. I don\u0026rsquo;t know if it would be considered \u0026ldquo;good\u0026rdquo; FXML or not. I don\u0026rsquo;t know if the author was an expert or a beginner. My sense is that it is fairly typical, and is probably what came out of SceneBuilder.\nWhat I can say for sure is that it\u0026rsquo;s monolithic. SceneBuilder/FXML tends to push programmers in this direction, because I see this a lot. It is possible to carve a layout up into smaller parts, but the FXML management then becomes a headache all in itself. So it\u0026rsquo;s rarely done.\nThis layout has everything encased in an AnchorPane that has only one child, a TabPane, but it has no anchoring specified. Once again, this is something the SceneBuilder seems to encourage because you see it a lot. Like, really\u0026hellip;a lot. The AnchorPane appears to have no purpose in the layout, but you have to scroll through the entire file to see that it only has the TabPane in it. You also have to scroll through the whole file to see that the TabPane has 4 Tabs defined.\nLet\u0026rsquo;s look at the first GridPane defined. It has this block of RowConstraints\n\u0026lt;rowConstraints\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;RowConstraints minHeight=\u0026#34;10.0\u0026#34; prefHeight=\u0026#34;30.0\u0026#34; vgrow=\u0026#34;NEVER\u0026#34;/\u0026gt; \u0026lt;/rowConstraints\u0026gt; Yeah, you have to count them. There\u0026rsquo;s 15 and they are all the same. You cannot do DRY with FXML. Need to change the height of all of them? Edit 15 lines (or do it 15 times in SceneBuilder). Needless to say, you wouldn\u0026rsquo;t do anything this crude in code. And there are 26 more GridPane rows scattered throughout this file that use the same parameters!\nMoving on to the content of the GridPane, we\u0026rsquo;ll just look at a small section:\n\u0026lt;Label text=\u0026#34;Number of Components\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;numComponentsSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Number of Epochs\u0026#34; GridPane.rowIndex=\u0026#34;2\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;numEpochsSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;3\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Nearest Neighbors\u0026#34; GridPane.rowIndex=\u0026#34;4\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Negative Sample Rate\u0026#34; GridPane.rowIndex=\u0026#34;6\u0026#34;/\u0026gt; \u0026lt;Label text=\u0026#34;Local Connectivity\u0026#34; GridPane.rowIndex=\u0026#34;8\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;nearestNeighborsSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;5\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;negativeSampleRateSpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;7\u0026#34;/\u0026gt; \u0026lt;Spinner fx:id=\u0026#34;localConnectivitySpinner\u0026#34; editable=\u0026#34;true\u0026#34; prefWidth=\u0026#34;100.0\u0026#34; GridPane.rowIndex=\u0026#34;9\u0026#34;/\u0026gt; First off, I guess we have to assume that the first Label goes in row/colum 0/0, because it has nothing specified. I\u0026rsquo;m guessing this is what SceneBuilder does by default???\nThe Spinner that goes with that first Label is actually declared in the next line, as well as the next Label/Spinner combination. After that we get the next three Labels and then we find that the Spinners that go along with them follow after the last Label. The rest of the GridPane seems to go more like that. Perhaps this is simply the order in which the contents were added in SceneBuilder??\nI wonder what\u0026rsquo;s in all these Spinners? Integers? Decimals? What are there ranges? How do they increment, and what are the default values? Do they change dynamically? You can\u0026rsquo;t tell any of this without looking at the FXML Controller, because clearly that\u0026rsquo;s where this stuff is defined. I don\u0026rsquo;t know if you can define these parameters in SceneBuilder, but the author has chosen not to here. This is probably fairly common. BTW: The associated FXML Controller in this project is nearly 800 lines long.\nWhat can see here from these 462 lines of FXML is that the assertion that the \u0026ldquo;declarative, hierarchical syntax\u0026rdquo; of FXML is somehow easy to read, understand and maintain is just not true.\nI think it\u0026rsquo;s clear that well structured and organized layout code is infinitely easier to read, understand and maintain than the equivalent FXML.\nWhat Are the Actual Benefits of FXML? # As far as I can tell, there is only one: You can use SceneBuilder.\nIt\u0026rsquo;s up to you to decide if SceneBuilder is something that you find value in. It\u0026rsquo;s entirely possible that the drag-and-drop approach of SceneBuilder makes it easier for you to visualize how your layouts will work as you construct them.\nA lot of people mention that they find the ability to quickly run a simulation of the layout in SceneBuilder to be valuable. For me, I find it just as easy to run the code and see it actually running. While I imagine that SceneBuilder is probably a bit faster, I don\u0026rsquo;t find that incremental builds in Gradle take very long at all, and I have never hesitated to launch a test run because I didn\u0026rsquo;t want to spend time waiting.\nI have worked on large systems where the specific screens I am developing are deep down a workflow that a significant amount of time to navigate to in the actual application. I those cases, I wouldn\u0026rsquo;t develop a screen layout \u0026ldquo;in situ\u0026rdquo;, I\u0026rsquo;d whip up a mini-application for testing the layout (and the Model, Controller and Interactor for that screen).\nThe downside of ScreenBuilder is that it strongly encourages you to develop the layout in isolation. In reality, the layout needs to work hand-in-glove with the Presentation Model. I like to let the GUI dictate the needs of the Presentation Model, and create the relationships between the Nodes in the layout and the Properties in the Presentation Model as I build the layout itself. In this way, I don\u0026rsquo;t just create the layout, I create a good part of the entire MVCI construct that it\u0026rsquo;s part of at the same time.\nSceneBuilder as a Beginners\u0026rsquo; Tool # One of the biggest problems that I have with JavaFX is that it is a huge, complicated library with a nearly complete lack of good tutorial material. This is an absolute nightmare for beginners.\nThere is this idea that SceneBuilder, as a graphical, drag-and-drop interface for JavaFX GUI design, somehow makes JavaFX more accessible to beginners. I seem to remember that Oracle touted SceneBuilder as huge benefit to switching over from Swing in the early days.\nBut for a beginner, starting up SceneBuilder is probably like sitting in the cockpit of a modern jet fighter when you\u0026rsquo;ve never even flown a kite before. There are a dizzying array of Controls you can use, and a head-spinning number of options that you can fiddle with for each one. And there\u0026rsquo;s no explanations for any of it (at least there weren\u0026rsquo;t years ago).\nMy main rememberance of SceneBuilder is the countless hours that I frittered away trying to make something\u0026hellip;anything work the way I wanted it to. I was lucky enough that I was on the clock, and I quickly realized that I couldn\u0026rsquo;t keep throwing this time away when I was supposed to be productive. So we decided to abandon SceneBuilder after a few months. Things got better after that.\nI often see comments from beginners that indicate that they are having the same experience. I\u0026rsquo;ve also seen the the resultant FXML that beginners create with SceneBuilder, which are often full of silly things like wrapping the whole layout in an AnchorPane with a single child and no anchors.\nDealing With the Complexity of FXML # Beginners, by definition, don\u0026rsquo;t understand what they are doing and there\u0026rsquo;s always a very big element of \u0026ldquo;paint by numbers\u0026rdquo; in their work. This is especially true because the SceneBuilder -\u0026gt; FXMLLoader -\u0026gt; FXML Controller process seems to add some layer of inpenetrable magic that they cannot get past. It\u0026rsquo;s clear that most beginners don\u0026rsquo;t even understand that the output of FXMLLoader.load() is just a Node subclass of some sort. They only ever see it referred to as root and dropped immediately into a Scene, so they think that\u0026rsquo;s all you can do with it.\nOnce you realize that FXML itself doesn\u0026rsquo;t actually give you anything of value, you can easily see it instead as the cost of using SceneBuilder.\nOver the decades, I\u0026rsquo;ve used all kinds of different screen generators in different systems, I\u0026rsquo;ve even written one myself for a \u0026ldquo;green screen\u0026rdquo; system. They all have to include some method to allow programmers to handle the stuff that doesn\u0026rsquo;t neatly fall into values and parameters that can be easily included in the screen building tool. Some use \u0026ldquo;hooks\u0026rdquo;. Some provide a place to enter code and subroutines. Some just generate code that you can modify yourself - I think Swing had one of those.\nSceneBuilder has FXML and the FXML Controller, along with the FXMLLoader that ties them together. That\u0026rsquo;s how it deals with things that you cannot easily define in SceneBuilder. It introduces a layer of complexity that you do not have with coded layouts.\nLet\u0026rsquo;s face it. Complexity is a huge issue with JavaFX, even without FXML.\nThink about some beginner who scratches his head for hours trying to figure out why that Node where he\u0026rsquo;s set visible to false is still taking up space in his layout. Why would they think they would need to set managed to false as well? How would they even know what managed meant? How are they supposed to cope with the magic that happens inside FXMLLoader?\nIt\u0026rsquo;s clear that beginners have difficulty understanding how elements inside an FXML Controller relate to the associated layout. It\u0026rsquo;s also clear that they have trouble understanding how to apply JavaFX concepts to an FXML/FXML Controller context, and that they even have trouble applying basic Java concepts to that context.\nThe Cost of FXML # The first cost of FXML, as you can see from that giant FXML example above, is that it actually makes it more difficult to read and understand how a layout works than the equivalent in well constructed and organized code.\nBut the most important cost of FXML is that it makes it very difficult, if not impossible, to develop a library of helpers, builders and factory methods (or the equivalent in classes) that simplify and standardize layout creation the way that you approach it.\nProgrammer complaints about JavaFX seem to revolve around one of two things: Too much boilerplate, or the lack of some particular Node or Control class.\nWhat do people mean by \u0026ldquo;boilerplate\u0026rdquo; with regards to JavaFX?\nUsually, it centres around the idea that you have to instantiate a Node as a variable, and then call several of its methods to configure it. Then put the Node into the layout. Something like this:\nLabel label = new Label(); label.getStyleClass().add(\u0026#34;some-selector\u0026#34;); label.textProperty().bind(model.someProperty()); HBox hBox = new HBox(10, label, otherNode); If you have ten Labels in your layout, and they are all configured in a similar way, then you\u0026rsquo;ve got 30 lines of boilerplate, which bloats your layout code and makes it difficult to read.\nBut you don\u0026rsquo;t have to live with this. You can create a static builder method that will do this for you:\npublic class Labels { public static Label promptLabel(ObseravbleStringProperty boundProperty) { Label label = Label(); label.textProperty().bind(boundProperty); label.getStyleClass().add(\u0026#34;some-selector\u0026#34;); return label; } } And you can call it like this:\nHBox hBox = new HBox(10, Labels.promptLabel(model.someProperty()),.....); And there you go, no more boilerplate. You can do this locally, as a method in your ViewBuilder class, or you can assemble a library of these methods that you put into a separate project.\nYou can do some of this stuff with FXML. You obviously cannot use builders, but you can create custom classes that compile into a jar file that you import into SceneBuilder to do some of the same things.\nWhich leads to the second point, missing Node classes\u0026hellip;\nThere are many instances in JavaFX where the only tools that we are given are very generic. Take text entry as an example. We have TextField.\nNow, TextField by itself is just a box that you can type anything into. But TextField supports the inclusion of a TextFormatter which allows you to take complete control over the input into a TextField. You can create TextFormatters for money amounts, integer amounts, postal codes, or phone numbers, or anything else you can think of. Personally, I wouldn\u0026rsquo;t use TextField without a TextFormatter for anything other than truly free-form text entry.\nLet\u0026rsquo;s look at phone numbers which have different formats all over the world. In Canada and the US we use something like \u0026ldquo;#(###)###-####\u0026rdquo;. In France they use \u0026ldquo;\u0026rdquo;## # ## ## ## ##\u0026quot;, while in Ukraine they use \u0026ldquo;### ## ### ####\u0026rdquo;, and in China it\u0026rsquo;s, \u0026ldquo;## ### #### ####\u0026rdquo;. And in many of these places, if not all, the format is different for calling in-country.\nOne possible implementation is to have a different TextFormatter for every phone number layout. TextFormatter is a Property of TextField, so it is possible to change the TextFormatter on-the-fly, or bind it to some other value. This suggests some possible implementation approaches. Alternatively, you could create builder methods to return specific styles of phone number entry. For instance: I feel that seeing one can make my point\nchildren += HBox(10.0, promptLabel(\u0026#34;Phone Number:\u0026#34;), frenchPhoneNumberTF(phoneNumber))) And now none of the mechanics of setting up the TextField are exposed to the layout code at all. Alternatively,\nchildren += HBox(10.0, promptLabel(\u0026#34;Phone Number:\u0026#34;), phoneNumberTF(countryCode, phoneNumber))) works too.\nIf you were in a domain where you really did need a range of different formats based on the country code, you\u0026rsquo;d probably be tempted to have a ComboBox for the country selection, and then a TextField for the phone number. You see this on websites all the time. In this case, I\u0026rsquo;d probably create a custom control that had both the ComboBox and the TextField in it, along with their associated prompt Labels, and the connections between the country code and the TextFormatter could be established internally. You could include a Property\u0026lt;Orientation\u0026gt; which would determine if the elements were layed out horizontally or vertically.\nYou\u0026rsquo;d use it like this:\nchildren += PhoneNumberEntryBox(countryCode, phoneNumber, orientation))) Can you do this with FXML?\nSomewhat. You cannot access the builder approach, but you could create FrenchPhoneNumberTF class and use it. You can reference PhoneNumberEntryBox, but you cannot connect the countryCode or phoneNumber parameters via FXML. Nor can you access the orientation parameter in FXML. In truth, you could make orientation a StlyeableProperty and have a custom attribute for it in a CSS file, which might be just as easy.\nBut here\u0026rsquo;s the thing: Anyone that spends a lot of time writing JavaFX applications is going to encounter the same use cases, that require the same customized or custom Controls, over and over again. Integrating them into your layouts is trivial - trivial to the point where they might as well be native Nodes in the JavaFX library - when you code your layouts by hand. It\u0026rsquo;s much more difficult to do the same with FXML.\nThe Importance of Builders and Custom Controls # I simply cannot over-emphasize the importance and value of this.\nThe core concept is that there is layout, and there is configuration, and there is logic.\nConfiguration is where the boilerplate and repeated code lives. Simply refuse to repeat yourself - apply DRY contstantly and consistently - and you\u0026rsquo;ll find yourself extracting the configuration out of your layouts. This leaves the layouts to be\u0026hellip;layout.\nThis is the really big issue (in my opinion) with FXML. It splits the logic out from layout, but leaves much of the configuration in the layout. And the rest of the configuration gets mingled in with the logic.\nAnd the truth is that almost all of the configuration is a small set of paramaters that are always combined in the same way for each use case. And the use cases are limited in number.\nIn the real layout code that I write, you\u0026rsquo;ll almost never see me use something like, new Label(). That\u0026rsquo;s because I\u0026rsquo;m almost always going to configure that Label in one of four or five ways, and I have builders for each of them. I do the same thing for many other Controls, too. I have custom TableColumns that have TableCells for a number of data types like dates, money, integers and booleans.\nWhen you look at layout code, you don\u0026rsquo;t care about the details of how a Label created through a call to Labels.dataLabel(model.someProperty) is configured. You know it\u0026rsquo;s a Label and it\u0026rsquo;s configured to display data. You also know that Labels.dataLabel() has been used hundreds of times, is thoroughly tested, and works properly. So it\u0026rsquo;s not likely to be a source of any problem you\u0026rsquo;re having.\nBecause of this, it\u0026rsquo;s not just code that\u0026rsquo;s not cluttering up your layouts, it\u0026rsquo;s not cluttering up your mind either. Do you worry about how new Label(\u0026quot;Something\u0026quot;) works? No, of course not. Nor do you worry about Labels.dataLabel().\nFXML With a Framework # If you are going to develop real applications that actually do something, and you want to do it right, you\u0026rsquo;ll need to adopt one of the commonly used frameworks like MVC, MVVM or my own MVCI. These frameworks all isolate your UI logic from your business (or application) logic, and provide the \u0026ldquo;separation of concerns\u0026rdquo; that make systems easier to maintain and enhance.\nWe now know that FXML doesn\u0026rsquo;t give you a head start on MVC, because the FXML Controller is not an MVC Controller. So here we\u0026rsquo;ll look at how you would go about implementing a framework in both an FXML and non-FXML application, and see how they are different\u0026hellip;\n{% include notice_kotlin %}\nIf I go into Intellij Idea and use the \u0026ldquo;New -\u0026gt; Project\u0026hellip;\u0026rdquo; wizard to create a JavaFX project, I\u0026rsquo;ll get a \u0026ldquo;Hello World\u0026rdquo; application that uses FXML. So let\u0026rsquo;s do that and see what it looks like:\nclass HelloApplication : Application() { override fun start(stage: Stage) { val fxmlLoader = FXMLLoader(HelloApplication::class.java.getResource(\u0026#34;hello-view.fxml\u0026#34;)) val scene = Scene(fxmlLoader.load(), 320.0, 240.0) stage.title = \u0026#34;Hello!\u0026#34; stage.scene = scene stage.show() } } fun main() { Application.launch(HelloApplication::class.java) } and the FXML Controller looks like this:\nclass HelloController { @FXML private lateinit var welcomeText: Label @FXML private fun onHelloButtonClick() { welcomeText.text = \u0026#34;Welcome to JavaFX Application!\u0026#34; } } while the FXML file looks like this:\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;?import javafx.geometry.Insets?\u0026gt; \u0026lt;?import javafx.scene.control.Label?\u0026gt; \u0026lt;?import javafx.scene.layout.VBox?\u0026gt; \u0026lt;?import javafx.scene.control.Button?\u0026gt; \u0026lt;VBox alignment=\u0026#34;CENTER\u0026#34; spacing=\u0026#34;20.0\u0026#34; xmlns:fx=\u0026#34;http://javafx.com/fxml\u0026#34; fx:controller=\u0026#34;ca.pragmaticcoding.fxmlstuff.HelloController\u0026#34;\u0026gt; \u0026lt;padding\u0026gt; \u0026lt;Insets bottom=\u0026#34;20.0\u0026#34; left=\u0026#34;20.0\u0026#34; right=\u0026#34;20.0\u0026#34; top=\u0026#34;20.0\u0026#34;/\u0026gt; \u0026lt;/padding\u0026gt; \u0026lt;Label fx:id=\u0026#34;welcomeText\u0026#34;/\u0026gt; \u0026lt;Button text=\u0026#34;Hello!\u0026#34; onAction=\u0026#34;#onHelloButtonClick\u0026#34;/\u0026gt; \u0026lt;/VBox\u0026gt; This is good enough for an example.\nNow, let\u0026rsquo;s look at what the exact same application would look like without FXML:\nclass HelloNoFXML : Application() { override fun start(stage: Stage) { with(stage) { title = \u0026#34;Hello!\u0026#34; scene = Scene(createContent(), 320.0, 240.0) show() } } private fun createContent(): Region = VBox(20.0).apply { padding = Insets(20.0) alignment = Pos.CENTER val welcomeText = Label() children += welcomeText children += Button(\u0026#34;Hello \u0026#34;).apply { onAction = EventHandler { welcomeText.text = \u0026#34;Welcome to JavaFX Application!\u0026#34; } } } } fun main() { Application.launch(HelloNoFXML::class.java) } And you can count and see that both versions have 20 lines of Kotlin code, but the FXML version also has 8 lines of layout FXML.\nIn my opinion, the non-FXML version has the advantage that it\u0026rsquo;s incredibly staight-forward. You can look at it and you can see that createContent() is a builder, and that everything in it is just plain, vanilla Kotlin code. You can see instantly how the action of the Button relates directly to the Label and how they are both in a VBox with a specific alignment, padding and spacing.\nBut now, let\u0026rsquo;s imagine that we are going to something more involved with this. We\u0026rsquo;re going to connect this up to some database or external API that will do things for us, and we are going to reflect the results of this external action in our GUI. For this simple example, we\u0026rsquo;ll just do this via the message in the Label. This means that we\u0026rsquo;ll need to implement a framework, in this case I\u0026rsquo;m going to use my own MVCI (Model-View-Controller-Interactor).\nIn the non-FXML version, this is pretty simple. The first thing I\u0026rsquo;ll do is create a Model:\nclass HelloModel { val welcomeMessage: StringProperty = SimpleStringProperty(\u0026#34;\u0026#34;) } I\u0026rsquo;ll split that createContent() method out to be the build() method in a Builder:\nclass HelloViewBuilder(private val model: HelloModel) : Builder\u0026lt;Region\u0026gt; { override fun build(): Region = VBox(20.0).apply { padding = Insets(20.0) alignment = Pos.CENTER children += Label().apply { textProperty().bind(model.welcomeMessage) } children += Button(\u0026#34;Hello \u0026#34;).apply { onAction = EventHandler { model.welcomeMessage.value = \u0026#34;Welcome to JavaFX Application!\u0026#34; } } } } Then we\u0026rsquo;ll introduce a Controller:\nclass HelloMvciController { private val model: HelloModel = HelloModel() private val viewBuilder = HelloViewBuilder(model) fun getView() = viewBuilder.build() } And we\u0026rsquo;ll link it back into the Application:\nclass HelloNoFXML : Application() { override fun start(stage: Stage) { with(stage) { title = \u0026#34;Hello!\u0026#34; scene = Scene(HelloMvciController().getView(), 320.0, 240.0) show() } } } You can see that this is still fundamentally the same thing. We\u0026rsquo;ve split the contents of the Label off into the Model, but this actually improves on the original code because now the Button refers to the Model, and has no reference to the Label, and we no longer need a variable for it. This removes internal coupling between the layout Nodes.\nBut now we need some business logic that will simulate some sort of access to an external API to fetch some data, and this will require an Interactor:\nclass HelloInteractor(private val model: HelloModel) { fun getWelcome(): Unit { model.welcomeMessage.value = \u0026#34;Hello from the Interactor\u0026#34; } } This doesn\u0026rsquo;t actually do anything special, but if you had some code that needed to access an external database or API, this is where you would put it.\nTo use this, we\u0026rsquo;ll need to update the Controller and the View to handle an \u0026ldquo;action\u0026rdquo; triggered by the View (internally from the Button). First the ViewBuilder:\nclass HelloViewBuilder(private val model: HelloModel, private val actionHandler : () -\u0026gt; Unit) : Builder\u0026lt;Region\u0026gt; { override fun build(): Region = VBox(20.0).apply { padding = Insets(20.0) alignment = Pos.CENTER children += Label().apply { textProperty().bind(model.welcomeMessage) } children += Button(\u0026#34;Hello \u0026#34;).apply { onAction = EventHandler { actionHandler.invoke() } } } } The ViewBuilder now as an additional constructor parameter that takes the Kotlin equivalent of Runnable. It now just invokes the Runnable when the Button is activated.\nAnd then the Controller:\nclass HelloMvciController { private val model: HelloModel = HelloModel() private val interactor = HelloInteractor(model) private val viewBuilder = HelloViewBuilder(model) {interactor.getWelcome()} fun getView() = viewBuilder.build() } Now, when the Button is activated, the Interactor\u0026rsquo;s getWelcome() method is invoked by the Controller and the Model is updated. It\u0026rsquo;s important to understand that the EventHandler itself is very much a GUI component - it responds to JavaFX events - while the \u0026ldquo;action\u0026rdquo; is actually GUI agnostic. The JavaFX nature of the EventHandler has been stripped away, and we\u0026rsquo;re just communicating via a generic Kotlin\\Java functional interface.\nIf we needed to disable and the later re-enable the Button, we\u0026rsquo;d define that in the View, and pass the re-enabling code to the Controller as a Runnable. The original Runnable would be transformed to a Consumer\u0026lt;Runnable\u0026gt;. All of this still remains just generic Kotlin\\Java function elements.\nThis is just a silly example, but in real life, the Controller would handle any threading issues, and the Interactor would connect to any external databases or API\u0026rsquo;s to do whatever it needed.\nBut regardless of what that was, the View wouldn\u0026rsquo;t change at all. Which is the point of using a framework like MVCI - it keeps the business logic far away from the UI logic.\nAs for the FXML\u0026hellip;\nTheoretically, we should be able to treat the FXML file, the FXMLController and the FXMLLoader as a View in MVCI. Let\u0026rsquo;s see how that would work.\nThe getView() method in the Controller would now call the FXMLLoader to create the view:\nclass HelloMvciControllerFxml { private val model: HelloModel = HelloModel() private val interactor = HelloInteractor(model) fun getView() : Region { val fxmlLoader = FXMLLoader(HelloApplication::class.java.getResource(\u0026#34;hello-view.fxml\u0026#34;)) return fxmlLoader.load() } } Then we can update the Application to work the same way that the non-FXML version worked:\nclass HelloApplication : Application() { override fun start(stage: Stage) { stage.title = \u0026#34;Hello!\u0026#34; stage.scene = Scene(HelloMvciControllerFxml().getView(), 320.0, 240.0) stage.show() } } And it runs just like it did before.\nBut now it gets more complicated. We need to get the Model into the FXML Controller, and we need to modify the FXML Controller such that it can call our MVCI controller to perform the action. The first thing to try is to grab the FXML Controller from the FXMLLoader, and then update it with these references:\nclass HelloMvciControllerFxml { private val model: HelloModel = HelloModel() private val interactor = HelloInteractor(model) fun getView(): Region { val fxmlLoader = FXMLLoader(HelloApplication::class.java.getResource(\u0026#34;hello-view.fxml\u0026#34;)) val view: Region = fxmlLoader.load() val fxmlController = fxmlLoader.getController() as HelloController fxmlController.setModel(model) fxmlController.setAction { interactor.getWelcome() } return view } } Let\u0026rsquo;s look at the FXML Controller:\nclass HelloController { @FXML private lateinit var welcomeText: Label private var actionHandler: (() -\u0026gt; Unit)? = null fun setModel(model: HelloModel) { welcomeText.textProperty().bind(model.welcomeMessage) } fun setAction(newActionHandler: () -\u0026gt; Unit) { actionHandler = newActionHandler } @FXML private fun onHelloButtonClick() { actionHandler?.invoke() } } The problem with this approach is that you have to deal with the fact that the Model is not present in the FXML Controller at the time that the FXML Controller is instantiated by the FXMLLoader. This means that you cannot perform setup operations in initialize() as you ordinarily would. In this example, I\u0026rsquo;ve loaded all that stuff (well, it\u0026rsquo;s just one thing but you get the idea) into setModel(), but that might not be the best approach.\nFor the action handler, I\u0026rsquo;ve taken a different approach and created it as a field in the FXML Controller which is initially null. Then it\u0026rsquo;s set later by the MVCI Controller after the FXMLLoader.load() has been invoked. The downside to this is that every reference to actionHandler has to deal with the possible null value. On the upside, the FXML file still implements the EventHandler in the standard FXML way and doesn\u0026rsquo;t need to be modified to accommodate the changes to the FXML Controller.\nThere is a better way. First, modify the FXML Controller so that it has constructor parameters :\nclass HelloController(private val model: HelloModel, private val actionHandler: () -\u0026gt; Unit) : Initializable { @FXML private lateinit var welcomeText: Label @FXML private fun onHelloButtonClick() { actionHandler.invoke() } override fun initialize(location: URL?, resources: ResourceBundle?) { welcomeText.textProperty().bind(model.welcomeMessage) } } You can see that now we can set the binding in initialize() and we don\u0026rsquo;t have to worry about null in the actionHandler for the Button.\nThen we have to tell the FXMLLoader to use a \u0026ldquo;Controller Factory\u0026rdquo; to instantiate the FXML Controller. This is just a Callback that will return an instantiation of our FXML Controller:\nclass HelloMvciControllerFxml { private val model: HelloModel = HelloModel() private val interactor = HelloInteractor(model) fun getView(): Region { val fxmlLoader = FXMLLoader(HelloApplication::class.java.getResource(\u0026#34;hello-view.fxml\u0026#34;)) fxmlLoader.setControllerFactory { HelloController(model) { interactor.getWelcome() } } return fxmlLoader.load() } } This works, and functions exactly the same as the non-FXML version.\nWhen you look at the code, it\u0026rsquo;s really not that complicated. Just create an FXML Controller with constructor parameters (just like the ViewBuilder in the non-FXML version) and use a ControllerFactory to instantiate the FXML Controller inside the FXMLLoader. From that point on, you can treat the FXML Controller as the interface for the View, and presence of FXML should be invisible to the rest of the framework.\nYou should note that there were no changes at all to the FXML file throughout this example. This means that, generally speaking, you can design your FXML files with SceneBuilder without any regard to the presence (or not) of an MVC, MVVM or MVCI framework sitting behind it.\nYou should also note that you\u0026rsquo;re probably not going to see this approach outlined anywhere else. I spent a bit of time specifically searching for variants on \u0026ldquo;JavaFX FXML MVC\u0026rdquo; and didn\u0026rsquo;t see anything like this anywhere.\nWhy not?\nI think mostly because any tutorial that you see gets sidetracked with the idea that the FXML Controller is the Controller in the MVC sense. The other thing you always see with this is that the \u0026ldquo;Model\u0026rdquo; that they propose is just a POJO of Observable classes, and doesn\u0026rsquo;t have any domain/application logic in it. All the application logic goes into the FXML Controller, which becomes essentially a \u0026ldquo;God\u0026rdquo; class.\nConclusion # As an experienced JavaFX developer I can say that if I\u0026rsquo;m building a typical business type application, then the View components (which include both the layout and the GUI logic) comprise a fairly small portion of the code base. I\u0026rsquo;d guess something approaching 10% of the total code in the application.\nThis probably sounds fantastical to most beginners. Impossible, really. That\u0026rsquo;s because JavaFX is a huge library which is horribly badly documented for beginners. Virtually everything that beginners think they are doing right is probably wrong. They do things like using EventHandlers and ChangeListeners when Binding is the better approach. Or, things like using ComboBox.getSelectionModel().getSelectedItem() instead of just ComboBox.getValue().\nNone of these \u0026ldquo;wrong\u0026rdquo; approaches is simpler or takes less code than doing it the right way. In fact, a typical beginner probably writes hundreds of lines of code for every screen that are just not necessary.\nSo when we talk about \u0026ldquo;To FXML or not to FXML\u0026rdquo;, we\u0026rsquo;re not talking about some profound decision that dictates the entire structure of your application. We\u0026rsquo;re talking about how you architect that 10% which comprises the View.\nThat is, however, as long as you don\u0026rsquo;t buy into the idea that the FXML Controller acts as the MVC Controller. Because it\u0026rsquo;s not, and if you treat it like it is then your application structure is going to suffer.\nAt the end of the day, the answer to, \u0026ldquo;Should you use FXML?\u0026rdquo;, depends on whether or not you see enough value in SceneBuilder to overcome the added complexity of dealing with the FXML. For most beginners, this is probably not true, but they are also probably not going to realize this. If you are a beginner, and you got this far down this article and you are curious about how you go about creating a layout without FXML, you should read my Absolute Beginners Guide to JavaFX.\n","date":"10 March 2025","externalUrl":null,"permalink":"/javafx/techniques/nofxml/2024-12-29-fxml-or-not/","section":"JavaFX","summary":"Introduction # From time to time the question about, “To FXML or not to FXML” comes up on Reddit and other social media, and I’m know as being firmly in the “Not FXML” camp. I’ve had a few people message me about this, so I thought it might be a good time to examine this topic in depth.\n","title":"Should You Use FXML?","type":"javafx"},{"content":"Observables and their related classes are the core of the Reactive aspect of JavaFX, and it’s critical to understand how they work if you want to take advantage of the benefits of Reactive design.\n","date":"29 September 2024","externalUrl":null,"permalink":"/javafx/elements/observables/","section":"JavaFX","summary":"Observables and their related classes are the core of the Reactive aspect of JavaFX, and it’s critical to understand how they work if you want to take advantage of the benefits of Reactive design.","title":"Observables, Bindings and Listeners","type":"javafx"},{"content":" The Elements of JavaFX You Need to Master # The Nodes # There are large amount of classes in JavaFX arranged in a hierarchy starting with a top level class called, Node. There are a number of layout classes that all work differently, such as BorderPane, ScrollPane, HBox, VBox, GridPane and StackPane which can all be embedded inside of each other to create exactly the layout you want.\nThere are classes to display images, text or text and images together. ListView, TreeView and TableView allow collections of data to be displayed on screen (and edited) in highly customizable ways.\nFor user input, you can choose from TextField, ComboBox, ChoiceBox, Spinner, Checkbox, RadioButton, Buttons and ToggleButtons.\nAll of these Nodes have a large number of methods and properties for controlling their presentation and how they act. Learning how to use them and configure them can be a daunting task. Luckily, most of the tutorials on the web seem to focus on this aspect of JavaFX, so help is easy to find.\nObservable Objects and Concepts # This is the Reactive part of JavaFX, and it\u0026rsquo;s critical to understand how this works.\nThere are three aspects to the observable nature of JavaFX. The first is a huge set of classes that wrap around values and implement the \u0026ldquo;Observer Design Pattern\u0026rdquo; into classes collectively called Properties. This enables you to do two important things: The first is to use Bindings to connect Properties to each other such that when value changes, the values in the connected Properties will change automatically. The second is to create ChangeListeners that watch these observable values and trigger code to run whenever they detect a change in them.\nThe key to using JavaFX to create Reactive applications is to establish a definitive place for the \u0026ldquo;State\u0026rdquo; of your GUI to reside, and then use Properties, Bindings and ChangeListeners to connect your GUI elements back to that \u0026ldquo;State\u0026rdquo; instead of copying the values in and out of it in your code.\nBut to do that, you\u0026rsquo;ll need to understand how these Properties, Bindings and ChangeListeners work.\nEvents and Actions # At its heart, JavaFX is \u0026ldquo;Event Driven Programming\u0026rdquo;, and even bindings probably boil down to event handling deep inside the JavaFX library. Events can be triggered by Property changes, through mouse move movements and clicks, by keyboard actions, and the actions on Nodes like Buttons and MenuItems. Events are captured in the code through \u0026ldquo;Handlers\u0026rdquo;, and each EventHandler specifies code that should be run when the event occurs.\nIt\u0026rsquo;s important to understand where actions and Events are more appropriate than State changes and Bindings. Something like a Button click is unmistakably an \u0026ldquo;Action\u0026rdquo;, and should probably be treated as such. It would be possible to configure a click handler on a Button to toggle a Property in State, which is then captured with a ChangeListener in an application logic class, but that\u0026rsquo;s probably the most complicated way to program the process.\nCSS # All of the JavaFX Nodes can be styled with cascading style sheets. These are largely compatible with web standards for CSS, but there are some differences to learn about. How you implement CSS styling largely becomes a matter of taste. You can override the standard styling for entire classes of Nodes, or you can create custom selectors and attach them to specific Nodes in your code. Or you can do both.\nGetting started with stylesheets is pretty easy. There are, as usual, some things you need to know before you get going but they\u0026rsquo;ve all been gathered together in this article:\nPseudo-Classes are the best way to represent changes in the State of your application the styling of your GUI.\nHandling the GUI Thread # Like virtually every GUI library available, it\u0026rsquo;s really, really bad to do long or blocking operations on the same thread that is maintaining the GUI. This handled by launching a background thread to do the otherwise blocking operation, and then triggering an event at its completion which will update the GUI. The GUI thread in JavaFX is called the \u0026ldquo;FX Application Thread\u0026rdquo;, or \u0026ldquo;FXAT\u0026rdquo;\nTip This is probably the number one issue that beginners in JavaFX struggle with.\nThe biggest implication of this is that you cannot program linearly. Try as hard as you like, but there\u0026rsquo;s no way that you can run anything that looks like a \u0026ldquo;Function\u0026rdquo; (ie. input some data and wait for an answer) triggered from the FXAT that uses a background thread. When your application behaves weirdly and seems laggy or hangs, come back to this paragraph and read it carefully because this is what you\u0026rsquo;re probably trying to do.\nJavaFX provides a handy class called \u0026ldquo;Task\u0026rdquo;, which supplies a runnable which you can pass to a Thread. When the code in the Task has been completed, it triggers a \u0026ldquo;Success\u0026rdquo; Event. You can supply an Event Handler which be invoked when the \u0026ldquo;Success\u0026rdquo; event is triggered. Just like any Event Handler, it will be run on the FXAT and can therefore update the GUI.\n","date":"29 September 2024","externalUrl":null,"permalink":"/javafx/elements/","section":"JavaFX","summary":"The Elements of JavaFX You Need to Master # The Nodes # There are large amount of classes in JavaFX arranged in a hierarchy starting with a top level class called, Node. There are a number of layout classes that all work differently, such as BorderPane, ScrollPane, HBox, VBox, GridPane and StackPane which can all be embedded inside of each other to create exactly the layout you want.\n","title":"The Elements of JavaFX","type":"javafx"},{"content":" JavaFX nodes # There are large amount of classes in JavaFX arranged in a hierarchy starting with a top level class called, Node. There are a number of layout classes that all work differently, such as BorderPane, ScrollPane, HBox, VBox, GridPane and StackPane which can all be embedded inside of each other to create exactly the layout you want.\nThere are classes to display images, text or text and images together. ListView, TreeView and TableView allow collections of data to be displayed on screen (and edited) in highly customizable ways.\nFor user input, you can choose from TextField, ComboBox, ChoiceBox, Spinner, Checkbox, RadioButton, Buttons and ToggleButtons.\nAll of these Nodes have a large number of methods and properties for controlling their presentation and how they act. Learning how to use them and configure them can be a daunting task. Luckily, most of the tutorials on the web seem to focus on this aspect of JavaFX, so help is easy to find.\nThe Articles: # The following articles each deal with single Node type or a group of similar Nodes:\n","date":"30 August 2024","externalUrl":null,"permalink":"/javafx/elements/nodes/","section":"JavaFX","summary":"The building-blocks of a JavaFX GUI are the classes in the hierarchy that extends from Node. There’s a great variety of Node subclasses, each of which serves a different purpose. Some classes are designed to facilitate organizing the elements of your GUI on the screen, some are for user input, and others are for displaying information. Some others are designed to trigger actions in your application.","title":"The JavaFX Nodes","type":"javafx"},{"content":"","date":"28 August 2024","externalUrl":null,"permalink":"/tags/javafx/","section":"Tags","summary":"","title":"Javafx","type":"tags"},{"content":"This series explains the JavaFX Observable framework, starting with the core interfaces and moving through properties, bindings, and practical usage.\nArticles # This series contains:\nIntroduction to the Observable classes Properties and Writable values Bindings and reactive expressions ","date":"28 August 2024","externalUrl":null,"permalink":"/javafx/elements/observable-classes/","section":"Series","summary":"This series explains the JavaFX Observable framework, starting with the core interfaces and moving through properties, bindings, and practical usage.\nArticles # This series contains:\n","title":"JavaFX Observable Classes","type":"series"},{"content":"","date":"28 August 2024","externalUrl":null,"permalink":"/tags/observable/","section":"Tags","summary":"","title":"Observable","type":"tags"},{"content":"","date":"28 August 2024","externalUrl":null,"permalink":"/tags/properties/","section":"Tags","summary":"","title":"Properties","type":"tags"},{"content":"","date":"28 August 2024","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":" Introduction - Why Use a Framework? # Frameworks are all about organizing your application so that it will be easy to maintain, enhance and expand over time.\nIf your application is going to actually \u0026ldquo;do something\u0026rdquo; it will probably need to interact with the world outside your application. This might just be the local file system, but it might be a local database, or a remote database, another local application or REST API for some service anywhere on the Web. In any event, you need to organize your application so that you don\u0026rsquo;t have your screen layout muddled up with business logic and calls to external databases and services.\nThere are a number of frameworks to choose from, but they are all designed to keep your business logic out of your layouts, and your layouts out of your business logic. If they are done right, then your layouts and your business logic can connect without either one knowing anything at all about the other.\nThis article is about my own framework, \u0026ldquo;Model-View-Controller-Interactor\u0026rdquo;, which works in a manner very similar to the other framemworks that you\u0026rsquo;ll hear about, but is fine tuned to work with JavaFX when it\u0026rsquo;s used to create \u0026ldquo;Reactive\u0026rdquo; GUI applications.\nWhat\u0026rsquo;s Wrong With the Others? # This is a quick guide, so I\u0026rsquo;m not going to go into a deep description of the other frameworks or how they work. But I will explain why I feel they fall short when building Reactive applications with JavaFX:\nModel-View-Presenter (MVP)\nThe problem with MVP is that the View is stripped down to nothing more than layout and styling, leaving all other aspects of the GUI to be handled by the Presenter. This requires a level of coupling between the View and the Presenter that is so tight that they might as well be considered to be a single component. The Presenter then becomes a \u0026ldquo;god class\u0026rdquo;. I\u0026rsquo;m not sure that anybody uses MVP any more.\nModel-View-ViewModel (MVVM)\nMVVM looks like a much better fit for Reactive JavaFX because it specifically allows for binding between the Presentation Model and the View. However, the ViewModel isn\u0026rsquo;t allowed to see the Domain Objects and the Model isn\u0026rsquo;t allowed to see Presentation Model and the result is complications passing data between the ViewModel and the Model. The coupling is loose, but it becomes a problem itself because it can be very complicated to work around.\nModel-View-Controller (MVC)\nMVC solves many of the issues with MVVM because the Presentation Model is part of the Model. However, MVC specifically exposes the Model (and, therefore, the Presentation Model) to the View in a \u0026ldquo;read-only\u0026rdquo; mode. This means that Properties in the Model cannot be bound bidirectionally to the Properties of the Nodes in the View and updates must be passed through the Controller to the Model.\nWhat is Model-View-Controller-Interactor? # MVCI solves the problems of these other frameworks by taking MVC and splitting the Model into two parts: the Presentation Model (just called the \u0026ldquo;Model\u0026rdquo; in this framework) and the \u0026ldquo;Interactor\u0026rdquo;. This new Model is simple a POJO composed of JavaFX Observable objects and lists, and the other three components all have read and write access to it.\nThe result is something that is instantly simple and easy to integrate with Reactive JavaFX. Properties of the Nodes in the View can be bound in either direction to the elements of the Model. This means that the Model is truly a data representation of the \u0026ldquo;state\u0026rdquo; of the GUI at all times. At the other end of the framework, all of the business logic is contained and isolated within the Interactor, which also is the only component that can access external services, applications and databases.\nThis fulfils the purpose of the framework. Business logic, application connective tissue and GUI design are all isolated from each other and very loosely coupled. You\u0026rsquo;ll find that this structure is a natural fit for Reactive JavaFX and takes very little effort to implement.\nThe Components of MVCI # This diagram shows the components of MVCI in relation to each other and external elements:\nLet\u0026rsquo;s take a look at the four components of MVCI and see what they are how they work together.\nThe Model # The Model is just a bunch of data. But it\u0026rsquo;s data stored in JavaFX Observable objects (typically Properties), and ObservableLists. The intention is that the Interactor will treat the Observables simply as wrappers around data, ignoring their Observable nature (for the most part). At the other end, the View treats the Model as a collection of ObservableVales that it will treat only as Observable. This means that the View primarily interacts with the Model through Bindings, and perhaps Subscriptions or Listeners.\nThe Controller # The Controller is the connective tissue of the framework. It instantiates all the other components, and it handles anything involving threading (which becomes important when you need to deal with long or blocking processes off the FXAT). It also contains any links back to other MVCI frameworks in your application, usually through shared Model elements. Typically, you will initiate an MVCI framework by instantiating the Controller, and it will bootstrap itself through it\u0026rsquo;s constructor code.\nThe Interactor # Here\u0026rsquo;s where the business logic resides, and the calls to your external services, databases and applications. It\u0026rsquo;s also the only place that you\u0026rsquo;ll find \u0026ldquo;Domain Objects\u0026rdquo;. Domain Objects are the classes that hold the data that are returned from (and are sent to) your \u0026ldquo;Services\u0026rdquo; and they are completely foreign to your View and your Controller.\nThe View and the ViewBuilder # The View is a fully functioning JavaFX Node subclass that can be added to the SceneGraph. It handles all of the display, and any direct interaction with the user.\nThe preferred way to create a View is the \u0026ldquo;configuration\u0026rdquo; of an existing Node sub-class via a Builder. Happily, JavaFX has an interface called Builder\u0026lt;T\u0026gt; with a single method called build(). If you follow this approach, and it\u0026rsquo;s usually the correct one, then you won\u0026rsquo;t have a View class in your framework, but you\u0026rsquo;ll have a ViewBuilder which implements Builder\u0026lt;Region\u0026gt;.\nWhat Goes Where? # One of the nice things about MVCI is that all of the decisions about where to put your code are straight-forward and easy to resolve. Let\u0026rsquo;s look at some of the most important items:\nBusiness Logic # This is the question to ask first: \u0026ldquo;Is this business logic?\u0026rdquo; If your application isn\u0026rsquo;t a business application, then substitute some other term like, \u0026ldquo;game logic\u0026rdquo;. You get the idea.\nIf the answer is \u0026ldquo;YES\u0026rdquo;, then it goes in the Interactor. No exceptions.\nPresentation # Another question to ask is whether the code deals solely with how the data is presented to the user. That \u0026ldquo;solely\u0026rdquo; is interpreted very narrowly and if the answer is \u0026ldquo;YES\u0026rdquo;, then it goes in the View. Is something going to be displayed in a particular colour? That colour choice is probably solely presentation and belongs in the View. However, if that colour choice is based on something that smells like business logic, then it\u0026rsquo;s not solely presentation. You\u0026rsquo;ll need to split out the business logic, put that part in the Interactor and use the results of that to determine the actual colour to be used in the View.\nFor example, imagine you have a list of grocery items and you want veggies in one colour, and fruit in another, and dairy in another colour. If your list is just; bananas, carrots, apples, tomatoes and milk, then you\u0026rsquo;ll have a problem determining the colours in the View. What\u0026rsquo;s a \u0026ldquo;tomato\u0026rdquo;? Is it a fruit, a veggie or dairy? That\u0026rsquo;s a business logic decision, and cannot be made in the View. Update your Model such that the list also contains the type, and then populate it accordingly from the Interactor. Now you\u0026rsquo;ll get; bananas(fruit), carrots(veggie), apples(fruit), tomatoes(technically fruit, but used like a veggie), and milk(dairy). In your View, you look at the types and determine the colour based on that, because now that is solely a presentation decision.\nDomain Logic # It\u0026rsquo;s important to understand that \u0026ldquo;Business Logic\u0026rdquo;, as it is used above actually means, \u0026ldquo;business logic specific to this MVCI construct\u0026rdquo;. This does not include calling external API\u0026rsquo;s or parsing JSON data. Things like that belong in a layer below your Interactor that holds the \u0026ldquo;Domain\u0026rdquo;.\nLet\u0026rsquo;s take a look at the fruit and veggie example above. If your list of items is coming from a database somewhere, you are NOT going to put SQL statements or any equivalent in your Interactor. Create a GroceryListService that does the database access, then parses the resultant JSON data and creates a list of GroceryItem which probably has at least name and quantity. GroceryItem is your domain object, and that list is passed back to your Interactor.\nBut what about the type?\nLet\u0026rsquo;s say that you\u0026rsquo;ve found some web service like Chomp that can give you information about grocery items, (full disclosure, Chomp doesn\u0026rsquo;t seem to tell you what department stuff is in) and you are going to use it to determine fruit, veggie or dairy. The code that contains the API calls and the JSON parsing for that function also goes into a service. However, the code that loops through your list of grocery items, calls the service to find out what type of item it is, and then integrates the two together into the Model is business logic for this MVCI construct and goes in the Interactor.\nPresentation Data # The Model contains just \u0026ldquo;Presentation Data\u0026rdquo;. In other words, every element is something that is potentially used directly by both the View and the Interactor. Another way that you can think about it is that the \u0026ldquo;Presentation Data\u0026rdquo; represents the \u0026ldquo;State\u0026rdquo; of your entire MVCI construct. Because the View and the Model are connected to each other through Bindings, any relevant changes to your View are going to be mirrored in your Model and they are going to be immediately visible and available to the Interactor. At the same time, any changes to the Model made by the business logic in the Interactor are going to be instantly reflected in the View.\nAn Example # The best way to understand this is to see it in code. So we\u0026rsquo;ll build that grocery list application to see how it works\u0026hellip;\nThe Application # class MvciApplication : Application() { override fun start(stage: Stage) { stage.scene = Scene(Controller().getView(), 600.0, 400.0) stage.show() } } fun main() = Application.launch(MvciApplication::class.java) The most important take-away here is the way that the Controller is instantiated and then simply used to retrieve the View so that it can be placed in the Scene.\nThe perspective that you need to have is that the View, which is just some sort of Node subclass, is the important product of the Controller from a viewpoint outside the MVCI framework. This is the thing that you are going to stick into your GUI. In this case we\u0026rsquo;re putting in as the root element of a Scene, but it could become the child of some other layout class. It will still work.\nThe Controller # The rough skeleton for a Controller is always something like this:\nclass Controller { private val model= Model() private val interactor = Interactor(model) private val viewBuilder:Builder\u0026lt;Region\u0026gt; = ViewBuilder(model) fun getView() : Region = viewBuilder.build() } But we are also going to be doing some database lookups in our application, so we\u0026rsquo;ll need to use Task to handle the threading and call the Interactor methods to do the work:\nclass Controller { private val model = Model() private val interactor = Interactor(model) private val viewBuilder: Builder\u0026lt;Region\u0026gt; = ViewBuilder(model) { fetchList() } private fun fetchList() { Thread(object : Task\u0026lt;List\u0026lt;GroceryItem\u0026gt;\u0026gt;() { override fun call() = interactor.getList() }.apply { setOnSucceeded { _ -\u0026gt; interactor.updateModelAfterFetch(get()) } }).apply { isDaemon = true start() } } fun getView(): Region = viewBuilder.build() } The Model # Our Model is going to have three classes, one to hold the actual grocery items and another to hold (at least) a list of those items. The final class is an Enum to hold the departments:\nclass Model { val listName: StringProperty = SimpleStringProperty(\u0026#34;\u0026#34;) val groceryList: ObservableList\u0026lt;GroceryModel\u0026gt; = FXCollections.observableArrayList() } class GroceryItem { val name :StringProperty = SimpleStringProperty(\u0026#34;\u0026#34;) val quantity: ObjectProperty\u0026lt;Int\u0026gt; = SimpleObjectProperty(0) val department: ObjectProperty\u0026lt;Department\u0026gt; = SimpleObjectProperty() } enum class Department { DAIRY, FRUITS, VEGGIES, MEATS, BAKERY; } The Enum class Department is more of a utility class than part of the Presentation Model, as it\u0026rsquo;s used in the Domain Objects as well.\nThe ViewBuilder # Our ViewBuilder is just a class that implemnents Builder\u0026lt;Region\u0026gt;. As is often the case with \u0026ldquo;top-level\u0026rdquo; Views, it\u0026rsquo;s best to return a BorderPane:\nclass ViewBuilder(private val model: Model, private val listFetcher: () -\u0026gt; Unit) : Builder\u0026lt;Region\u0026gt; { override fun build(): Region = BorderPane().apply { center = createTable() bottom = createLookup() padding = Insets(15.0) } private fun createLookup(): Region = HBox(10.0).apply { children += Label(\u0026#34;List Name:\u0026#34;) children += TextField().apply { textProperty().bindBidirectional(model.listName) } children += Button(\u0026#34;Fetch\u0026#34;).apply { setOnAction { listFetcher.invoke() } } padding = Insets(5.0) } private fun createTable(): Node = TableView\u0026lt;GroceryModel\u0026gt;().apply { columns += TableColumn\u0026lt;GroceryModel, String\u0026gt;(\u0026#34;Item Name\u0026#34;).apply { setCellValueFactory { p -\u0026gt; p.value.name } } columns += TableColumn\u0026lt;GroceryModel, Department\u0026gt;(\u0026#34;Department\u0026#34;).apply { setCellValueFactory { p -\u0026gt; p.value.department } } columns += TableColumn\u0026lt;GroceryModel, Int\u0026gt;(\u0026#34;Quantity\u0026#34;).apply { setCellValueFactory { p -\u0026gt; p.value.quantity } } items = model.groceryList columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY_LAST_COLUMN } } The View is just a BorderPane with a TableView in the centre and an HBox holding the \u0026ldquo;Lookup\u0026rdquo; controls in the bottom. You can see how the input TextField is bound to Model.listName bidirectionally, and the ObservableList held in Model.groceryList becomes the items in the TableView.\nWe\u0026rsquo;ll talk about this some more later, but note that the EventHandler on the Button invokes the Runnable passed from the Controller as listFetcher.\nThe Interactor # class Interactor(private val model: Model) { private val listService = GroceryListService() fun getList(): List\u0026lt;GroceryItem\u0026gt; { return if (model.listName.value.isEmpty()) { listService.fetchList(\u0026#34;DEFAULT\u0026#34;) } else { listService.fetchList(model.listName.value) } } fun updateModelAfterFetch(groceries: List\u0026lt;GroceryItem\u0026gt;) { model.groceryList.setAll(groceries.map { createGroceryModel(it) }) } private fun createGroceryModel(groceryItem: GroceryItem) = GroceryModel().apply { name.value = groceryItem.itemName department.value = groceryItem.theDepartment quantity.value = groceryItem.howMany } } Things to note about the Interactor:\nThe function getList() contains code to handle a blank list name.\nThis is because the manner that an empty list name is dealt with is something that is very specific to this application. It\u0026rsquo;s not defined at the service level. Some other application might want to return an empty list, or maybe generate an error warning.\nThe Interactor knows about GroceryItem, which is a \u0026ldquo;Domain Object\u0026rdquo;. There are no usages of GroceryItem in the Controller, Model or View.\nThe Interactor does not compose REST calls, or SQL statements. It simply asks the Service to get the information, without any knowledge of how it will do it.\nThe Interactor takes the Domain Objects from the Service, and converts them into Presentation Model objects, which are then put into the Model.\nThe Service Layer (Domain) Elements # We are just going to simulate the operation of a Service here, since we\u0026rsquo;re not going to create a database or access any remote web API\u0026rsquo;s to retrieve data:\nclass GroceryListService { fun fetchList(listName: String): List\u0026lt;GroceryItem\u0026gt; { Thread.sleep(2000) return listOf( GroceryItem(\u0026#34;Celery\u0026#34;, Department.VEGGIES, 10), GroceryItem(\u0026#34;Milk\u0026#34;, Department.DAIRY, 4), GroceryItem(\u0026#34;Grapes\u0026#34;, Department.FRUITS, 230) ) } } data class GroceryItem(val itemName: String, val theDepartment: Department, val howMany: Int) We\u0026rsquo;ve also defined our Domain Object, GroceryItem here. The most important point is that GroceryItem is NOT just a POJO representation of JSON data from some external source. It might actually be created that way, this definition of GroceryItem is related to whatever layour of our Service interacts with its client applications. The Service might be accessed by dozen of different screens in our application, and they may or may not expect to send or receive GroceryItem to or from the Service. But they all know how to deal with GroceryItem if they get it.\nSome More About this Example # This application will run. It looks like this:\nAll told, there\u0026rsquo;s about 100 lines of code in this example (I counted), and this is way more complete than any example you\u0026rsquo;ll find for MVC or MVVM out there on the web. Let\u0026rsquo;s look at what this does:\nIt actually runs. It handles background threading via Task It has an actual Service layer. The Service layer simulates external services completely hidden from the MVCI framework. It has some business logic. The MVCI framework part of this example is 100% operational.\nThe only simulated part is the Service layer which is external to the framework. This means that you can absolutely test this framework without connecting to an actual external database or REST API. Coupling and Dependencies in MVCI # This is quick guide, so we won\u0026rsquo;t have a lot of theory. However, the main point of any framework is to limit and control the coupling in your application. So it\u0026rsquo;s important to understand how MVCI does this, and where the depencies exist.\nMVCI is Almost a Closed System # The first thing that you need to understand is that the primary method of all coupling is via public methods (or public fields). Every time that you introduce a public method, you\u0026rsquo;ve tied yourself down just a little bit more. A class with no public methods cannot be coupled to (or depended on).\nIn MVCI the Model, the Interactor and the ViewBuilder are all private to the framework. There\u0026rsquo;s no way for any outside class to get a reference to any of these three classes. That means that there\u0026rsquo;s no way that any outside class can call any public methods in these three classes, making them effectively private to the framework.\nThere are exactly two truly public methods in MVCI, and they are both in the Controller. The first is the constructor for the Controller, which has to be public in order to create the framework in an application. It is possible to create dependencies here by specifying constructor parameters.\nThe second externally visible method is the Controller.getView() method, which returns the View, which is always just a vanilla JavaFX Region or Region subclass.\nThis is literally the bare minimum coupling that you can have and still have a functional framework.\nCoupling Through the Model # This is the key element of coupling in MVCI. Every other component can see the Model and depends on its visible structure.\nClearly, therefore, any visible changes to the Model have the potential to break some other component of the framework. At the same time, changes can often be made to the other components without affecting any other part as long as they continue to work with the Model without changing its structure.\nIn practice, this gives a fair degree of latitude. Internal changes can be made to the Model, as long as the public methods remain unchanged.\nCoupling Between the Controller and the ViewBuilder # The View with JavaFX is almost always a Region or a standard subclass of Region, and will only have the public methods of Region The result is that there\u0026rsquo;s no coupling introduced via the View itself.\nHowever, the ViewBuilder is custom but you can see that it is instantiated as Builder\u0026lt;Region\u0026gt;, which has only one public method, Builder.build().\nAt first glance, it would seem that the View needs to have dependencies on the Controller in order to invoke activity in the Interactor. To avoid this two techniques are used; \u0026ldquo;Dependency Inversion\u0026rdquo; (or \u0026ldquo;Inversion of Control\u0026rdquo;, IoC), and \u0026ldquo;Dependecy Injection\u0026rdquo;.\nThis is done via the constructor of the ViewBuilder. In the example it\u0026rsquo;s: ViewBuilder(private val model: Model, private val listFetcher: () -\u0026gt; Unit). It\u0026rsquo;s the second parameter that does the work, listFetcher : () -\u0026gt; Unit, which is the Kotlin equivalent of a Runnable, but we could have specified a Consumer or a BiConsumer here.\nThe important thing is that listFetcher is a general purpose functional interface that \u0026ldquo;inverts\u0026rdquo; the dependency. Now, instead of the ViewBuilder needing a reference to the Controller in order to call one of its public methods, the Controller needs to know what functional element it needs to pass to the ViewBuider.\nEssentially the ViewBuilder, by having this constructor parameter, is saying, \u0026ldquo;Anyone can instantiate me, but you need to give me this functional element in order to do so\u0026rdquo;. That functional element has to implement the specified interface, but there\u0026rsquo;s no knowledged passed on to the ViewBuilder about what it does or how it does it.\nAnd yes, the Controller has to supply something that responds meaningfully to the context in the View is going to invoke it. But, at the end of it all, the View/ViewBuilder knows zero about the Controller, and the Controller only knows that the ViewBuilder is a Builder\u0026lt;Region\u0026gt; and requires a particular functional element as a constructor parameter.\nCoupling Between the Controller and the Interactor # This is the biggest coupling after the Model itself.\nIn order to work, the Interactor needs to expose a number of public methods that can be called from the Controller. In the example we have Interactor.getList() and Interactor.updateModelAfterFetch().\nYou should note, however, that all of the coupling inside MVCI is outwards from the Controller. The Controller knows what parameters to pass to the ViewBuilder, and it knows which methods to call in the Interactor. Since the Controller instantiates the other elements, it also already has to know their types.\nOn the other side, neither the ViewBuilder nor the Interactor have any knowledge at all about even the existence of the Controller or each other.\nWhen you are building your own applications, you want to keep things this way. Try to stick to these \u0026ldquo;rules\u0026rdquo;:\nDon\u0026rsquo;t add public methods to the Controller, as they will become visible outside the framework and become points for coupling. Don\u0026rsquo;t pass references to the Controller to the ViewBuilder or the Interactor. Don\u0026rsquo;t pass references to each other between the Interactor and the ViewBuilder. Coupling Between the Interactor and the Services # The last piece of coupling is between the Interactor and the Services it will use. Once again, this coupling is unavoidable if you want you application to actually \u0026ldquo;do\u0026rdquo; something. But you can keep it strictly one-way. That is to say, the Interactor will have dependencies on its Services, but the Services won\u0026rsquo;t have any dependencies on the Interactor.\nThere are two main points of coupling:\nThe \u0026ldquo;Domain Objects\u0026rdquo;\nClearly the structure of the Domain Objects is a major dependency in this part of the application. However, it\u0026rsquo;s also the main way of controlling the coupling, as it isolates the Interactor from changes in the external services. Even if a database structure is radically altered, it remains the Services\u0026rsquo; responsibility to continue to return valid Domain Objects in the same structure as always.\nThe Services\u0026rsquo; APIs\nThe public methods of the Service are the other coupling at this level. There\u0026rsquo;s nothing that says that a Service has to be designed for use by multiple MVCI frameworks. You can have a Service specifically tailored to work with a specific framework, but you still want to isolate the inner workings of the Service from the MVCI framework.\nConclusion # I cannot stress how ridiculously simple it is to implement MVCI. Take these 15 lines of code:\nclass SkeletonModel {} class SkeletonInteractor(private var model: SkeletonModel){} class SkeletonViewBuilder(private var model: SkeletonModel): Builder\u0026lt;Region\u0026gt; { override fun build(): Region { TODO(\u0026#34;Not yet implemented\u0026#34;) } } class SkeletonController() { private val model = SkeletonModel() private val interactor = SkeletonInteractor(model) private val viewBuilder:Builder\u0026lt;Region\u0026gt; = SkeletonViewBuilder(model) fun getView() = viewBuilder.build() } This took me 3 minutes and 12 seconds to type in from scratch (yes, I timed it). If you put it together with this:\nclass SkeletonApp : Application() { override fun start(stage: Stage) { stage.scene = Scene(SkeletonController().getView()) stage.show() } } fun main() = Application.launch(SkeletonApp::class.java) It will run. Okay, it will crash on the TODO in SkeletonViewBuilder.build(), but it will compile and run.\nThis is how I start every screen that I build. From here I usually start building the layout. As I add components to the layout, then I add the data elements to the Model as I go. In this way I have a working layout at all times. Sometimes I\u0026rsquo;ll just hard code starting values in the constructors of the fields in the Model, sometimes I\u0026rsquo;ll initialize them in the init{} block in the Interactor.\nAt the beginning, I\u0026rsquo;m interested in the Interactor as a tool to test my GUI, so the methods I create there are designed to simulate business logic that generates data sets that challenge my layout. Later, I can address actual business logic without worrying too much about how it impacts the GUI.\nIn this way, MVCI doesn\u0026rsquo;t just organize my projects, it actually becomes a tool that accelerates and simplifies development.\nAll this stuff is just second nature to me now, and even if I\u0026rsquo;m doing something trivial I\u0026rsquo;ll start with those same 15 lines of code. It\u0026rsquo;s just that useful.\nHopefully, you\u0026rsquo;ll find it useful too.\n","date":"1 August 2024","externalUrl":null,"permalink":"/javafx/techniques/mvci/2024-08-01-mvci-quick-guide/","section":"JavaFX","summary":"Introduction - Why Use a Framework? # Frameworks are all about organizing your application so that it will be easy to maintain, enhance and expand over time.\n","title":"JavaFX: Quick Guide to MVCI","type":"javafx"},{"content":"","date":"1 August 2024","externalUrl":null,"permalink":"/javafx/techniques/mvci/","section":"JavaFX","summary":"Model-View-Controller-Interactor (MVCI) is a framework I designed specifically to work with Reactive JavaFX.","title":"Model-View-Controller-Interactor","type":"javafx"},{"content":"","date":"6 April 2024","externalUrl":null,"permalink":"/javafx/projects/","section":"JavaFX","summary":"Complete projects that show how to build real applications with Reactive JavaFX","title":"Example Projects","type":"javafx"},{"content":" Introduction # This was originally posed as a question on Reddit about writing vast amounts of Text onto a Canvas, because \u0026ldquo;TableView cannot handle the huge amounts of data to be displayed\u0026rdquo;. Of course, trying to have giant Canvas in a ScrollPane with thousands of lines of data is absolutely not the way to go. On the other hand TableView is exactly designed to handle large data sets while minimizing the use of screen resources.\nOnce this issue was sorted out, the OP continued on with TableView, but it seemed to me that this was better done as a ListView, and I wondered what it would take to do it. So, here it is.\nSource Code # The source code for this project can be found here\nFeatures # There\u0026rsquo;s a set of CheckBoxes at the bottom of the screen that control the display of the various formats of the data; octal, binary, decimal and character. Each ListCell has an \u0026ldquo;Edit\u0026rdquo; Button which controls the display of the bottom row of the ListCell which has a TextField and a \u0026ldquo;Commit\u0026rdquo; Button.\nThe display is broken up into 16 byte ListCells and each byte of the 16 is displayed in a VBox in the various formats.\nAll of the actual editing is done through the TextField in the bottom of the ListCell. When editing is happening and the TextField has focus, the hexadecimal display of byte closest to that of the caret in the TextField is highlighted. This isn\u0026rsquo;t perfect, but early testing revealed that without this indicator it was a little difficult to determine where the caret location corresponded to the byte by byte display.\nProgramming Notes # Here\u0026rsquo;s an overview of how the application is designed, with a few particulars highlighted\u0026hellip;\nFramework # Even though this application simply generates a few thousand bytes of random data, it has been designed with an Model-View-Controller-Interactor framework. There\u0026rsquo;s no file handling in this example, so there\u0026rsquo;s no Service level, and the Interactor generates the dummy data.\nEven given the simplicity of this, you can still see how the MVCI framework works.\nThe Data Model # The data model for the application is composed of a single ObservableList\u0026lt;HexRow\u0026gt;. HexRow itself is composed of an Integer to hold the file offset of the first byte in the row, and a ByteArray to hold the actual data.\nNote that neither of these to elements is an Observable type of value. There is some overhead to Observable data types, as they have to track Listeners and bound values and invalidation and so on. Generally this isn\u0026rsquo;t an issue, but if there are millions of bytes of data, each wrapped in an Observable class, it might get out of hand.\nWhat do you lose when the HexRow elements are not Observable? Not a lot for this kind of application. The ListCells are all autonomous, and the Observable elements are all in the local data model inside the ListCell. This is updated via updateItem when the ListCell is recycled, and the code in the \u0026ldquo;Commit\u0026rdquo; Button updates item directly.\nWhat is lost is the ability to have the ListCells all automatically redisplay if bytes are added or removed from any row. However, handling this case is going to take a fairly heavy amount of processing regardless, as each HexRow will need to be updated, taking data from one neighbour or another in turn down to the end of the file. In which case the whole list can probably be invalidated, which should cause the ListCells to reload.\nByteArray is interesting itself. It seems to be a Kotlin only data type (yeah Kotlin!) and has some nifty methods to convert to a String in hexadecimal format. It made most of the coding pretty easy.\nData Conversions # All of the data conversions are done off the hexadecimal String representation of the ByteArray held in the StringProperty encapsulated in ObservableValue.map() functions. This means that these values stay in lock-step as the StringProperty is edited, or the value in the StringProperty is updated via updateItem().\nThe primary method is called extractHex which pulls two digits from the String based on an index. If the String isn\u0026rsquo;t long enough, it returns null, so this method returns a String?. The subsequent conversions need to deal with this nullability. Kotlin has a nifty function, String.hexToInt() that takes the String representation of a hexadecimal number and converts it into an Integer, which can then by converted in a different base to display octal or binary through Int.toString(radix).\nprivate fun extractHex(bigString: String, index: Int): String? = if (bigString.length \u0026gt; ((index * 2) + 1)) bigString.substring(index * 2, (index * 2) + 2) else null @OptIn(ExperimentalStdlibApi::class) private fun toRadix(hexString: String, radix: Int) = hexString.hexToInt().toString(radix) private fun extractRadix(bigString: String, index: Int, radix: Int) = extractHex(bigString, index)?.let { toRadix(it, radix) } ?: \u0026#34;\u0026#34; @OptIn(ExperimentalStdlibApi::class) private fun extractChar(bigString: String, index: Int) = extractHex(bigString, index)?.hexToInt()?.toChar()?.toString() ?: \u0026#34;\u0026#34; You can see that the String.hexToInt() function is still experimental, but it seems to work well.\nControlling the Rows # There\u0026rsquo;s a data structure called HexCellControlModel. This is composed of a set of BooleanProperties. The main View instantiates this Model and then passes it to the HexEditorCells in their constructors. This way, each HexEditorCell will have a reference to the single Model instance. Then the CheckBoxes at the bottom of the GUI have their Selected Property bound to one of these BooleanProperties in HexCellControlModel.\nInside the HexEditorCell the Labels that display the values have their Visible and Managed Properties bound to one of the BooleanProperties in HexCellControlModel.\nIn this way, all the ListCells respond in unison to each of the CheckBoxes.\nThe Heading Row # The Labels across the top of the ListView are actually not part of the ListView itself. The entire View is a BorderPane with the centre occupied with just the ListView. The bottom has an HBox with the CheckBoxes in it, and the top has an HBox with the heading Labels in it.\nAll of the VBoxes that make up the main part of the HexEditorCell have a fixed minimum width, and the Labels in the heading row follow along with that.\nThere\u0026rsquo;s one quirk, the binary display takes significantly more horizontal space than the other modes, so when it is visible the minimum width has to increase to hold this. This is achieved by using ObservableValue.map() to convert the BooleanProperty into a ObservableDoubleValue that could then be bound to the MinWidth Property of the Labels.\nEditing # Originally, I pondered the idea of having context menus on the Labels for the hex values, and that\u0026rsquo;s still something that could be added. Instead, I reasoned that the byte by byte display was good for seeing and understanding the data, but it was probably just easiest to edit it as a String in a TextField.\nTo make it a bit easier to follow how the editing in the TextField related to the byte by byte display, a PseudoClass was added to the hex Labels to indicate that they corresponded to the values near the caret in the TextField. This PseudoClass is styled to put a border around the Label and change the background to a light blue. The border needed to be given insets of \u0026ldquo;-2\u0026rdquo; on the top and bottom to avoid having the Label grow vertically when the borders were added and displacing the contents of that column downwards.\nThe TextField as supplied in this example doesn\u0026rsquo;t have a Filter or TextFormatter to prevent non-hex digits from being added. This is something that would be needed to put this into production.\nConclusion # Virtually all of the cool stuff is inside the HexEditorCell, as expected. My original advice to the OP was:\nThink if you just had 16 bytes of data to edit on a screen. Not millions of bytes, just 16 and a whole screen to put a layout on.\nWould you carve it up into 16 independent cells? Probably not.\nWhat I would probably do is create an HBox with 16 VBoxes inside. Each VBox would hold a Label, one each for hex, octal, binary, decimal and character.\nAnd that\u0026rsquo;s exacly the process that I took here. Think of how you\u0026rsquo;d want to see or edit just a handful of data, and design a layout to serve that purpose. Then put it into a ListCell and let the ListView virtualization do the work of loading the data into it.\n","date":"6 April 2024","externalUrl":null,"permalink":"/examples/hex-editor/","section":"JavaFX","summary":"Here’s a sample application that uses a ListView to display and edit bytes in a data file, showing the values in hex, octal, binary, decimal and as characters.","title":"Example: Hexadecimal Editor","type":"javafx"},{"content":" Introduction # This tutorial was inspired by this question (and the answer) on StackOverFlow. There was a lot of back and forth about the OP\u0026rsquo;s strange ideas about how ListView and TableView work, but the answer posted by Sai Dandem was very good and forms the basis for what we\u0026rsquo;re going to look at in this article.\nThe Initial Problem # Let\u0026rsquo;s take a look at a screen shot that shows the issues clearly:\nThe most obvious problem (there are others) is that the border of the Region runs right through the Label at the top. That was the subject of the StackOverflow question; how to find a way such that the border at least appeared to stop before it went through the Label.\nIt\u0026rsquo;s not totally relevant, but here\u0026rsquo;s the code I used to generate this:\nclass LabelBoxApplication : Application() { override fun start(stage: Stage) { val scene = Scene(createContent(), 320.0, 240.0).apply { addStyleSheet(\u0026#34;/css/LabelBox.css\u0026#34;) addWidgetStyles() } stage.title = \u0026#34;LabelBox Demo\u0026#34; stage.scene = scene stage.show() } fun createContent() = BorderPane().apply{ center = VBox(Label(\u0026#34;This is the Title\u0026#34;).apply{ styleClass += \u0026#34;box-label\u0026#34; }).apply{ styleClass += \u0026#34;label-box\u0026#34; padding = Insets(-10.0, 0.0, 0.0, 8.0) } padWith(20.0) } } fun main() { Application.launch(LabelBoxApplication::class.java) } The main thing to note about this approach is that a negative Inset is used to move the Label up and into the area occupied by the border.\nA First Solution # This was the stylesheet that was used in this example:\n.label-box { -fx-border-width: 2px; -fx-border-color: black; -fx-border-radius: 6px; } .box-label { -fx-font-size: 16px; } We can make one small change to it, adding a background colour to the Label, which by default has a transparent background.\n.label-box { -fx-border-width: 2px; -fx-border-color: black; -fx-border-radius: 6px; } .box-label { -fx-font-size: 16px; -fx-background-color: white; } Now we get this:\nWhich is all very good until we change the background color of the BorderPane to some other colour:\nThere is one last trick we can try, which is to change the -fx-background-color of the Labal to \u0026ldquo;inherit\u0026rdquo;, but that yields this:\nWhich doesn\u0026rsquo;t work because the Label inherits from the VBox which has a transparent background. However, if we set the VBox to something other than transparent, like \u0026ldquo;inherit\u0026rdquo;\u0026hellip;\n.label-box { -fx-border-width: 2px; -fx-border-color: black; -fx-border-radius: 6px; -fx-background-color: inherit; } .box-label { -fx-font-size: 16px; -fx-background-color: inherit; } .wrapper-region { -fx-background-color: blue; } Then it will work again:\nComplex Backgrounds # If all you\u0026rsquo;re going to use this LabelBox with is simple backgrounds, then you\u0026rsquo;re done. This will work fine, and you need not do any more work.\nHowever\u0026hellip;\nIf you are going to use this with any kind of complex background, like a gradient or an image background, then you\u0026rsquo;ll have problems. Here\u0026rsquo;s what it looks like with a gradient:\nThe problem is that \u0026ldquo;inherit\u0026rdquo; causes the background to repeat from scratch in each region, which is not what we want.\nMaking a Section of the Border Transparent # It\u0026rsquo;s clear that obscuring the border by making the Label region non-transparent is not the answer to the problem. We need to make the border transparent in the same region that we have the Label.\nRefining the Layout # Before we get to the transparent border section, let\u0026rsquo;s take another look at our layout. Initially, it looked like this, restyled to show the regions better:\nThe green section is entire Pane for our LabelBox and the blue area is the content. Notice how the Label pokes out over the top of the Pane? That\u0026rsquo;s something we need to think about. We need to decide if we\u0026rsquo;re OK having some of our Pane's stuff existing outside its boundaries.\nIt can be a problem. Take a look at this:\nHere we see the LabelBox duplicated in a VBox. You can see how the Label on the lower LabelBox intrudes into the space occupied by the upper one. This is something that you can program around, but it\u0026rsquo;s probably better not to. It just makes the LabelBox trickier to deal with. So we\u0026rsquo;ll rearrange the layout to solve this:\nHere, the border has been added back in so that you can see how it now has to come down from the top in order to meet with the middle of the Label text. The Label is now completely contained within the boundaries of the Pane. You can stack them, and it will still look good without requiring any gap:\nUsing Clipping to Hide Some of the Border # JavaFX supports the idea of a \u0026ldquo;clipping\u0026rdquo; region, which defines the visual area in which an element will appear on the screen. In this case, we just want the border to be affected, and we need to define a shape that includes just the area that the Label will occupy. We want something like this:\nThe translucent grey box is the area that we want to restrict the border to show. We can create this shape by creating two Rectangles, one that covers then entire area, and the other that is just a little bigger than the Label, and in the same location as the Label. Then we call Shape.subtract() to remove the smaller Rectangle from the larger one.\nThere\u0026rsquo;s just one problem with this\u0026hellip;\nWe need to take in consideration that it\u0026rsquo;s just the border that we want to clip. This means that we cannot just add a border to the Pane and clip the Pane because it will also clip out the background of the Pane if it\u0026rsquo;s not transparent. So we\u0026rsquo;ll need to create a separate Pane that has nothing but a border, and then apply the clipping to this new Pane.\nLet\u0026rsquo;s look at the code:\nclass LabelBox2Application : Application() { override fun start(stage: Stage) { val scene = Scene(createContent(), 320.0, 240.0).apply { addStyleSheet(\u0026#34;/css/LabelBox.css\u0026#34;) addWidgetStyles() } stage.title = \u0026#34;LabelBox Demo\u0026#34; stage.scene = scene stage.show() } fun createContent() = BorderPane().apply { padding = Insets(20.0, 20.0, 20.0, 20.0) styleClass += \u0026#34;wrapper-region\u0026#34; center = VBox(20.0, createPane(), createPane()) } private fun createPane(): Region { val label = Label(\u0026#34;This is the Title\u0026#34;).apply { layoutX = 10.0 styleClass += \u0026#34;box-label\u0026#34; } val borderPane = Pane().apply { styleClass += \u0026#34;border-pane\u0026#34; layoutYProperty().bind(label.heightProperty().divide(2.0)) } val content = StackPane().apply { layoutX = 5.0 layoutYProperty().bind(label.heightProperty().add(2.0)) children += Button(\u0026#34;This is Some Content\u0026#34;) } val pane = Pane().apply { minHeight = 200.0 children += listOf(borderPane, label, content) needsLayoutProperty().addListener(InvalidationListener { Platform.runLater { setClipping(borderPane, label) } }) borderPane.minWidthProperty().bind(widthProperty()) borderPane.minHeightProperty().bind(heightProperty().subtract(label.heightProperty().divide(2))) content.minWidthProperty().bind(widthProperty().subtract(10.0)) content.minHeightProperty().bind(heightProperty().subtract(label.heightProperty().add(6.0))) borderPane.widthProperty().addListener(InvalidationListener { setClipping(borderPane, label) }) borderPane.heightProperty().addListener(InvalidationListener { setClipping(borderPane, label) }) label.widthProperty().addListener(InvalidationListener { setClipping(borderPane, label) }) label.heightProperty().addListener(InvalidationListener { setClipping(borderPane, label) }) } return pane } private fun setClipping(nodeToClip: Region, areaToHide: Region) { val rectangle = Rectangle(nodeToClip.width, nodeToClip.height + nodeToClip.layoutY).apply { layoutX = nodeToClip.layoutX } val clip = Rectangle(areaToHide.width + 5.0, areaToHide.height).apply { layoutX = areaToHide.layoutX layoutY = areaToHide.layoutY } nodeToClip.clip = Shape.subtract(rectangle, clip) } } fun main() { Application.launch(LabelBox2Application::class.java) } The Layout # All the layout code is in createPane(). You can see that there are four elements: the Label; the Pane with the border; the StackPane with the content; and the Pane to hold it all.\nWe\u0026rsquo;ve changed the main Region from the VBox in the first example to a Pane. This is because we\u0026rsquo;re going to need to monkey with the layout positions of the elements in any event, so we don\u0026rsquo;t need the automatic layout of VBox here. It will also make it easier to translate to a custom class (you\u0026rsquo;ll see).\nPane just piles its children on top of each other in the top left corner of the Pane. So we need to position everything manually.\nThe label stays at the top, but it needs to be tucked over to the right to avoid the corner arc of the border. The borderPane stays at the left, but needs to move down 1/2 the height of the label. The contentPane needs to move down the height of the label plus a little bit, and also to the right to avoid the border. This positioning just sets the top left corner of each of the elements. Additionally, the borderPane needs to have its width and height bound to the containing Pane, with the height adjusted to account for how much it dropped down at the top to meet the middle of the label.\nThe contentPane also needs to be bound to the size of the containing Pane but adjusted to stay within the borderPane without touching the border.\nThe rest of the code deals with the clipping\u0026hellip;\nThe Clipping # The clipping is done by calling Node.setClip(), and, in and of itself, it\u0026rsquo;s pretty simple. The biggest issue to deal with is the fact that our clipping Shape is not going to appear on the SceneGraph. This means that it won\u0026rsquo;t be managed by the Layout Manager when the layout changes.\nWhat this really means is that we cannot us bind() to bind the locations and dimensions of the Shape components to the elements that are on the screen. Bummer.\nWhat we need to do is to use InvalidationListeners to trigger a recalculation of the clipping whenever the dimensions of the label or the borderPane change. This is why we have the four Listeners added at the end of the layout code, each of which just calls the clipping function.\nThe setClipping function itself is very simple. The two Rectangles are created with correct sizes, they are positioned properly, the smaller rectangle is subtracted from the larger one and the Node.setClip() is called on the borderPane.\nThere\u0026rsquo;s one final oddity, and it\u0026rsquo;s due to the fact that we cannot just bind the clipping shapes to the label and borderPane dimensions. Our Listeners are only triggered when the dimensions change after the elements are on the SceneGraph. The initial values aren\u0026rsquo;t captured, and any attempt to look at them just gives you zeros. What we need to do is to capture the moment that they are added to the SceneGraph and call the clipping routine after they have their dimensions established. We can catch that by putting a Listener on the needsLayoutProperty of the containing Pane. This flips to true when it\u0026rsquo;s ready to build the layout. Unfortunately, this happens before the layout is built, so we need to put our call to the clipping routine inside a call to Platform.runLater() which will run the clipping routine as a separate job after the layout is completed.\nThe Result # That\u0026rsquo;s it, this program creates a \u0026ldquo;builder\u0026rdquo; function that creates a LabelBox that works with complex backgrounds:\nCreating a Custom Class # The only real difference between Pane and Region is that Pane exposes getChildren() as a public method, while it\u0026rsquo;s protected in Region. This is an important distinction when creating custom classes, but it means that our layout code, which is based on Pane can be moved straight out of the box into a custom class extending Region.\nLet\u0026rsquo;s do that:\nclass LabelBox : Region() { init { val label = Label(\u0026#34;This is the Title\u0026#34;).apply { layoutX = 10.0 styleClass += \u0026#34;box-label\u0026#34; } val borderPane = Pane().apply { styleClass += \u0026#34;border-pane\u0026#34; layoutYProperty().bind(label.heightProperty().divide(2.0)) } val content = StackPane().apply { layoutX = 5.0 layoutYProperty().bind(label.heightProperty().add(2.0)) children += Button(\u0026#34;This is Some Content\u0026#34;) } minHeight = 200.0 children += listOf(borderPane, label, content) needsLayoutProperty().addListener(InvalidationListener { Platform.runLater { setClipping(borderPane, label) } }) borderPane.minWidthProperty().bind(widthProperty()) borderPane.minHeightProperty().bind(heightProperty().subtract(label.heightProperty().divide(2))) content.minWidthProperty().bind(widthProperty().subtract(10.0)) content.minHeightProperty().bind(heightProperty().subtract(label.heightProperty().add(6.0))) borderPane.widthProperty().addListener(InvalidationListener { setClipping(borderPane, label) }) borderPane.heightProperty().addListener(InvalidationListener { setClipping(borderPane, label) }) label.widthProperty().addListener(InvalidationListener { setClipping(borderPane, label) }) label.heightProperty().addListener(InvalidationListener { setClipping(borderPane, label) }) } private fun setClipping(nodeToClip: Region, areaToHide: Region) { val rectangle = Rectangle(nodeToClip.width, nodeToClip.height + nodeToClip.layoutY).apply { layoutX = nodeToClip.layoutX } println(areaToHide.width) val clip = Rectangle(areaToHide.width + 5.0, areaToHide.height).apply { layoutX = areaToHide.layoutX layoutY = areaToHide.layoutY } nodeToClip.clip = Shape.subtract(rectangle, clip) } } All we\u0026rsquo;ve done here is wrap the two methods from our builder code into a class extending Region and rename the createPane() method it init.\nAnd then to run it\u0026hellip;\nclass LabelBox3Application : Application() { override fun start(stage: Stage) { val scene = Scene(createContent(), 320.0, 240.0).apply { addStyleSheet(\u0026#34;/css/LabelBox.css\u0026#34;) addWidgetStyles() } stage.title = \u0026#34;LabelBox Demo\u0026#34; stage.scene = scene stage.show() } fun createContent() = BorderPane().apply { padding = Insets(20.0, 20.0, 20.0, 20.0) styleClass += \u0026#34;wrapper-region\u0026#34; center = VBox(20.0, LabelBox(), LabelBox()) } } fun main() { Application.launch(LabelBox3Application::class.java) } It just runs and looks exactly the same as the last screen snap.\nRefining the Custom Class # There\u0026rsquo;s a bit of cleanup we can do, now that we have everything encapsulated into a new class\u0026hellip;\nclass LabelledPane : Region() { private val rectangle = Rectangle() private val clipZone = Rectangle() private val label = Label(\u0026#34;This is the Title\u0026#34;).apply { layoutX = 10.0 styleClass += \u0026#34;pane-label\u0026#34; widthProperty().addListener(InvalidationListener { setClipping() }) heightProperty().addListener(InvalidationListener { setClipping() }) } private val borderPane = Pane().apply { styleClass += \u0026#34;pane-border\u0026#34; minWidthProperty().bind(this@LabelledPane.widthProperty()) minHeightProperty().bind(this@LabelledPane.heightProperty().subtract(label.heightProperty().divide(2))) layoutYProperty().bind(label.heightProperty().divide(2.0)) widthProperty().addListener(InvalidationListener { setClipping() }) heightProperty().addListener(InvalidationListener { setClipping() }) } private val content = StackPane().apply { layoutX = 5.0 layoutYProperty().bind(label.heightProperty().add(2.0)) minWidthProperty().bind(this@LabelledPane.widthProperty().subtract(10.0)) minHeightProperty().bind(this@LabelledPane.heightProperty().subtract(label.heightProperty().add(6.0))) children += Button(\u0026#34;This is Some Content\u0026#34;) } init { minHeight = 200.0 children += listOf(borderPane, label, content) styleClass += \u0026#34;labelled-pane\u0026#34; needsLayoutProperty().addListener(InvalidationListener { Platform.runLater { setClipping() } }) } private fun setClipping() { with(rectangle) { width = borderPane.width height = borderPane.height + borderPane.layoutY layoutX = borderPane.layoutX } with(clipZone) { width = label.width + 5.0 height = label.height layoutX = label.layoutX layoutY = label.layoutY } borderPane.clip = Shape.subtract(rectangle, clipZone) } } First off, we\u0026rsquo;ve renamed the class to LabelledPane, which is more in line with JavaFX\u0026rsquo;s TitledPane which is somewhat similar.\nNext we\u0026rsquo;ve moved the components out of init{} and established them as fields of the class. This means we don\u0026rsquo;t need to pass them to setClipping(), and we can bind their dimension properties to the class properties when they are initialized. This allows init{} to be much smaller.\nFinally, the StyleClasses have been renamed, and the CSS now has the components in a structure:\n.labelled-pane { } .labelled-pane \u0026gt; .pane-label { -fx-font-size: 16px; } .labelled-pane \u0026gt; .pane-border { -fx-border-width: 2px; -fx-border-color: black; -fx-border-radius: 6px; } When this is run, it looks just like the other examples, nothing has functionally changed.\nMaking it Useful # If all you ever wanted to put into your LabelledPanes was a Button that did nothing with the caption \u0026ldquo;This is Some Content\u0026rdquo;, then we\u0026rsquo;d be done. But since probably do want to put our own content into the LabelledPane, we\u0026rsquo;ll need a way to do it, which means adding a function to the class. I haven\u0026rsquo;t been able to find a way to override Region's getChildren() method and still use it to add the other components, so I\u0026rsquo;m calling it getContent(), and it will just delegate to the getChildren() method of the StackPane called content.\nDealing With Sizing # One thing that become very quickly apparent when you try to vary the contents of the LabelledPane is that we need to handle the sizing of both the contents and the LabelledPane itself in relationship to the layout. Region has no inherent behaviour that prevents its children from extending beyond its bounds. So if the children are bigger than the size of the LabelledPane, they\u0026rsquo;ll just run outside the LabelledPane. So we\u0026rsquo;ll need to find a way to tame this.\nFor testing, I used a Label as the content with a large amount of text in it. I turned wrapping on, and Label will use ellipses to indicate if there is more text than can fit into the space provided. This is, therefore, a fairly complicated child from a layout perspective, that will challenge the design of the LabelledPane to contain it and display in an optimal manner.\nTo get some insight, I peeked into the layout code for VBox and then stole same ideas from it. In practice, this can become problematic because many of the standard JavaFX classes have private methods, and attempting to pull code out of these classes often means that you try to call private and package-protected methods from your own code in your own packages. Copying out those private methods, you find that they, in turn, call other private methods. And so on\u0026hellip;\nIn this case, though, it was possible to prune out some small snippets that do a few important things without going down a rabbit hole leading to huge amounts of code copied from VBox and Region.\nHere\u0026rsquo;s the code for the class:\nclass LabelledPane(labelText: String) : Region() { private val rectangle = Rectangle() private val clipZone = Rectangle() private val id: Int = counter++ companion object { var counter = 0 } private val label = Label(labelText).apply { layoutX = 10.0 styleClass += \u0026#34;pane-label\u0026#34; widthProperty().addListener(InvalidationListener { setClipping() }) heightProperty().addListener(InvalidationListener { setClipping() }) maxWidthProperty().bind(this@LabelledPane.widthProperty().subtract(24.0)) isWrapText = true } private val borderPane = Pane().apply { styleClass += \u0026#34;pane-border\u0026#34; minWidthProperty().bind(this@LabelledPane.widthProperty()) minHeightProperty().bind(this@LabelledPane.heightProperty().subtract(label.heightProperty().divide(2))) layoutYProperty().bind(label.heightProperty().divide(2.0)) widthProperty().addListener(InvalidationListener { setClipping() }) heightProperty().addListener(InvalidationListener { setClipping() }) } private val content = VBox().apply { layoutX = 5.0 layoutYProperty().bind(label.heightProperty().add(2.0)) maxWidthProperty().bind(this@LabelledPane.widthProperty().subtract(10.0)) } init { children += listOf(borderPane, label, content) styleClass += \u0026#34;labelled-pane\u0026#34; minHeightProperty().bind(label.heightProperty().add(content.minHeightProperty()).add(20.0)) widthProperty().addListener(InvalidationListener { prefHeight = getContentHeight() }) needsLayoutProperty().addListener(InvalidationListener { Platform.runLater { setClipping() content.maxHeightProperty().unbind() prefHeight = getContentHeight() content.maxHeightProperty().bind(heightProperty().subtract(label.heightProperty()).subtract(10.0)) } }) } private fun getContentHeight(): Double { val value = computeChildPrefAreaHeight(content, width - 6.0) return value + label.height + 10.0 } private fun computeChildPrefAreaHeight(child: Node, width: Double): Double { var snapWidth = -1.0 if (child.isResizable \u0026amp;\u0026amp; child.contentBias == Orientation.HORIZONTAL) { snapWidth = snapSizeX( boundedSize( child.minWidth(-1.0), if (width != -1.0) width else child.prefWidth(-1.0), child.maxWidth(-1.0) ) ) } return snapSizeY( boundedSize( child.minHeight(snapWidth), child.prefHeight(snapWidth), child.maxHeight(snapWidth) ) ) } private fun boundedSize(min: Double, pref: Double, max: Double): Double { return min(max(pref, min), max(min, max)) } private fun setClipping() { with(rectangle) { width = borderPane.width height = borderPane.height + borderPane.layoutY layoutX = borderPane.layoutX } with(clipZone) { width = label.width + 5.0 height = label.height layoutX = label.layoutX layoutY = label.layoutY } borderPane.clip = Shape.subtract(rectangle, clipZone) } fun getContent(): ObservableList\u0026lt;Node\u0026gt; = content.children fun labelTextProperty() = label.textProperty() } Conclusion # I think this project is neat because it\u0026rsquo;s a cool problem/idea, and you could ask the question, \u0026ldquo;Shouldn\u0026rsquo;t something like this be included in the standard JavaFX layout classes?\u0026rdquo;. And perhaps it should. But the truth is that JavaFX gives programmers all the tools that they need to create just about anything that you can think of. Yes, there are some quirky bits about this that aren\u0026rsquo;t obvious if you\u0026rsquo;re just a beginner, but it can be done.\nThere\u0026rsquo;s nothing magical about the Node classes included with JavaFX. If you look at the source code, you\u0026rsquo;ll see that it\u0026rsquo;s just standard Java code, using JavaFX tools to exactly the same stuff that\u0026rsquo;s shown here. For sure, there\u0026rsquo;s some super complicated stuff in some of the classes that will melt you brain if you try to figure it out, but it can be figured out, and it\u0026rsquo;s not necessarily any better than anything you could write yourself.\nFrom time to time, I see someone complain that \u0026ldquo;No new controls have been added to JavaFX for years\u0026hellip;\u0026rdquo;. There\u0026rsquo;s never any mention of what they think is missing, so I guess they\u0026rsquo;re just complaining to look smart or something. Part of the reason for the general nature of that statement is that if there was something missing, and they knew enough, or were skilled enough to be affected by it, they should have enough knowledge and skill to build it themselves.\nThis LabelledBox control isn\u0026rsquo;t perfect, I noticed some quirky things happening with those Labels when I resized the window causing the wrapping and truncating to change. But it is good enough for a lot of things. I\u0026rsquo;m sure that when I use it in layouts in the future, I\u0026rsquo;ll find more problems. And maybe I\u0026rsquo;ll fix them. Or not.\nThe other thing that\u0026rsquo;s cool about this project is that it shows how dead simple it is to go from factory/builder method customizing a layout to creating a full blown custom class. As a builder, we used Pane because we need access to a public getChildren(), but as a custom class, we extend Region and then we have access to its protected getChildren() method. Other than that, though\u0026hellip;not too much. A little bit more work was required to make it act more like a standard JavaFX Node, especially with regards to styling was just about all it took.\n","date":"25 March 2024","externalUrl":null,"permalink":"/javafx/techniques/custom_controls/label_pane/","section":"JavaFX","summary":"Another example of creating a custom class.  This time to build a container with a border and a Label that sits on the border.","title":"Creating a Custom Component: LabelledPane","type":"javafx"},{"content":" The JavaFX Layout Techniques You Need to Master # Understanding Nodes and Properties and StyleSheets is one thing, but there are also techniques that you\u0026rsquo;ll need to learn in order to create JavaFX applications.\n","date":"25 March 2024","externalUrl":null,"permalink":"/javafx/techniques/custom_controls/","section":"JavaFX","summary":"These are the essential layout techniques you need to learn with JavaFX.","title":"Custom Controls","type":"javafx"},{"content":" Introduction # In previous articles, we\u0026rsquo;ve looked at how to create custom components by using builders and factory methods, and then by extending Region to create a custom component class. If you\u0026rsquo;re writing JavaFX applications, then extending Region is all that you ever need to do to create a custom component, because it\u0026rsquo;s a pretty complete solution.\nThe idea of \u0026ldquo;skins\u0026rdquo; was mentioned briefly, but not pursued because you don\u0026rsquo;t really need it when you\u0026rsquo;re creating your own custom components for your own use.\nBut\u0026hellip;What\u0026rsquo;s the point of skins then?\nThe idea behind a skinnable Control is that you can change its visual representation without messing with the workings of the Control itself. Virtually all of the standard JavaFX Controls are built as skinnable components.\nNow, you may not ever want to create a brand new skinnable Control, but you very well might want to reskin one of the existing Controls. That\u0026rsquo;s what we\u0026rsquo;re going to look at here.\nIn this article we\u0026rsquo;re going to look at ToggleButton and change it so that it doesn\u0026rsquo;t look like a Button any more, but something entirely different.\nDisclaimer # There aren\u0026rsquo;t any \u0026ldquo;official\u0026rdquo; tutorials or documentation about how to go about creating a skin. The closest I\u0026rsquo;ve seen is a placeholder on the OpenJDK Wiki for Upcoming Topics, but that could be years old by now and still not coming. The best way that I know how to figure it out is to look at the source code for JavaFX and other libraries that have created skins.\nBuilding a skin is a bit different from creating a layout in a Region for a custom component. Some of the methods that you need to use are designed to be invoked - often indirectly - from the Layout Manager. In this respect, some of your coding can be considered to be \u0026ldquo;under the hood\u0026rdquo; from a JavaFX perspective.\nUndoubtedly, there are rules about how things should be done what\u0026rsquo;s allowed in some of these methods but you won\u0026rsquo;t find these spelled out anywhere and, once again, you have to figure them out for yourself.\nThis is the first time I\u0026rsquo;ve tried to do something like this, and I\u0026rsquo;ll be the first to admit that I don\u0026rsquo;t know everything about JavaFX skins and custom controls.\nHowever, the code in this tutorial works, and the result is a ToggleButton that acts like a ToggleButton (but doesn\u0026rsquo;t look like one), and that doesn\u0026rsquo;t break when you try to do normal ToggleButton stuff with it. Perhaps that\u0026rsquo;s enough.\nWhat is a Skin? # The Skin/Skinnable concept in JavaFX is an implementation of the Model-View-Controller framework specialized for JavaFX Controls.\nThe Skin itself takes the role of the View. Skin is an interface, that has only 3 methods: dispose(), getNode() and getSkinnable(). Of these, the most interesting is getSkinnable() which returns a reference to the Control that the Skin is attached to.\nSkinnable is the complimentary interface implemented by Controls that also has only 3 methods: getSkin(), setSkin() and skinProperty().\nNone of this appears to shed a light on how Skins work, however. And that\u0026rsquo;s about as far as you\u0026rsquo;ll get with the JavaDocs.\nLet\u0026rsquo;s look at how Skins work\u0026hellip;\nEvery Skinnable class has two parts, the Skin and the Control itself, which is the Skinnable. Keeping with the comparison to MVC, the Skinnable comprises the Model and the Controller in a single unit. The OpenJDK Wiki says the Control is the Model, and the Skin is the Controller + View, but I disagree with that (for what it\u0026rsquo;s worth).\nThe Model for these controls is composed of the Properties that you\u0026rsquo;re used to dealing with in JavaFX. These are things like the TextProperty, MinWidthProperty, OnEventProperty and VisibleProperty. The Skinnable/Controller contains all of the methods for accessing these Properties from outside the Control, like getText(), setMinWidth(), and visibleProperty().\nMost, if not all, of these Properties are shared with the Skin/View, so that they can be used in the component\u0026rsquo;s layout.\nYou instantiate one of these objects through the Skinnable part - the Control itself. Something like new Button(\u0026quot;Click Me\u0026quot;). All of the standard JavaFX Controls have a default Skin defined, and the Control will instantiate that by itself. Somehow (and we don\u0026rsquo;t really care how), the Skin gets passed to the Layout Manager which manages it in the GUI.\nReskinning a Control # Since Skinnable has a method, setSkin(), we can create our own Skins and attach them to the standard JavaFX Controls. This is actually the intention with the design - at least in theory - so that you\u0026rsquo;re not locked into the screen implementations that came \u0026ldquo;out of the box\u0026rdquo;. In reality it can get a bit tricky, the standard Skins often use a private JavaFX API that we don\u0026rsquo;t have access to - so extending an existing Skin will often be problematic.\nIt\u0026rsquo;s also possible to specify the skin via a stylesheet. In that case, you use the -fx-skin attribute.\nIn this article we\u0026rsquo;re going to look at reskinning ToggleButton to turn it into something quite different, a toggle (or \u0026ldquo;flip\u0026rdquo;) switch. What we are going for is something that looks like one of these:\nYou can see that this is quite a different look from the standard ToggleButton, which looks like a regular Button but has two looks, one for \u0026ldquo;Selected\u0026rdquo; and one for \u0026ldquo;Not Selected\u0026rdquo;. Our version will also have two looks:\nThe Standard ToggleButton Skin # Let\u0026rsquo;s take a look at the standard Skin for ToggleButton, so we can see how it works\u0026hellip;\npublic class ToggleButtonSkin extends LabeledSkinBase\u0026lt;ToggleButton\u0026gt; { private final BehaviorBase\u0026lt;ToggleButton\u0026gt; behavior; public ToggleButtonSkin(ToggleButton control) { super(control); behavior = new ToggleButtonBehavior\u0026lt;\u0026gt;(control); } @Override public void dispose() { super.dispose(); if (behavior != null) { behavior.dispose(); } } } That\u0026rsquo;s pretty underwhelming!\nThere\u0026rsquo;s really nothing here. Some behaviour stuff, whatever that is, but nothing visual. If you look at BehaviorBase you\u0026rsquo;ll find that it relates to traversal and hot key stuff - important things, but still nothing visual.\nNow, if you look at LabeledSkinBase you\u0026rsquo;ll see that there\u0026rsquo;s a lot of stuff there. A lot. It\u0026rsquo;s quite complicated. But it has nothing specific to ToggleButton or even Button to it at all.\nIt turns out that what makes a Button look like a Button has nothing to do with the Skin at all. It\u0026rsquo;s all in the styling. From the visual perspective, a Button is just a Label that\u0026rsquo;s been styled to look like a Button, and a ToggleButton is just a Button with an added Pseudo Class for selected.\nThe New Skin # But we want our ToggleButton to look like something completely different, and there isn\u0026rsquo;t any way to do that with just some CSS hocus pocus. We need a new layout with different elements that will show our toggle as a switch.\nIt turns out that the skin that gets us furthest towards this is CheckBoxSkin. Just like ToggleButtonSkin, it extends LabeledSkinBase, but it also has an extra element - the checkbox. So what we\u0026rsquo;re going to do is to add the toggle switch in the same manner that CheckBoxSkin adds the checkbox, but still base off ToggleButtonSkin.\nWhy not extend from CheckBoxSkin?\nCheckBox has three states: \u0026ldquo;selected\u0026rdquo;, \u0026ldquo;unselected\u0026rdquo; and \u0026ldquo;indeterminate\u0026rdquo;. CheckBoxSkin works with CheckBox and if we use CheckBoxSkin then we\u0026rsquo;ll need to implement our Control as a CheckBox not a ToggleButton. Which means that we\u0026rsquo;ll need to deal with \u0026ldquo;indeterminate\u0026rdquo;. To keep this as a ToggleButton we\u0026rsquo;ll need to use a ToggleButton skin.\nThe approach we\u0026rsquo;re going to take here is to instantiate a ToggleButton, and the use its setSkin() method to replace the standard skin with our own skin, which we\u0026rsquo;ll call ToggleFlipSkin. In this way we\u0026rsquo;re not going to change the behaviour of ToggleButton one bit. It will still behave exactly like a standard ToggleButton, except that its visual component will be customized.\nCustom Styleable Property # Before we get into the actual visual switch aspect, we need to deal with one factor that we\u0026rsquo;ll need in order to build that switch. And that means adding a StyleableProperty\u0026hellip;\nThere\u0026rsquo;s only one StyeableProperty that we\u0026rsquo;re going to add, one which will control the size of the toggle switch itself by specifying the radius of the flippers inside it. This radius will drive the rest of the sizing for the toggle switch.\nSince we\u0026rsquo;re not extending ToggleButton, we need to do all of this from inside the skin, but it\u0026rsquo;s essentially the same as adding any other StyleableProperty to Node. We need to add the StyleableProperty and then add the CssMetaData to support it.\nThe quirk here is that the skin does not implement Styleable, so we need to build the CssMetaData with reference to the ToggleButton control itself, which does implement Styleable. This results in some awkward self-references and casting back to the skin where the new CssMetaData is defined.\nLet\u0026rsquo;s look at just this part:\nfun flipperSizeProperty() = flipperSizeImpl val flipperSizeImpl: StyleableDoubleProperty = SimpleStyleableDoubleProperty(ToggleFlip.BUTTON_SIZE_META_DATA, this, \u0026#34;flipperSize\u0026#34;, 12.0) var flipperSize: Double get() = flipperSizeImpl.value set(value) = flipperSizeImpl.set(value) companion object CssStuff { val BUTTON_SIZE_META_DATA: CssMetaData\u0026lt;ToggleButton, Number\u0026gt; = object : CssMetaData\u0026lt;ToggleButton, Number\u0026gt;(\u0026#34;-wfx-flipper-size\u0026#34;, StyleConverter.getSizeConverter()) { override fun isSettable(styleable: ToggleButton) = !((styleable.skin as ToggleFlipSkin).flipperSizeImpl.isBound) override fun getStyleableProperty(styleable: ToggleButton) = (styleable.skin as ToggleFlipSkin).flipperSizeImpl } private val cssMetaDataList = (ToggleButton.getClassCssMetaData() + BUTTON_SIZE_META_DATA) as MutableList fun getClassCssMetaData() = cssMetaDataList } override fun getCssMetaData(): MutableList\u0026lt;CssMetaData\u0026lt;out Styleable, *\u0026gt;\u0026gt; { return getClassCssMetaData() } If you\u0026rsquo;ve read the article about customizing custom components that extend Region, you\u0026rsquo;ll see that this is very similar code.\nThe two implemented functions in new CssMetaData object - isSettable() and getStyleableProperty() - both take a Styleable as a parameter. In this case, it\u0026rsquo;s going to be our control, which is a ToggleButton. But of course we need to refer to the StyleableProperty which is defined in our skin, which means we have to call ToggleButton.getSkin(). Then we have to cast it our skin class which is ToggleFlipSkin - and this is safe because we couldn\u0026rsquo;t get here unless we were here in the first place.\nFinally, we need to get the rest of the CssMetaData elements from ToggleButton not the skin, and that\u0026rsquo;s how cssMetaDataList is created.\nBTW: If you want to avoid this round-about casting stuff by extending ToggleButton and adding the StyleableProperty and the CssMetaData directly to it, you\u0026rsquo;ll still get a little stuck. That\u0026rsquo;s because getCssMetaData() is final in ToggleButton (and most other controls). There\u0026rsquo;s actually a note that if you want to override it you should override getCssMetaData() in the skin.\nThe Toggle Switch Graphic # Now we are going to look at how to create the extra piece of visual component, the switch graphic itself\u0026hellip;\nThis is one of the rare occasions when AnchorPane is the right solution! Our toggle switch is an AnchorPane with two StackPanes inside it, each anchored to either the lef or right side of the AnchorPane. The StackPanes are made into circles by calling setShape(Circle(10.0)). These StackPanes are the \u0026ldquo;flippers\u0026rdquo; of the switch. The Visible property of each flipper is bound to the Selected property of the ToggleButton such that only one is visible at a time.\nThat\u0026rsquo;s all there is to it really. The minimum and maximum height and width properties of the flippers are bound to the FlipperSize StyleableProperty. The only tricky bit here is that the anchor distances need to scale up and down as the size of the flippers is bigger or smaller.\nLet\u0026rsquo;s take a look at the code for this:\nprivate fun createFlipperBox(): Region = AnchorPane().apply { val flipperOff = flipper(\u0026#34;flipper\u0026#34;, false) val flipperOn = flipper(\u0026#34;flipper\u0026#34;, true) setFlipperInsets(flipperOff, flipperOn) flipperSizeProperty().addListener { _ -\u0026gt; setFlipperInsets(flipperOff, flipperOn) } minWidthProperty().bind(flipperSizeProperty().multiply(6.0)) children += listOf(flipperOff, flipperOn) styleClass += \u0026#34;flipper-box\u0026#34; } private fun flipper(theStyle: String, showWhen: Boolean) = StackPane().apply { minWidthProperty().bind(flipperSizeImpl.multiply(2.0)) minHeightProperty().bind(flipperSizeImpl.multiply(2.0)) maxHeightProperty().bind(flipperSizeImpl.multiply(2.0)) maxWidthProperty().bind(flipperSizeImpl.multiply(2.0)) shape = Circle(10.0) styleClass += theStyle visibleProperty().bind( Bindings.createBooleanBinding( { control.isSelected == showWhen }, control.selectedProperty() ) ) } private fun setFlipperInsets(buttonOff: StackPane, buttonOn: StackPane) { val inset = flipperSize / 6.0 AnchorPane.setLeftAnchor(buttonOff, inset) AnchorPane.setTopAnchor(buttonOff, inset) AnchorPane.setBottomAnchor(buttonOff, inset) AnchorPane.setRightAnchor(buttonOn, inset) AnchorPane.setTopAnchor(buttonOn, inset) AnchorPane.setBottomAnchor(buttonOn, inset) } This is all very straight-forward, and there\u0026rsquo;s nothing very complicated about this layout. The minimum width of the flipper box is set to 6 times the radius of the flippers, which gives a nicely proportioned toggle switch design.\nThe Toggle Switch Styling # The biggest question about a toggle switch is, \u0026ldquo;Which way is on?\u0026rdquo;. In this design, it\u0026rsquo;s the one on the right, but that\u0026rsquo;s not going to be obvious to a user. We\u0026rsquo;ll need a visual clue.\nThere\u0026rsquo;s a lot of different ways to do this. To keep things simple for this example, we\u0026rsquo;re just going to use colours. You could, however, put images or text inside the StackPanes, you could add Text in the areas outside the StackPanes and you could add custom StyleableProperties to support these approaches. Just using colours may not be the best approach because some users might have difficulties differentiating them.\nFor colours, though, it\u0026rsquo;s all in the style sheets:\n.toggle-flip { -wfx-flipper-size: 8px; } .toggle-flip .flipper-box { -fx-base: #ffb8bb; -fx-background-color: -fx-shadow-highlight-color, derive(-fx-outer-border, -2%), -fx-inner-border-bottomup, -fx-body-color; -fx-background-insets: 1 0 0 0, 0, 3 0 1 1.5, 2; -fx-background-radius: 12px, 12px, 112px, 10px; -fx-text-fill: -fx-text-base-color; } .toggle-flip:selected .flipper-box { -fx-base: #6acda0; } .toggle-flip .flipper { -fx-background-color: derive(-fx-base, +90%); } Most of the complexity here is to create a 3D feel to the switch body via shading and highlighting. That\u0026rsquo;s why there\u0026rsquo;s 4 different background colours with different insets.\nThe colour change is done by manipulating -fx-base. This colour is set at the root of the Modena stylesheet as a global value. By redefining it in .toggle-flip .flipper-box it\u0026rsquo;s just changed for the AnchorPane and all of its children. It starts off as pastel pink, and then in .toggle-flip:selected .flipper-box it gets changed to pastel green.\nThe result looks like this for selected:\nAnd this for unselected:\nOne last point:\nIf you change the flipper radius to a much larger number, then the background radii from the style sheet start to make the AnchorPane corners decidely square. There\u0026rsquo;s no way to couple the -wfx-flipper-size to the -fx-background-radius attribute in the style sheet. It doesn\u0026rsquo;t seem unreasonable, however, to expect a programmer to adjust the background radii if they are changing the flipper size.\nHowever, the flipper size property is exposed publicly. If this size is manipulated programatically, there\u0026rsquo;s really no way to adjust the background radii in tandem. Doing this would involve calling up the AnchorPane background and expecting it to be a List of BackgroundFill and then manipulating the radius of each. But doing that assumes, then, that the style sheet specifies that the backgrounds are constructed this way. Which, of course, doesn\u0026rsquo;t need to be true.\nThe Layout of the Skin # So far, we haven\u0026rsquo;t really introduced any brand new concepts, just layouts and styling. Working with the skin layout itself is a little bit trickier. This is where we\u0026rsquo;re going to see how our new layout component interacts with the rest of the skin.\nRemember that ToggleButtonSkin extends LabeledSkinBase and that Labeled subclasses have two basic layout components; a Text and a Graphic and either one (or both) of them can be Null. We need to integrate a third layout component with those other two components in a way that makes sense. The skin itself is a Region, and the standard skin approach is to manually place the children in the Region and control the other parameters to make it work.\nThe updateChildren() Method # One thing about skins is that there are a whole bunch of special techniques and rules that aren\u0026rsquo;t documented or explained anywhere. The only way to learn is to look at existing examples and try to understand what they are doing and why they are doing it that way.\nThe first of these is the method to add our new layout element, the \u0026ldquo;FlipBox\u0026rdquo;, to the Region. For this we need to use this:\nprivate val flipperBox: Region? = createFlipperBox() override fun updateChildren() { super.updateChildren() if (flipperBox != null) { children.add(flipperBox) } } It appears that this is the only place where you should be adding new Nodes to the layout.\nHere flipperBox is a field in the skin. It is possible - actually, certain - that this will be called from the super-type before flipperBox has been initialized. Because of this, you have to check to make sure that flipperBox is not Null before you attempt to add it to the children of the Region.\nHonestly, I don\u0026rsquo;t know how it does this and Intellij complains that flipperBox can never be Null. But I have the stack dumps to show that it happens.\nThere\u0026rsquo;s a call to updateChildren() in the init{} block of our skin, this ensures that the flipperBox gets added to the layout once it has been initialized.\nThe layoutChildren() Method # The next important method to understand is layoutChildren(). This method is called whenever the Layout Manager needs to recalculate the layout of the skin Region. It is called automatically, and can be triggered by any number of things that you really don\u0026rsquo;t have any control over.\nOne thing that will trigger it is if you change the size or position of a Node inside the layout. Another that will trigger it is if you add more children (or children of children) to the layout. You will get an infinite loop if you add children inside the layoutChildren() method. So, don\u0026rsquo;t do that.\nLet\u0026rsquo;s look at our layoutChildren():\noverride fun layoutChildren(x: Double, y: Double, w: Double, h: Double) { val boxWidth = snapSizeX(flipperSize * 6.33) val boxHeight = snapSizeY(flipperSize * 2.33) val computeWidth = control.prefWidth(-1.0).coerceAtLeast(control.minWidth(-1.0)) val labelWidth = (computeWidth - boxWidth).coerceAtMost(w - snapSizeX(boxWidth)) val labelHeight = control.prefHeight(labelWidth).coerceAtMost(h) val maxHeight = boxHeight.coerceAtLeast(labelHeight) val xOffset = Utils.computeXOffset(w, labelWidth + boxWidth, control.alignment.hpos) + x val yOffset = Utils.computeYOffset(h, maxHeight, control.alignment.vpos) + y layoutLabelInArea(xOffset + boxWidth, yOffset, labelWidth, maxHeight, control.alignment) flipperBox.resize(boxWidth, boxHeight) positionInArea( flipperBox, xOffset, yOffset, boxWidth, maxHeight, 0.0, control.alignment.hpos, control.alignment.vpos ) } The first part of this method is figuring out how much space things should take up. Note that the coerceAtLeast() and coerceAtMost() functions are Kotlin native implementations of Math.max() and Math.min().\nIt\u0026rsquo;s important to note that all of these calculated numbers are just for the use of positioning the children in the layout. By the time this method is called, something higher up the chain has figured out where the whole thing goes in the screen layout and how big it will be - that\u0026rsquo;s what the x,y,w and h parameters are.\nThe last three calls do the positioning work\u0026hellip;\nThe method layoutLabelInArea() is the method that handles the traditional Label elements of the layout: the Text and the Graphic. If you like, you can think of our overall layout as an AnchorPane in a Region with a Label. This is the call that organizes the layout of the Label part.\nThe next call, to flipperBox.resize() is a method of Region and the JavaDocs say that your application should not call this directly.\nSo what\u0026rsquo;s up?\nOnce you\u0026rsquo;re inside layoutChildren(), as well as some other places in the skin, those \u0026ldquo;Application code should not\u0026hellip;\u0026rdquo; rules just don\u0026rsquo;t apply. This is code that is only invoked from inside the Layout Manager, and this is the place those methods are intended to be called from. In this case, resize() actually sets the size of the AnchorPane, and if you leave this call out, then you get a FlipperBox with zero size - and you can\u0026rsquo;t see it.\nFinally, we have positionInArea(). This is the method that figures out where to put the FlipperBox in the Region. This is a method that delegates to another positionInArea() method adding in a parameter for zero insets, and that delegates to method of Region that adds some more default parameters which in turn delegates to another method.\nFor the most part, this code was lifted directly from CheckBoxSkin and then the boxWidth and boxHeight calculations were changed to apply the flipperSize property.\nSetting the Control Size # Changing layoutChildren() almost finished the job, but the text was getting chopped off. This could be fixed by manually setting the minWidth() of the ToggleButton in the layout code, but that shouldn\u0026rsquo;t be necessary. Once again, checking the code for CheckBoxSkin had the answer.\nThe following methods were added to the skin:\noverride fun computeMinWidth( height: Double, topInset: Double, rightInset: Double, bottomInset: Double, leftInset: Double ) = super.computeMinWidth( height, topInset, rightInset, bottomInset, leftInset ) + snapSizeX(flipperBox.minWidth(-1.0)) override fun computeMinHeight( width: Double, topInset: Double, rightInset: Double, bottomInset: Double, leftInset: Double ) = super.computeMinHeight(width - flipperBox.minWidth(-1.0), topInset, rightInset, bottomInset, leftInset) .coerceAtLeast(topInset + flipperBox.minHeight(-1.0) + bottomInset) override fun computePrefWidth( height: Double, topInset: Double, rightInset: Double, bottomInset: Double, leftInset: Double ) = super.computePrefWidth( height, topInset, rightInset, bottomInset, leftInset ) + snapSizeX(flipperSize * 6.33) + snapSizeX(20.0) override fun computePrefHeight( width: Double, topInset: Double, rightInset: Double, bottomInset: Double, leftInset: Double ) = super.computePrefHeight(width - flipperBox.prefWidth(-1.0), topInset, rightInset, bottomInset, leftInset) .coerceAtLeast(topInset + flipperBox.prefHeight(-1.0) + bottomInset) These methods let the normal LabeledSkinBase skin calculate the dimension of the Label part of the layout, and then add in an allowance for the FlipperBox.\nThe main concern here was computePrefWidth(). It now adds enough space for the FlipperBox and a spacer of 20 pixels.\nSome further investigation revealed that these methods are expected to be overriden by skins in order to handle sizing properly.\nThe result looks like this:\nAll the Code in One Place # At this point, you\u0026rsquo;ve seen nearly all the code for the skin, just chopped up into little parts. Let\u0026rsquo;s take a look at it all together:\nimport com.sun.javafx.scene.control.skin.Utils import javafx.beans.binding.Bindings import javafx.css.* import javafx.scene.control.Skin import javafx.scene.control.ToggleButton import javafx.scene.control.skin.ToggleButtonSkin import javafx.scene.layout.AnchorPane import javafx.scene.layout.Region import javafx.scene.layout.StackPane import javafx.scene.shape.Circle class ToggleFlipSkin(val control: ToggleButton) : ToggleButtonSkin(control) { fun flipperSizeProperty() = flipperSizeImpl val flipperSizeImpl: StyleableDoubleProperty = SimpleStyleableDoubleProperty(ToggleFlip.BUTTON_SIZE_META_DATA, this, \u0026#34;flipperSize\u0026#34;, 12.0) var flipperSize: Double get() = flipperSizeImpl.value set(value) = flipperSizeImpl.set(value) private val flipperBox: Region = createFlipperBox() companion object CssStuff { val BUTTON_SIZE_META_DATA: CssMetaData\u0026lt;ToggleButton, Number\u0026gt; = object : CssMetaData\u0026lt;ToggleButton, Number\u0026gt;(\u0026#34;-wfx-flipper-size\u0026#34;, StyleConverter.getSizeConverter()) { override fun isSettable(styleable: ToggleButton) = !((styleable.skin as ToggleFlipSkin).flipperSizeImpl.isBound) override fun getStyleableProperty(styleable: ToggleButton) = (styleable.skin as ToggleFlipSkin).flipperSizeImpl } private val cssMetaDataList = (ToggleButton.getClassCssMetaData() + BUTTON_SIZE_META_DATA) as MutableList fun getClassCssMetaData() = cssMetaDataList } override fun getCssMetaData(): MutableList\u0026lt;CssMetaData\u0026lt;out Styleable, *\u0026gt;\u0026gt; { return getClassCssMetaData() } init { control.styleClass.setAll(\u0026#34;toggle-flip\u0026#34;) updateChildren() } override fun updateChildren() { super.updateChildren() if (flipperBox != null) { children.add(flipperBox) } } private fun createFlipperBox(): Region = AnchorPane().apply { val flipperOff = flipper(\u0026#34;flipper\u0026#34;, false) val flipperOn = flipper(\u0026#34;flipper\u0026#34;, true) setFlipperInsets(flipperOff, flipperOn) flipperSizeProperty().addListener { _ -\u0026gt; setFlipperInsets(flipperOff, flipperOn) } minWidthProperty().bind(flipperSizeProperty().multiply(6.0)) children += listOf(flipperOff, flipperOn) styleClass += \u0026#34;flipper-box\u0026#34; } private fun flipper(theStyle: String, showWhen: Boolean) = StackPane().apply { minWidthProperty().bind(flipperSizeImpl.multiply(2.0)) minHeightProperty().bind(flipperSizeImpl.multiply(2.0)) maxHeightProperty().bind(flipperSizeImpl.multiply(2.0)) maxWidthProperty().bind(flipperSizeImpl.multiply(2.0)) shape = Circle(10.0) styleClass += theStyle visibleProperty().bind( Bindings.createBooleanBinding( { control.isSelected == showWhen }, control.selectedProperty() ) ) } private fun setFlipperInsets(buttonOff: StackPane, buttonOn: StackPane) { val inset = flipperSize / 6.0 AnchorPane.setLeftAnchor(buttonOff, inset) AnchorPane.setTopAnchor(buttonOff, inset) AnchorPane.setBottomAnchor(buttonOff, inset) AnchorPane.setRightAnchor(buttonOn, inset) AnchorPane.setTopAnchor(buttonOn, inset) AnchorPane.setBottomAnchor(buttonOn, inset) } override fun computeMinWidth( height: Double, topInset: Double, rightInset: Double, bottomInset: Double, leftInset: Double ) = super.computeMinWidth( height, topInset, rightInset, bottomInset, leftInset ) + snapSizeX(flipperBox.minWidth(-1.0)) override fun computeMinHeight( width: Double, topInset: Double, rightInset: Double, bottomInset: Double, leftInset: Double ) = super.computeMinHeight(width - flipperBox.minWidth(-1.0), topInset, rightInset, bottomInset, leftInset) .coerceAtLeast(topInset + flipperBox.minHeight(-1.0) + bottomInset) override fun computePrefWidth( height: Double, topInset: Double, rightInset: Double, bottomInset: Double, leftInset: Double ) = super.computePrefWidth( height, topInset, rightInset, bottomInset, leftInset ) + snapSizeX(flipperSize * 6.33) + snapSizeX(20.0) override fun computePrefHeight( width: Double, topInset: Double, rightInset: Double, bottomInset: Double, leftInset: Double ) = super.computePrefHeight(width - flipperBox.prefWidth(-1.0), topInset, rightInset, bottomInset, leftInset) .coerceAtLeast(topInset + flipperBox.prefHeight(-1.0) + bottomInset) override fun layoutChildren(x: Double, y: Double, w: Double, h: Double) { val boxWidth = snapSizeX(flipperSize * 6.33) val boxHeight = snapSizeY(flipperSize * 2.33) val computeWidth = control.prefWidth(-1.0).coerceAtLeast(control.minWidth(-1.0)) val labelWidth = (computeWidth - boxWidth).coerceAtMost(w - snapSizeX(boxWidth)) val labelHeight = control.prefHeight(labelWidth).coerceAtMost(h) val maxHeight = boxHeight.coerceAtLeast(labelHeight) val xOffset = Utils.computeXOffset(w, labelWidth + boxWidth, control.alignment.hpos) + x val yOffset = Utils.computeYOffset(h, maxHeight, control.alignment.vpos) + y layoutLabelInArea(xOffset + boxWidth, yOffset, labelWidth, maxHeight, control.alignment) flipperBox.resize(boxWidth, boxHeight) positionInArea( flipperBox, xOffset, yOffset, boxWidth, maxHeight, 0.0, control.alignment.hpos, control.alignment.vpos ) } } Running It # The entire point of this approach was to be able to use a standard ToggleButton and then just replace the skin to implement a new look and feel. And that\u0026rsquo;s all you need to do to use this new skin:\nclass ToggleFlipDemo : Application() { private val nameProperty: StringProperty = SimpleStringProperty(\u0026#34;Not Started\u0026#34;) private var counter: Int = 0 override fun start(primaryStage: Stage) { primaryStage.scene = Scene(createContent()).addWidgetStyles().apply { object {}::class.java.getResource(\u0026#34;/css/toggleflip.css\u0026#34;)?.toString()?.let { stylesheets += it } } primaryStage.show() } private fun createContent(): Region = BorderPane().apply { val customControl1 = ToggleButton(\u0026#34;Power\u0026#34;).apply { graphic = FontIcon(\u0026#34;typ-plug\u0026#34;).apply { iconSize = 23 } skin = ToggleFlipSkin(this) } val customControl2 = ToggleButton(\u0026#34;Power\u0026#34;).apply { skin = ToggleFlipSkin(this) } val customControl3 = ToggleButton(\u0026#34;\u0026#34;).apply { skin = ToggleFlipSkin(this) graphic = FontIcon(\u0026#34;typ-plug\u0026#34;).apply { iconSize = 23 } } center = VBox(20.0, customControl1, customControl2, customControl3).apply { alignment = Pos.CENTER_LEFT padding = Insets(0.0, 0.0, 0.0, 100.0) } minWidth = 400.0 } padWith 20.0 } fun main() = Application.launch(ToggleFlipDemo::class.java) Final Notes # One thing about this design is that the toggle switch graphic always appears to the left of the Label. That\u0026rsquo;s because that\u0026rsquo;s how CheckBox works, and we started from there. It\u0026rsquo;s also easier to deal with if you have a column of these switches because the graphics will all line up nicely - something that could be an issue if the Label text for each is a different length with the graphic on the right. Not that that couldn\u0026rsquo;t be dealt with, by making the layout behaviour pin the graphic at the far right of the layout, and specifying a preferred width for each one. But that would make the design more complicated.\nAlso, you could introduce a StyleableProperty for the positioning of the label and graphic. You could also design it such that the switch graphic could be vertical instead of horizontal.\nThis design preserves the ability to use either or both of the Label text and graphic, like this:\nConclusion # There\u0026rsquo;s no point in pretending that reskinning a Control is simple and something suitable for beginners to JavaFX. The programming itself isn\u0026rsquo;t complex, but you do need a solid understanding of the basic principles of layouts, Nodes and Controls. You\u0026rsquo;ll need to be comfortable poking around in the source code for JavaFX, and understand how the pieces connect to each other.\nIf you do try this yourself, it\u0026rsquo;s almost guaranteed that you\u0026rsquo;ll knock up against undocumented constraints and quirks of the Skin class. There are times when it will be frustrating, and you\u0026rsquo;ll have no choice other than to test out various approaches to find what works.\nIt\u0026rsquo;s also instructive to look at the source code for ControlsFX and AtlantaFX, as they both contain completely custom skins that don\u0026rsquo;t rely on extending existing skins in JavaFX.\n","date":"19 July 2023","externalUrl":null,"permalink":"/javafx/techniques/toggle-button-reskin/","section":"JavaFX","summary":"An introduction to JavaFX Skins and Skinnable and how to create your own skin.","title":"Reskinning ToggleButton","type":"javafx"},{"content":" Introduction # One of the advantages of coding layouts by hand and following both DRY (Don\u0026rsquo;t Repeat Yourself) and the \u0026ldquo;Single Responsibility Principle\u0026rdquo; is that your code ends up composed of lots of small methods that do very specific things. Over time, it becomes very easy to start viewing the results of these methods as \u0026ldquo;custom layouts\u0026rdquo;, \u0026ldquo;custom controls\u0026rdquo; or \u0026ldquo;custom widgets\u0026rdquo;. This is especially true if you\u0026rsquo;ve built up a library of these methods that you call from different layouts.\nThis can be as simple as a method that instantiates a Label and then adds a StyleClass to it. If you call your method headingLabel(), then you\u0026rsquo;d start to think of the results not just as a Label, but a HeadingLabel, or maybe just a Heading.\nThere\u0026rsquo;s a great deal of power in this way of thinking about your layouts, because now the layout code isn\u0026rsquo;t calling a bunch of methods, it\u0026rsquo;s integrating a collection of custom widgets. That\u0026rsquo;s not just semantics, it is a different viewpoint.\nIf you keep applying DRY across your projects, or even layouts in the same project, you\u0026rsquo;ll eventually move some of those small methods, the ones that aren\u0026rsquo;t one-offs, into separate library so that you can share them across screens and projects. Once you\u0026rsquo;ve done that, you get the added bonus that you never have to think about how those custom widgets work, because you know that they work - and you can concentrate on your layout instead.\nIn this tutorial we\u0026rsquo;re going to look at how to start from a simple small method in a layout and then how you\u0026rsquo;d implement it as part of a shared library.\nOur Example # For this tutorial we\u0026rsquo;re going to work with a control that looks like this:\nIt\u0026rsquo;s an HBox with a Label, a TextField and a Button in it. When the TextField has focus, then the Button becomes the default button, so you can just type in the TextField and hit to trigger the Button. When the TextField is empty, then the Button is disabled.\nIt has some specific functionality, so it\u0026rsquo;s not totally trivial, and yet the implementation of the layout is pretty straight-forward and easy to understand. So let\u0026rsquo;s get to it\u0026hellip;\nFirst Version - Local Factory Method # The first way to build this, we\u0026rsquo;re going just create it as a local factory method. It\u0026rsquo;s going to be a \u0026ldquo;one-off\u0026rdquo;, so we don\u0026rsquo;t need to be particularly careful about it, and we can make all kinds of assumptions about how it\u0026rsquo;s being used - because we know how it\u0026rsquo;s being used:\nclass FactoryMethodStyle : Application() { override fun start(stage: Stage) { stage.scene = Scene(createContent()) stage.show() } private val name: StringProperty = SimpleStringProperty(\u0026#34;\u0026#34;) private val results: StringProperty = SimpleStringProperty(\u0026#34;No search done\u0026#34;) private fun createContent(): Region = BorderPane().apply { center = createCustomBox(\u0026#34;Last Name:\u0026#34;, \u0026#34;Search\u0026#34;, name, EventHandler\u0026lt;ActionEvent\u0026gt; { doSearch() }); bottom = Label().apply { textProperty().bind(results) } padding = Insets(20.0) } private fun createCustomBox(labelText: String, buttonText: String, boundProperty: StringProperty, handler: EventHandler\u0026lt;ActionEvent\u0026gt;): Region = HBox().apply { val textField = TextField().apply { textProperty().bindBidirectional(boundProperty) } children += Label(labelText) children += textField children += Button(buttonText).apply { onAction = handler defaultButtonProperty().bind(textField.focusedProperty().and(name.isNotEmpty)) disableProperty().bind(name.isEmpty) } alignment = Pos.CENTER_LEFT spacing = 6.0 } private fun doSearch() { results.value = \u0026#34;Nothing found for: ${name.value}\u0026#34; } } fun main() = Application.launch(FactoryMethodStyle::class.java) This is Kotlin, but there\u0026rsquo;s nothing scary in here. If you\u0026rsquo;re really stuck, you can read my Kotlin for Java Programmers article, which should clear everything up.\nWe have a BorderPane, and in the centre we\u0026rsquo;re putting our widget, which we\u0026rsquo;ll call \u0026ldquo;CustomBox\u0026rdquo; throughout this article. The factory method needs the text for the Label and the Button, the StringProperty to bind to the TextField and the EventHandler for the Button.\nThen we construct the HBox and we create the contents, do all the bindings and put the parts into the HBox.\nThe method returns Region which isn\u0026rsquo;t going to expose any of the contents since there\u0026rsquo;s no public Region.getChildren() method - this is going to be important later on. Once you\u0026rsquo;ve got the CustomBox there\u0026rsquo;s really nothing you can do with it except put it in the layout. Region allows you to mess with the min and max width and height, but that\u0026rsquo;s about all, and there are the methods from Node, too.\nSecond Version - Builder Class # Now, we\u0026rsquo;re going to look at how you might start to move this out of your layout code altogether, and the ways that you might implement it as part of a library of sub-layouts and widgets that you use frequently.\nJavaFX has this handy Interface called Builder. It\u0026rsquo;s generic so you declare you class as implementing Builder\u0026lt;SomeClass\u0026gt;, with SomeClass being the class of the object that is going to be built.\nThe standard method with builders is to configure them, and then call Builder.build() to get the output. Let\u0026rsquo;s take a look at a builder for CustomBox:\nclass CustomBoxBuilder(private val labelText: String, private val buttonText: String, private val boundProperty: StringProperty, private val handler: EventHandler\u0026lt;ActionEvent\u0026gt;) : Builder\u0026lt;Region\u0026gt; { override fun build(): Region = HBox().apply { val textField = TextField().apply { textProperty().bindBidirectional(boundProperty) } children += Label(labelText) children += textField children += Button(buttonText).apply { onAction = handler defaultButtonProperty().bind(textField.focusedProperty().and(textField.textProperty().isNotEmpty)) disableProperty().bind(textField.textProperty().isEmpty) } alignment = Pos.CENTER_LEFT spacing = 6.0 } } And here\u0026rsquo;s how we\u0026rsquo;d use it:\nclass BuilderStyle : Application() { override fun start(stage: Stage) { stage.scene = Scene(createContent()) stage.show() } private val name: StringProperty = SimpleStringProperty(\u0026#34;\u0026#34;) private val results: StringProperty = SimpleStringProperty(\u0026#34;No search done\u0026#34;) private fun createContent(): Region = BorderPane().apply { center = CustomBoxBuilder(\u0026#34;Last Name:\u0026#34;, \u0026#34;Search\u0026#34;, name, { doSearch() }).build() bottom = Label().apply { textProperty().bind(results) } padding = Insets(20.0) } private fun doSearch() { results.value = \u0026#34;Nothing found for: ${name.value}\u0026#34; } } fun main() = Application.launch(BuilderStyle::class.java) To be honest, the CustomBoxBuilder.build() method is almost exactly the same as createCustomBox() from the first version. We don\u0026rsquo;t have access to name in CustomBoxBuilder so some small changes were made to the bindings to reference textField.textProperty() instead. Otherwise, they are identical.\nBut since CustomBoxBuilder is a generic tool, we can\u0026rsquo;t make assumptions about how it\u0026rsquo;s going to be used, and we can\u0026rsquo;t assume that all of the design decisions that we made initially are going to work for any new situations. So we\u0026rsquo;ll need to add some more capabilities to CustomBoxBuilder by adding decorators. Using decorators will allow us to use a \u0026ldquo;fluent API\u0026rdquo; style to configure our builder\u0026hellip;\ntypealias ButtonRunner = (Runnable) -\u0026gt; Unit class CustomBoxBuilder(private val labelText: String, private val buttonText: String, private val boundProperty: StringProperty) : Builder\u0026lt;Region\u0026gt; { private var spacing: Double = 4.0 private var buttonAction: ButtonRunner = {} private val actionRunning: BooleanProperty = SimpleBooleanProperty(false) override fun build(): Region = HBox().apply { val textField = TextField().apply { textProperty().bindBidirectional(boundProperty) } children += Label(labelText) children += textField children += Button(buttonText).apply { onAction = EventHandler\u0026lt;ActionEvent\u0026gt; { actionRunning.value = true buttonAction { actionRunning.value = false } } defaultButtonProperty().bind(textField.focusedProperty().and(textField.textProperty().isNotEmpty)) disableProperty().bind((textField.textProperty().isEmpty).or(actionRunning)) } alignment = Pos.CENTER_LEFT spacing = this@CustomBoxBuilder.spacing } fun setSpacing(newValue: Double) = this.apply { spacing = newValue } fun setButtonAction(newAction: ButtonRunner) = this.apply { buttonAction = newAction } } First of all, Kotlin makes declaring decorators ridiculously easy. The structure:\nfun something() = this.apply{} will return the object itself, and run whatever is in the {} against the object. For spacing, we just added a private field to the builder, and then wrote a decorator setSpacing() method that updates the field. It\u0026rsquo;s almost exactly a traditional setter except that it returns the object.\nWhen we start thinking about how a Button might be used in a more general sense, it\u0026rsquo;s obvious that it could invoke a background Task that might take some time. We want to prevent clicking the Button again and launching a new background Task while the first one is running. This means we\u0026rsquo;ll need to disable it, and then create a Runnable that the Task can run when it\u0026rsquo;s completed.\nThat\u0026rsquo;s what we have here. First of all, we created a Kotlin thing called a typealias. It\u0026rsquo;s just a way to have a meaningful name on a more complicated type definition. In this case, it\u0026rsquo;s the object that we need to have passed in to run the Button action, hence ButtonRunner. In Java terms, Kotlin\u0026rsquo;s, (Runnable) -\u0026gt; Unit is the same thing as Consumer\u0026lt;Runnable\u0026gt;, which means that ButtonRunner == Consumer\u0026lt;Runnable\u0026gt;.\nFinally, the disabling logic for Button gets a little more complicated. We already have the DisableProperty bound to whether or not the TextField is empty, so we can\u0026rsquo;t just call Button.setDisable(true) because we\u0026rsquo;ll get an Exception about trying to set a bound Property. So we\u0026rsquo;ve introduced the actionRunning Property and added it into the disable binding. Then we just toggle actionRunning to control the disabling of the Button.\nThird Version - Extend a Layout Class (Not Recommended) # The builder method is great, but once you\u0026rsquo;ve run Builder.build() and have your Region there\u0026rsquo;s nothing that you can do to it other than put it in a layout. Let\u0026rsquo;s say that you wanted to change the spacing in response to something that happens in your GUI, you can\u0026rsquo;t do it.\nSo the answer is to have something that behaves more like a Node from the ground up. You can instantiate it, put it into a layout and continue to call methods in it to interact with it.\nThat approach is to create a custom class by extending HBox. This is somewhat equivalent to what most beginners do anyways, when they return HBox from their factory method instead of Region. Our example would look like this:\ntypealias ButtonRunner = (Runnable) -\u0026gt; Unit class CustomHBox(private val labelText: String, private val buttonText: String, private val boundProperty: StringProperty) : HBox() { private var buttonAction: ButtonRunner = {} private val actionRunning: BooleanProperty = SimpleBooleanProperty(false) init { createLayout() } private fun createLayout() { val textField = TextField().apply { textProperty().bindBidirectional(boundProperty) } children += Label(labelText) children += textField children += Button(buttonText).apply { onAction = EventHandler\u0026lt;ActionEvent\u0026gt; { actionRunning.value = true buttonAction { actionRunning.value = false } } defaultButtonProperty().bind(textField.focusedProperty().and(textField.textProperty().isNotEmpty)) disableProperty().bind((textField.textProperty().isEmpty).or(actionRunning)) } alignment = Pos.CENTER_LEFT } fun setButtonAction(newAction: ButtonRunner) = this.apply { buttonAction = newAction } fun withSpacing(newSpacing: Double) = this.apply { spacing = newSpacing } } And you\u0026rsquo;d use it this way\u0026hellip;\nclass HBoxStyle : Application() { private val name: StringProperty = SimpleStringProperty(\u0026#34;\u0026#34;) private val results: StringProperty = SimpleStringProperty(\u0026#34;No search done\u0026#34;) override fun start(stage: Stage) { stage.scene = Scene(createContent()) stage.show() } private fun createContent(): Region = BorderPane().apply { center = CustomHBox(\u0026#34;Last Name:\u0026#34;, \u0026#34;Search\u0026#34;, name).setButtonAction { postRunAction -\u0026gt; doSearch() PauseTransition(Duration(3000.0)).apply { onFinished = EventHandler { postRunAction.run() } play() } }.withSpacing(50.0) bottom = Label().apply { textProperty().bind(results) } padding = Insets(20.0) } private fun doSearch() { results.value = \u0026#34;Nothing found for: ${name.value}\u0026#34; } } fun main() = Application.launch(HBoxStyle::class.java) You can see that this implementation is only slightly different from the Builder structure. The build() method has been renamed to createLayout() and we now call it from init{} (which is almost like a constructor in Java). The method withSpacing() needed to be changed a little so that it delegates to HBox.setSpacing(), while still acting as a decorator.\nBut, why is this not recommended?\nFourth Version - Extend Region # The problem with creating a custom class by extending HBox is that you expose all of the methods of HBox to the client code. This includes, for instance, HBox.getChildren(). And that means that someone can add to and remove stuff from your layout. Or they could decide to modify the TextField somehow; just call HBox.getChildren() stream or loop through the List until you find the TextField and then start messing with it.\nIs this a big deal???\nThat depends. If you\u0026rsquo;re just working by yourself and you know you aren\u0026rsquo;t going to do those things (until you do), or if you know you can deal with the consequences, then it\u0026rsquo;s probably not a big deal.\nHowever, if you\u0026rsquo;re working in a bigger or more formal environment with other programmers, it can be huge. Part of the beauty of having a library of custom components is that - especially over time - you know that they work, and have been tested and proven. So you don\u0026rsquo;t even have to think about them much. You use them and you rely on them.\nBut if you have a situation where someone has found it expedient to monkey with the inner workings of one of those components from your library in the client code, then the bets are off. And if you don\u0026rsquo;t catch that they\u0026rsquo;ve done it, then it could cause confusion.\nEven worse, in that situation you now have coupling between the client code and the implementation of your custom class. Let\u0026rsquo;s imagine that they\u0026rsquo;ve done something to the Button by grabbing the children of the HBox and then taking the 3rd element from the List. But then you decide to add graphic to the HBox as the first element. Now the Button is the 4th element and this change will break their client code.\nSo the best thing is to create a custom class that extends from something that doesn\u0026rsquo;t expose it\u0026rsquo;s inner workings to the client code. The easiest answer to that is to use Region as your base class. The reason for this is that getChildren() is protected in Region, and isn\u0026rsquo;t public until Pane and its subclasses. So you can create a class that extends from Region, and your client code can\u0026rsquo;t mess with its implementation:\nclass CustomRegion(private val labelText: String, private val buttonText: String, private val boundProperty: StringProperty) : Region() { private var buttonAction: ButtonRunner = {} private val actionRunning: BooleanProperty = SimpleBooleanProperty(false) private val hBox: HBox init { hBox = createLayout() children.add(hBox) } private fun createLayout() = HBox().apply { val textField = TextField().apply { textProperty().bindBidirectional(boundProperty) } children += Label(labelText) children += textField children += Button(buttonText).apply { onAction = EventHandler\u0026lt;ActionEvent\u0026gt; { actionRunning.value = true buttonAction { actionRunning.value = false } } defaultButtonProperty().bind(textField.focusedProperty().and(textField.textProperty().isNotEmpty)) disableProperty().bind((textField.textProperty().isEmpty).or(actionRunning)) } alignment = Pos.CENTER_LEFT } fun setButtonAction(newAction: ButtonRunner) = this.apply { buttonAction = newAction } fun withSpacing(newSpacing: Double) = this.apply { hBox.spacing = newSpacing } } class RegionStyle : Application() { private val name: StringProperty = SimpleStringProperty(\u0026#34;\u0026#34;) private val results: StringProperty = SimpleStringProperty(\u0026#34;No search done\u0026#34;) override fun start(stage: Stage) { stage.scene = Scene(createContent()) stage.show() } private fun createContent(): Region = BorderPane().apply { center = CustomRegion(\u0026#34;Last Name:\u0026#34;, \u0026#34;Search\u0026#34;, name).setButtonAction { postRunAction -\u0026gt; doSearch() PauseTransition(Duration(3000.0)).apply { onFinished = EventHandler { postRunAction.run() } play() } }.withSpacing(50.0) bottom = Label().apply { textProperty().bind(results) } padding = Insets(20.0) } private fun doSearch() { results.value = \u0026#34;Nothing found for: ${name.value}\u0026#34; } } fun main() = Application.launch(RegionStyle::class.java) This is very similar to the HBox version except that the HBox is now added in as the sole element in Region.getChildren(). So we have our same HBox wrapped in a Region.\nJust doing that hides away the HBox. None of the client code can call Region.getChildren() because it\u0026rsquo;s protected.\nIt does mean that we have to deal with the Spacing issue. Since our client code can\u0026rsquo;t just call HBox.setSpacing(), we\u0026rsquo;ll have to provide a method in our custom class to provide this functionality. Here we have withSpacing() implemented as a decorator.\nFifth Version - Custom, Skinnable Control # We aren\u0026rsquo;t going to go into the details of this here, as it\u0026rsquo;s tutorial or two all in itself, but we\u0026rsquo;ll talk about it so tha you can know when it would be appropriate to use.\nOur CustomRegion is designed such that the GUI implementation is muddled up with the control code. It\u0026rsquo;s not a big deal here, because it\u0026rsquo;s still pretty simple, but it could be an issue if you wanted to change the implementation of the GUI.\nThe JavaFX class, Control extends Region and implements an interface called Skinnable. The design essentially allows an MVC-type structure to be used. Since there\u0026rsquo;s never any business/application logic to implement (it\u0026rsquo;s a GUI widget, after all), and the Model won\u0026rsquo;t have domain objects, the Model is essentially just the Presentation Model. The View implementation is done in a class that implements an interface called, Skin. The Control class becomes the Controller contains the Properties that comprise the Presentation Model, and the View is implemented in the Skin.\nIn practice, unless you want a client project to be able to rework the Skin, there\u0026rsquo;s very little reason to go through the trouble of creating a Skinnable Control. You can do virtually everything you need by extending Region. So we aren\u0026rsquo;t going to look at how to do this in this article as your not likely to need it building real applications.\nConclusion # I think the key idea is that every programmer, or every programming shop, is going to evolve their own conventions about how different layout components are going to be used and put together. There\u0026rsquo;s often lots of different ways to get to very similar outcomes in JavaFX, and best way is usually the one that makes the most sense to the programmers doing the coding.\nThat means that you\u0026rsquo;re going to find yourself using the same patterns over and over again, and you don\u0026rsquo;t want to be reinventing the wheel every time. And you certainly don\u0026rsquo;t want those details clogging up your layout code.\nMove the code that implements those patterns out into factory methods in your layout code, then if you use the same patterns in another layout, move them out into a library of factory methods that you can use anywhere. Most of your \u0026ldquo;custom widgets\u0026rdquo; are going to be implemented this way.\nWhen things get a little more complicated, or you need to tune the implementation to handle a variety of situations, then it might be time to implement a Builder class for your custom widget. If the implementation needs to be dynamic and respond to changes in the state of your GUI, then you might need to create a custom class that extends Region.\n","date":"27 February 2023","externalUrl":null,"permalink":"/javafx/techniques/custom_controls_when/","section":"JavaFX","summary":"Creating a custom control really isn’t that hard to do.  But when should you use one, and when should you just use a builder to create a layout?","title":"When and How to Create Custom Controls","type":"javafx"},{"content":" Introduction # OK, first off I know I\u0026rsquo;m swimming against the stream here, and at odds with things like this found in some of the tutorial pages from Oracle for JavaFX:\nJavaFX enables you to design with Model-View-Controller (MVC), through the use of FXML and Java. The \u0026ldquo;Model\u0026rdquo; consists of application-specific domain objects, the \u0026ldquo;View\u0026rdquo; consists of FXML, and the \u0026ldquo;Controller\u0026rdquo; is Java code that defines the GUI\u0026rsquo;s behavior for interacting with the user.\nThis is really what we\u0026rsquo;re going to focus on, how this is wrong, and how to do it right.\nI think that if you want a more definitive discussion of MVC, you can\u0026rsquo;t do better than Martin Fowler\u0026rsquo;s Article. It\u0026rsquo;s pretty abstract and can be heavy reading, but he gets it right. Here\u0026rsquo;s his intro to MVC:\nProbably the widest quoted pattern in UI development is Model View Controller (MVC) - it\u0026rsquo;s also the most misquoted. I\u0026rsquo;ve lost count of the times I\u0026rsquo;ve seen something described as MVC which turned out to be nothing like it.\nI think the quote from the Oracle tutorial is a perfect example of the problem he mentions.\nThe Structure of MVC # Before we can go any further, let\u0026rsquo;s look at the parts of MVC and what they do. First, here\u0026rsquo;s a diagram from somewhere out in the web:\nThis diagram shows a particular use case (user adds something to a \u0026ldquo;cart\u0026rdquo;), but has the essentials down.\nView The View is the GUI. It\u0026rsquo;s the only part of the framework that the user interacts with. Controller The Controller\u0026rsquo;s job is to turn user input into actions. It also is the part that interacts with the other GUI parts of your application. Model The Model is the data and the business logic. Note that the \u0026ldquo;data\u0026rdquo; comes in two parts: Presentation Data and Domain Data. The Model is the only component that deals with Domain Data, and a big part of its job is to keep Domain Data away from the View. What the View Does # The View is layout, but it\u0026rsquo;s also much more than that in MVC. It\u0026rsquo;s an independent element that handles all the user interaction.\nLet\u0026rsquo;s take a look at some of the things that the View is solely responsible for:\nLayout Styling and configuration Connections between layout elements Animations Data binding to the Presentation Model Detecting user interactions such as mouse clicks and keystrokes Enabling and disabling layout elements Triggering actions Responding to changes in the Presentation Model I don\u0026rsquo;t think that there\u0026rsquo;s anything controversial about this list. All of these items are clearly the responsibility of the View in an MVC framework.\nIf I was building an application without FXML, these are all things that I\u0026rsquo;d put into my View or my ViewBuilder (which creates the View). For every element of the layout, I\u0026rsquo;d configure and style it and I\u0026rsquo;d bind it to the Presentation Model if necessary. Any user interactions would be defined and I\u0026rsquo;d create EventHandlers for Buttons, Toggles and other things might generate Events.\nMost importantly though, no other element of the framework would have any knowledge of the inner structure of the View. There\u0026rsquo;s zero dependencies on any aspect of the implementation of the View. It\u0026rsquo;s a \u0026ldquo;black box\u0026rdquo; to the rest of the framework.\nNow let\u0026rsquo;s look at how we\u0026rsquo;d do it with FXML\u0026hellip;\nWe can do the three two items with the FXML file, but that\u0026rsquo;s about it. The rest of the responsibilities have to be handled by the FXML Controller.\nBut if the FMXL Controller is doing things like animations and binding Node value properties to the Presentation Model - things that are absolutely the sole responsibility of the View, then what role is the FXML Controller really playing?\nIt\u0026rsquo;s pretty obvious, when you look at it this way, that the FXML Controller is actually part of the View.\nBack to That Oracle Quote # I\u0026rsquo;ll repeat it again here:\nJavaFX enables you to design with Model-View-Controller (MVC), through the use of FXML and Java. The \u0026ldquo;Model\u0026rdquo; consists of application-specific domain objects, the \u0026ldquo;View\u0026rdquo; consists of FXML, and the \u0026ldquo;Controller\u0026rdquo; is Java code that defines the GUI\u0026rsquo;s behavior for interacting with the user.\nThe big problem that we have here are their definitions of the parts of MVC. First:\nThe \u0026ldquo;Model\u0026rdquo; consists of application-specific domain objects\nWell, yes, the Model does contain domain objects, but it also contains the Presentation Model and the application logic.\nthe \u0026ldquo;View\u0026rdquo; consists of FXML\nExcept that the FXML file simply describes the layout, styling and some configuration. This is not a View, this just layout.\nand the \u0026ldquo;Controller\u0026rdquo; is Java code that defines the GUI\u0026rsquo;s behavior for interacting with the user\nThis is just dead wrong. That\u0026rsquo;s not role of the Controller in MVC, but it is the role of the FXML Controller as part of the View.\nSeparation of Concerns # Every time I ask someone what they think the value is of FXML (aside from using SceneBuilder) they\u0026rsquo;ll inevitably respond with:\nIt ensures separation of concerns.\nIt must be out there in some popular tutorial somewhere.\nWell, to be sure, separation of concerns is an awesome concept. Where I came from we called it \u0026ldquo;loose coupling\u0026rdquo;, but it\u0026rsquo;s all the same, really. We should all strive to have loose coupling between the components of our applications. It\u0026rsquo;s main purpose of using a framework like MVC - to keep the business logic far, far away from the GUI and visa versa.\nBut does FXML give you \u0026ldquo;separation of concerns?\u0026rdquo;\nWell, it does split the layout from the other parts of the View. So that\u0026rsquo;s something.\nBut if you treat the FXML Controller as an MVC Controller, then you immediately start to squash all of those concerns together again. This is because the FXML Controller is full of View stuff, and the MVC Controller is supposed to be full of action stuff. Now you have your View stuff sitting in the same place as your action stuff and it very quickly gets hopelessly entangled.\nAnd then it gets worse.\nIt\u0026rsquo;s that \u0026ldquo;The Model consists of domain objects\u0026rdquo; bit from the Oracle tutorial. Many FXML programmers think that they\u0026rsquo;ve made a \u0026ldquo;Model\u0026rdquo; when they\u0026rsquo;ve created a few POJO domain objects. So they\u0026rsquo;ve created a \u0026ldquo;Customer\u0026rdquo; object, and maybe an \u0026ldquo;Order\u0026rdquo; object and these will hold the data that\u0026rsquo;s retrieved from the database, and that\u0026rsquo;s it - they\u0026rsquo;re done - Model created.\nBut where does the application logic go??????\nAnd, you know, there\u0026rsquo;s only one place to put it. The FXML Controller!\nAnd what about the Presentation Model? It becomes fields in the FXML Controller.\nAnd where do those domain objects get stored? Once again\u0026hellip;as fields in the FXML Controller.\nSo off they go, and now there\u0026rsquo;s database access code sitting in the FXML Controller - probably in some code that defines an EventHandler for a Button.\nYou can\u0026rsquo;t get more coupled than that.\nThe result is that they\u0026rsquo;ve created a monolithic, single-class application with everything muddled together. This isn\u0026rsquo;t an exaggeration, virtually every example of an FXML application that you\u0026rsquo;ll find out there on the internet does exactly this.\nBut there\u0026rsquo;s separation of concerns! The layout is tucked away in the FXML file!\nHow It Should Look # The key thing to understand is that the FXML and the FXML Controller, together with the FXML Loader, create the View. It\u0026rsquo;s that simple.\nYou\u0026rsquo;re on your own to create the Controller and the Model, and you need to understand the framework if you\u0026rsquo;re going to be able to do it.\nLet\u0026rsquo;s take a look at a simple MVC application and see how the parts work together. I think that FXML complicates the issue at the start, so I\u0026rsquo;ll just create a Region subclass to be my View, and then we\u0026rsquo;ll look at how you\u0026rsquo;d implement FXML later on.\nLet\u0026rsquo;s look at the View first:\npublic class View extends VBox { private final Model.PresentationModel viewModel; private final BooleanProperty showProgress = new SimpleBooleanProperty(false); private final Consumer\u0026lt;Runnable\u0026gt; actionHandler; public View(Model.PresentationModel viewModel, Consumer\u0026lt;Runnable\u0026gt; actionHandler) { this.viewModel = viewModel; this.actionHandler = actionHandler; initializeLayout(); } private void initializeLayout() { getChildren().addAll(createTopBox(), createButton()); setPadding(new Insets(30)); } private Node createButton() { Button button = new Button(\u0026#34;Start\u0026#34;); button.setOnAction(evt -\u0026gt; { showProgress.set(true); button.setDisable(true); actionHandler.accept(() -\u0026gt; { showProgress.set(false); button.setDisable(false); }); }); return button; } private Region createTopBox() { StackPane results = new StackPane(); results.getChildren().addAll(createDataBox(), createProgressIndicator()); return results; } private Node createDataBox() { Label prompt = new Label(\u0026#34;Number of Cycles:\u0026#34;); prompt.getStyleClass().add(\u0026#34;label-text\u0026#34;); HBox inputBox = new HBox(6, prompt, createTextField()); inputBox.setAlignment(Pos.CENTER); VBox results = new VBox(20, inputBox, createDataLabel()); results.setAlignment(Pos.CENTER); results.visibleProperty().bind(showProgress.not()); return results; } @NotNull private Label createDataLabel() { Label dataLabel = new Label(); dataLabel.textProperty().bind(viewModel.theResultProperty()); dataLabel.getStyleClass().add(\u0026#34;data-text\u0026#34;); return dataLabel; } private Node createTextField() { TextField textField = new TextField(); TextFormatter\u0026lt;Long\u0026gt; textFormatter = new TextFormatter\u0026lt;\u0026gt;(new LongStringConverter()); textField.setTextFormatter(textFormatter); textField.setMaxWidth(120.0); textFormatter.valueProperty().bindBidirectional(viewModel.cycleCountProperty()); return textField; } private Node createProgressIndicator() { ProgressIndicator progressIndicator = new ProgressIndicator(); progressIndicator.progressProperty().bind(viewModel.progressProperty()); progressIndicator.setMinSize(200, 200); progressIndicator.visibleProperty().bind(showProgress); progressIndicator.visibleProperty().addListener(observable -\u0026gt; { if (progressIndicator.isVisible()) { Transition transition = new Transition() { { setCycleDuration(Duration.millis(2000)); } @Override protected void interpolate(double v) { progressIndicator.setOpacity(v); } }; transition.play(); } }); return progressIndicator; } } It\u0026rsquo;s just a VBox with a StackPane above a Button. In the StackPane are a ProgressIndicator and HBox holding a Label and a TextField. Either the ProgressIndicator or the HBox is visible at any given time, but never both at the same time.\nNow, let\u0026rsquo;s look at the Model:\npublic class Model { private final PresentationModel presentationModel = new PresentationModel(); private String domainObject = \u0026#34;Nothing Yet\u0026#34;; public Model() { presentationModel.setTheResult(domainObject); } void doSomethingComplicated(BiConsumer\u0026lt;Long, Long\u0026gt; progressUpdater) { for (long idx = 0; idx \u0026lt; presentationModel.getCycleCount(); idx++) { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } progressUpdater.accept(idx, presentationModel.getCycleCount()); } domainObject = \u0026#34;Something was found\u0026#34;; } void integrateComplicatedResults() { presentationModel.setTheResult(domainObject); } public PresentationModel getPresentationModel() { return presentationModel; } public static class PresentationModel { private final DoubleProperty progress = new SimpleDoubleProperty(0.0); private final ObjectProperty\u0026lt;Long\u0026gt; cycleCount = new SimpleObjectProperty\u0026lt;\u0026gt;(5L); private final StringProperty theResult = new SimpleStringProperty(\u0026#34;\u0026#34;); public double getProgress() { return progress.get(); } public DoubleProperty progressProperty() { return progress; } public void setProgress(double progress) { this.progress.set(progress); } public long getCycleCount() { return cycleCount.get(); } public ObjectProperty\u0026lt;Long\u0026gt; cycleCountProperty() { return cycleCount; } public void setCycleCount(long cycleCount) { this.cycleCount.set(cycleCount); } public String getTheResult() { return theResult.get(); } public StringProperty theResultProperty() { return theResult; } public void setTheResult(String theResult) { this.theResult.set(theResult); } } } And the Controller:\npublic class Controller { private final View view; private Model model = new Model(); public Controller(long initialCycleCount) { model.getPresentationModel().setCycleCount(initialCycleCount); view = new View(model.getPresentationModel(), this::startSomethingBig); } private void startSomethingBig(Runnable postRunGUIAction) { Task\u0026lt;Void\u0026gt; bigTask = new Task\u0026lt;Void\u0026gt;() { @Override protected Void call() { model.doSomethingComplicated(this::updateProgress); return null; } }; bigTask.setOnSucceeded(evt -\u0026gt; { model.getPresentationModel().progressProperty().unbind(); model.integrateComplicatedResults(); postRunGUIAction.run(); }); Thread bigTaskThread = new Thread(bigTask); model.getPresentationModel().progressProperty().bind(bigTask.progressProperty()); bigTaskThread.start(); } public View getView() { return view; } } Here\u0026rsquo;s the Main class, and you can see how it interacts with the Controller to get things running:\npublic class Main extends Application { @Override public void start(Stage stage) throws Exception { Scene scene = new Scene(new Controller(6L).getView()); scene.getStylesheets().add(getClass().getResource(\u0026#34;/css/default.css\u0026#34;).toExternalForm()); stage.setScene(scene); stage.show(); } } When it runs, it looks like this at first:\nWhen the \u0026ldquo;complicated process\u0026rdquo; is running, it looks like this:\nAnd when it\u0026rsquo;s all done, it looks like this:\nThe View class is clearly doing all of the View stuff and nothing but the View stuff. It handles the layout, the View related Button interaction, styling, the configuration of the TextField and binding to the Presentation Model. I even put a fade-in transition on the ProgressIndicator so that you could see how it\u0026rsquo;s all handled by the View.\nThe View reveals nothing of its inner workings to any of the other components.\nThe View has only two dependencies, and they are clearly declared in its constructor: it needs the Presentation Model, and the action handler for the Button (or whatever triggers the action). It takes a Consumer\u0026lt;Runnable\u0026gt; for this action handler, because it needs to perform the cleanup of the GUI after the action has completed. Note that there\u0026rsquo;s nothing in this action handler that requires the GUI element to be a Button, or even something that generates an ActionEvent. It could be a mouse-over or a keystroke, for example.\nThe Model is very specifically designed and named in this example to make the intent of the structure clear. The Presentation Data is split out into an enclosed class called Model.PresentationModel, and instantiated into a field called presentationModel. There\u0026rsquo;s a field called domainData, to make a point about how domain data can be handled.\nHowever, the PresentationModel is split out so that it can be passed to the View without the need to expose all of the other public methods of the Model to the View. In this way, the View is ignorant of the Model itself, and only has knowledge of the PresentationModel on which it depends.\nThe Controller does just what it is supposed to do, it turns the user interaction into an action. That\u0026rsquo;s the Controller.startSomethingBig() method. It handles the threading, and provides the connection back to the View (through the Presentation Model) to handle the progress monitoring of the process.\nThere\u0026rsquo;s also a constructor parameter for the Controller, to illustrate how the other parts of the GUI would interact with each other through their Controllers. In this case, it\u0026rsquo;s just the Application class itself, but you get the idea.\nOne Small Issue # If you are being technically nit-picky (and there\u0026rsquo;s nothing wrong with that), you\u0026rsquo;ll notice that this isn\u0026rsquo;t quite MVC.\nIn MVC, the View is allowed to read from the Presentation Model directly but it\u0026rsquo;s not allowed to update it directly. In this example, we\u0026rsquo;re using bi-directional binding to connect the TextField value to the Presentation Model. That means that anything typed in by the user, immediately updates the Presentation Model without the intervention of the Controller.\nHonestly, I don\u0026rsquo;t know a way around this that isn\u0026rsquo;t really clumsy. That quote from Martin Fowler\u0026rsquo;s website continues with:\nFrankly a lot of the reason for this is that parts of classic MVC don\u0026rsquo;t really make sense for rich clients these days.\nMVC does seem to be aware of binding-like capabilities, but it predates the JavaFX Reactive library of Observables, Properties and Bindings by decades. So, I think that in this respect we\u0026rsquo;ll have to accept that JavaFX works best with something that\u0026rsquo;s close to MVC, but not quite MVC.\nIn any case, nothing about the structure of this example, using the bi-directional binding, compromises the fundamental goal of MVC - which is to provide for loose coupling between the View and the application logic. So I think we\u0026rsquo;re still good here.\nCleaning This Up a Bit # This example was very deliberately designed to point out the MVC structure. There\u0026rsquo;s a class called \u0026ldquo;Controller\u0026rdquo;, another called \u0026ldquo;Model\u0026rdquo; and one called \u0026ldquo;View\u0026rdquo;. Model has an encapsulated class called \u0026ldquo;PresentationModel\u0026rdquo; and a field called \u0026ldquo;domainData\u0026rdquo; so that you can see how Presentation Data is different from the Domain Data.\nBut there\u0026rsquo;s a problem with having a View that extends VBox. There\u0026rsquo;s nothing stopping the Controller from defining its view field as VBox, and then, if we were to change the structure of the View class, we\u0026rsquo;d have to modify the Controller class as well.\nIt\u0026rsquo;s far better to have \u0026ldquo;Builder\u0026rdquo; for the View that returns something very generic like Region. It wasn\u0026rsquo;t done this way originally, because then we\u0026rsquo;d have a class called ViewBuilder instead of View. But now you\u0026rsquo;ve seen it the first way, we can refactor this. Here\u0026rsquo;s the ViewBuilder:\npublic class ViewBuilder implements Builder\u0026lt;Region\u0026gt; { private final Model.PresentationModel viewModel; private final BooleanProperty showProgress = new SimpleBooleanProperty(false); private final Consumer\u0026lt;Runnable\u0026gt; actionHandler; public ViewBuilder(Model.PresentationModel viewModel, Consumer\u0026lt;Runnable\u0026gt; actionHandler) { this.viewModel = viewModel; this.actionHandler = actionHandler; } public Region build() { VBox results = new VBox(10); results.getChildren().addAll(createTopBox(), createButton()); results.setPadding(new Insets(30)); return results; } private Node createButton() { Button button = new Button(\u0026#34;Start\u0026#34;); button.setOnAction(evt -\u0026gt; { showProgress.set(true); button.setDisable(true); actionHandler.accept(() -\u0026gt; { showProgress.set(false); button.setDisable(false); }); }); return button; } private Region createTopBox() { StackPane results = new StackPane(); results.getChildren().addAll(createDataBox(), createProgressIndicator()); return results; } private Node createDataBox() { Label prompt = new Label(\u0026#34;Number of Cycles:\u0026#34;); prompt.getStyleClass().add(\u0026#34;label-text\u0026#34;); HBox inputBox = new HBox(6, prompt, createTextField()); inputBox.setAlignment(Pos.CENTER); VBox results = new VBox(20, inputBox, createDataLabel()); results.setAlignment(Pos.CENTER); results.visibleProperty().bind(showProgress.not()); return results; } @NotNull private Label createDataLabel() { Label dataLabel = new Label(); dataLabel.textProperty().bind(viewModel.theResultProperty()); dataLabel.getStyleClass().add(\u0026#34;data-text\u0026#34;); return dataLabel; } private Node createTextField() { TextField textField = new TextField(); TextFormatter\u0026lt;Long\u0026gt; textFormatter = new TextFormatter\u0026lt;\u0026gt;(new LongStringConverter()); textField.setTextFormatter(textFormatter); textField.setMaxWidth(120.0); textFormatter.valueProperty().bindBidirectional(viewModel.cycleCountProperty()); return textField; } private Node createProgressIndicator() { ProgressIndicator progressIndicator = new ProgressIndicator(); progressIndicator.progressProperty().bind(viewModel.progressProperty()); progressIndicator.setMinSize(200, 200); progressIndicator.visibleProperty().bind(showProgress); progressIndicator.visibleProperty().addListener(observable -\u0026gt; { if (progressIndicator.isVisible()) { Transition transition = new Transition() { { setCycleDuration(Duration.millis(2000)); } @Override protected void interpolate(double v) { progressIndicator.setOpacity(v); } }; transition.play(); } }); return progressIndicator; } } The changes here are that instead of extending VBox this class now implements Builder\u0026lt;Region\u0026gt; and the initializeLayout() method has been renamed to build(), made public and returns Region. It\u0026rsquo;s also no longer called from the constructor. Everything else in the class remains exactly the same.\nNow the new Controller class:\npublic class Controller { private final ViewBuilder viewBuilder; private Model model = new Model(); public Controller(long initialCycleCount) { model.getPresentationModel().setCycleCount(initialCycleCount); viewBuilder = new ViewBuilder(model.getPresentationModel(), this::startSomethingBig); } private void startSomethingBig(Runnable postRunGUIAction) { Task\u0026lt;Void\u0026gt; bigTask = new Task\u0026lt;Void\u0026gt;() { @Override protected Void call() { model.doSomethingComplicated(this::updateProgress); return null; } }; bigTask.setOnSucceeded(evt -\u0026gt; { model.getPresentationModel().progressProperty().unbind(); model.integrateComplicatedResults(); postRunGUIAction.run(); }); Thread bigTaskThread = new Thread(bigTask); model.getPresentationModel().progressProperty().bind(bigTask.progressProperty()); bigTaskThread.start(); } public Region getView() { return viewBuilder.build(); } } The main change here is that Controller.getView() now delegates to ViewBuilder.build().\nImplementing FXML # At this point it should be clear that the nature of the View has been completely isolated from the Controller and the Model, and we can implement it any way we want - just so long as we can use the PresentationModel and the actionHandler that come from the Controller.\nThis means we could even implement it as FXML! So that\u0026rsquo;s what we\u0026rsquo;re going to do.\nI\u0026rsquo;m not going to invest a lot of time and effort into creating a cool design that has all of the functionality of the original View in FXML. We just need something that will work. I\u0026rsquo;m also not installing SceneBuilder, so I\u0026rsquo;m hand creating a simple FXML and it\u0026rsquo;s going to be absolutely minimal.\nThe FXML File:\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;?import javafx.scene.control.Button?\u0026gt; \u0026lt;?import javafx.scene.layout.BorderPane?\u0026gt; \u0026lt;BorderPane fx:id=\u0026#34;borderPane\u0026#34; xmlns=\u0026#34;http://javafx.com/javafx/19\u0026#34; xmlns:fx=\u0026#34;http://javafx.com/fxml/1\u0026#34;\u0026gt; \u0026lt;center\u0026gt; \u0026lt;Button fx:id=\u0026#34;button\u0026#34;\u0026gt;\u0026lt;/Button\u0026gt; \u0026lt;/center\u0026gt; \u0026lt;/BorderPane\u0026gt; Pretty simple, just a button in a BorderPane. I\u0026rsquo;m not doing any styling here either, it\u0026rsquo;s pure layout. We have fx:id for both the BorderPane and the Button, so we\u0026rsquo;ll do the rest in the FXML Controller:\npublic class FxmlController implements Initializable { private final Model.PresentationModel viewModel; private final Consumer\u0026lt;Runnable\u0026gt; actionHandler; @FXML BorderPane borderPane; @FXML Button button; public FxmlController(Model.PresentationModel viewModel, Consumer\u0026lt;Runnable\u0026gt; actionHandler) { this.viewModel = viewModel; this.actionHandler = actionHandler; } @Override public void initialize(URL url, ResourceBundle resourceBundle) { button.textProperty().bind(viewModel.theResultProperty()); borderPane.setPadding(new Insets(20)); borderPane.setMinWidth(200); button.setOnAction(evt -\u0026gt; { button.setDisable(true); actionHandler.accept(() -\u0026gt; { button.setDisable(false); }); }); } } The only component of the MVC structure that we need to change is the ViewBuilder:\npublic class ViewBuilder implements Builder\u0026lt;Region\u0026gt; { private final Model.PresentationModel viewModel; private final Consumer\u0026lt;Runnable\u0026gt; actionHandler; public ViewBuilder(Model.PresentationModel viewModel, Consumer\u0026lt;Runnable\u0026gt; actionHandler) { this.viewModel = viewModel; this.actionHandler = actionHandler; } @Override public Region build() { FXMLLoader loader = new FXMLLoader(ViewBuilder.class.getResource(\u0026#34;/fxml/view.fxml\u0026#34;)); loader.setController(new FxmlController(viewModel, actionHandler)); try { return loader.load(); } catch (IOException e) { return new BorderPane(); } } } When it starts up, it looks like this:\nWhile the process is running, it looks like this:\nAnd ends up like this:\nKey Take-Aways # First, and most importantly, is that the FXML based View is way different from the hand coded View, but NOTHING needed to be changed in the Controller or the Model. The coupling is completely defined by the constructor parameters of the ViewBuilder, and those did not change.\nThis is super important to understand because it is the entire point of MVC.\nSecondly, the FXML based View doesn\u0026rsquo;t display or allow the user to change the cycle count, nor does it use the progress property. This is OK. There\u0026rsquo;s no rule that says that every possible View has to use the entire data set in the Presentation Model. Perhaps we just want a minimalist widget to stick in the corner of a screen - it\u0026rsquo;s a valid implementation of the View component.\nThird, just as with the hand coded View, there\u0026rsquo;s no need, or even temptation, to put calls to external services or API\u0026rsquo;s in the View code. This FXML Controller exists solely to work with the FXML file to create the View. Nothing more.\nFinally, the fact that this View is FXML is utterly irrelevant to the rest of the MVC framework. It doesn\u0026rsquo;t change the structure of the framework, and it doesn\u0026rsquo;t change the approach.\nAnd that\u0026rsquo;s how you achieve \u0026ldquo;separation of concerns\u0026rdquo; with FXML.\n","date":"20 February 2023","externalUrl":null,"permalink":"/javafx/techniques/nofxml/2023-02-24-fxml-is-not-mvc/","section":"JavaFX","summary":"Introduction # OK, first off I know I’m swimming against the stream here, and at odds with things like this found in some of the tutorial pages from Oracle for JavaFX:\n","title":"FXML is NOT Model-View-Controller","type":"javafx"},{"content":"","date":"28 January 2023","externalUrl":null,"permalink":"/categories/kotlin/","section":"Categories","summary":"","title":"Kotlin","type":"categories"},{"content":" What is Kotlin? # Kotlin is a multi-purpose programming language that runs in the JVM. In many ways, it\u0026rsquo;s what Java would have been had it been designed 20 years later.\nKotlin code is much more concise than the equivalent Java. It has native features that address many of the gripes that programmers have about Java, including the following:\nNull safety is built-in. Fields are implemented as Properties that include default (but configurable) getters and setters. The ability to add methods and fields to classes without formally extending them. Semicolons are optional! Automatic inference of data types when declaring Variables Default values in method signatures, reducing the need to overload methods. Kotlin is 100% compatible with Java, so you can freely use any libraries that are written in or for Java. With a few caveats, you can also call all of you Kotlin code from Java. You can even mix Kotlin in with Java in the same project!\nOf course, Kotlin works really well with JavaFX. In fact, JavaFX is way better with Kotlin than it is with Java.\nRead More\u0026hellip; # I\u0026rsquo;ve written a few articles about Kotlin, you can find them here:\n","date":"28 January 2023","externalUrl":null,"permalink":"/kotlin/","section":"Kotlin","summary":"What is Kotlin? # Kotlin is a multi-purpose programming language that runs in the JVM. In many ways, it’s what Java would have been had it been designed 20 years later.\n","title":"Kotlin","type":"kotlin"},{"content":" Introduction # I\u0026rsquo;ve been using Kotlin for a while now, and I\u0026rsquo;ve been using it to write JavaFX applications. What I\u0026rsquo;ve found is that the things that make Kotlin better than Java, make Kotlin way, way better for JavaFX.\nIf you\u0026rsquo;re new to Kotlin, you might want to read my Kotlin for Java Programmers article.\nLet\u0026rsquo;s get one thing out of the way first\u0026hellip;\nDo You Need TornadoFX? # No.\nA lot of people are under the impression that TornadoFX is the implementation of JavaFX in Kotlin, and that you need it in order to use JavaFX with Kotlin.\nThis is simply not true. TornadoFX is an interesting library that does some cool things, but it\u0026rsquo;s not any kind of replacement for the JavaFX library.\nSo, what is TornadoFX?\nMore than anything else, it\u0026rsquo;s a wrapper for the standard JavaFX library that attempts to make using JavaFX less verbose and more \u0026ldquo;Kotlin-like\u0026rdquo;. It tackles the verbosity by supplying generic \u0026ldquo;builders\u0026rdquo; that make use of extension functions on Node classes to enable a more declarative style of configuration for layouts. It also incorporates tools to use an MVC framework, also in a manner to make it as less verbose as possible.\nAnd it\u0026rsquo;s very good at doing this. But this approach has a couple of issues:\nRepeated Configurations # If you spend any amount of time programming with JavaFX, you\u0026rsquo;re going to find that there are certain patterns of configuration and styling that you use over and over and over. And, if you\u0026rsquo;re using style sheets to handle your styling (as you should), then you\u0026rsquo;re going to be using most of those styles in the same way, over and over and over.\nWant you really want to avoid is repeating that code over and over and over. Apply DRY (Don\u0026rsquo;t Repeat Yourself), and put that code in a library that you can call. Any such library that you create is going to be highly personalized to yourself or your team.\nTornadoFX, is all about making it easier to write that code inside your layout. In many cases your layout code becomes composed of configuring builders that will generate your layout. The builders are nicely designed to streamline the code, but don\u0026rsquo;t move you towards DRY. You can probably do it, but it moves your task from writing builders for Nodes to writing builders for TornadoFX builders.\nConfiguration and Styling in the Layout # Let\u0026rsquo;s take a look at an example from the TornadoFX documentation, this shows a call to one of their builders:\ntableview\u0026lt;Person\u0026gt; { items = persons column(\u0026#34;ID\u0026#34;, Person::idProperty) column(\u0026#34;Name\u0026#34;, Person::nameProperty) column(\u0026#34;Birthday\u0026#34;, Person::birthdayProperty) readonlyColumn(\u0026#34;Age\u0026#34;, Person::age).cellFormat { text = it.toString() style { if (it \u0026lt; 18) { backgroundColor += c(\u0026#34;#8b0000\u0026#34;) textFill = Color.WHITE } } } } This would become your layout code with TornadoFX. You can see how this strips away a lot of the boilerplate that you\u0026rsquo;d need with a pure JavaFX implementation, and the result absolutely does do more with less.\nBut that \u0026ldquo;Age\u0026rdquo; column.\nYes, the customization of the Cell is way more straightforward than in pure JavaFX, but it\u0026rsquo;s still a clear violation of the \u0026ldquo;Single Responsibility Principle\u0026rdquo; - it should be delegated.\nBut the design of TornadoFX encourages you to put the configuration and styling in with the layout.\nSo, the question is whether or not the streamlining of the code compensates for the complication of having the configuration and styling mingled in with the layout.\nShould You Use TornadoFX? # If you\u0026rsquo;re following the ideas in this website - building Reactive layouts with MVCI and sticking to the \u0026ldquo;Single Responsibility Principle\u0026rdquo; and DRY - there\u0026rsquo;s probably not a lot of benefit to using TornadoFX.\nTornadoFX tries to deal with the complexity by simplifying the integration of the configuration into the layout code. What it doesn\u0026rsquo;t do is remove the configuration from your layouts, which, in my opinion, is a better approach.\nKotlin JavaFX Techniques # There are a number of cool features in Kotlin that make JavaFX a lot easier to implement, and some features that mean you need to take a look at how you should build things in Kotlin. Let take a look at some of these\u0026hellip;\nThe .apply{} Function # This is the star in Kotlin for JavaFX. When you call Object.apply{} it passes the object to the lambda in the {} as this, and apply{} returns the original object. This is especially useful in the pattern val x = Node().apply{}.\nLet\u0026rsquo;s say you\u0026rsquo;ve got a Label that you want have display some data the screen and bound to a Property in your model. You have a style class set up for, too.\nIn Java:\nLabel label = new Label(); label.getStyleClass().add(\u0026#34;data-label\u0026#34;); label.textProperty().bind(model.someDataProperty()); hbox.getChildren().add(label); But in Kotlin, you can use .apply{} and avoid instantiating label altogether:\nhbox.children += Label().apply{ styleClass += \u0026#34;data-label\u0026#34; textProperty().bind(model.someData) } (Note that, just like with a class in Java, you can skip the this. when referring to member fields and functions)\nNow, that might not seem like much, but it does lead to things like this:\nhBox = HBox(10.0, Label(\u0026#34;Name: \u0026#34;).apply{styleClass += \u0026#34;prompt-label\u0026#34;}, Label().apply{ styleClass += \u0026#34;data-label\u0026#34; textProperty().bind(model.someData) }) This can get a bit garbled (something to watch out for with .apply{}), so you could try:\nhBox = HBox(10.0).apply{ children += Label(\u0026#34;Name: \u0026#34;).apply{ styleClass += \u0026#34;prompt-label\u0026#34; } children += Label().apply{ styleClass += \u0026#34;data-label\u0026#34; textProperty().bind(model.someData) } } Perhaps even more useful is that you create functions using the same technique:\nhBox = HBox(10.0).apply{ children += listOf(Label(\u0026#34;Name: \u0026#34;).apply{styleClass += \u0026#34;prompt-label\u0026#34;}, dataLabel(model.someData)) styleClass += \u0026#34;red-border-box\u0026#34; } fun dataLabel(boundValue : ObservableStringValue) = Label().apply{ styleClass += \u0026#34;data-label\u0026#34; textProperty().bind(boundValue) } JavaFX Property Fields # Kotlin uses the term \u0026ldquo;property\u0026rdquo; to refer to what would be called \u0026ldquo;fields\u0026rdquo; in Java. Kotlin properties are actually more complicated structures that have integrated getters and setters (which can be customized) which are automatically called when you access the property. This means that you can directly reference the property from outside the class without worrying about tying down your internal implementation.\nOf course, in JavaFX a Property is specific kind of ObservableValue, which is quite different from a Kotlin property. Additionally, there is the concept of a Property Bean, which is a particular structure exposing the Property outside its containing class.\nA Property Bean has three methods in its containing class. For a Property called fred you would have the following methods:\nA getter for the Property value called getFred(). A setter for the Property value called setFred(). A getter for the property itself called fredProperty() In Kotlin, you can\u0026rsquo;t just write the getFred() and setFred() methods because they would conflict with the internal getter and setter for fred. So you have to do something a little bit different. I wrote an entire article about this a while back.\nHere\u0026rsquo;s a sample program from the original article, showing how this is done.\n{% gist f6d3bd9dfc261b6d411d7945172d5833 %}\nAnd the output looks like this:\nNickname: Shorty Property: ObjectProperty [value: Shorty] That gets the job done, and it allows you direct access to the values of the Property via Class.fred.\nThe JavaFX Property Bean is the accepted way to implement Properties in a Model, but\u0026hellip;\nDo you really need Property Beans? # This naming convention is primarily designed to facilitate those tools that use reflection in the class PropertyReference to access fields via a String name. In the standard JavaFX library, this class is used in the following places: PropertyValueFactory, TreeViewPropertyValueFactory and variants of Bindings.select(). The first two should probably be deprecated, because you shouldn\u0026rsquo;t use them any more now that we have lambdas and writing the equivalent Callback is trivial.\nThe last case is about 14 methods in the Bindings class that, \u0026ldquo;Create a binding used to get a member, such as a.b.c.\u0026rdquo;, according to the JavaDocs. Personally, I have never used Bindings.select(), and I suspect that it\u0026rsquo;s primarily intended when you want to pass around Property names as Strings and then directly use those names for binding.\nOf course, if you\u0026rsquo;re using external JavaFX libraries, they may require that you use the Property Bean format because they are using something that incorporates PropertyReference.\nBut\u0026hellip;if you\u0026rsquo;re not doing any of those things, you can ditch the Property Bean. Just make your Property property a val you\u0026rsquo;re done.\nPersonally, I\u0026rsquo;ve stopped habitually using the Property Bean pattern because it just doesn\u0026rsquo;t add any value for me.\nExtension Functions, Package Functions and Operator Overloading # The next few features go together produce some really powerful techniques which are the core tools you can use to compress your layout code.\nExtension Functions # One of the coolest features of Kotlin is the ability to extend a class without creating a new class. With this technique you can add both methods and data to an existing class without having access to the source of that class.\nFor instance, you can do something like this:\nfun Int.plusThree() : Int = this + 3 . . . val x: Int = 7 println(\u0026#34;The answer: ${x.plusThree()}\u0026#34;) This can be amazingly valuable with JavaFX, where there are a lot of classes that you use all the time where it would be handy to have methods that do something with a single call that normally take a few steps.\nExtension functions can also be used as decorators using .apply{}:\nprivate fun createContent(): Region = BorderPane().apply { val model: StringProperty = SimpleStringProperty(\u0026#34;Albert\u0026#34;) padding = Insets(20.0) center = HBox(10.0, Label().bound(model), Label(\u0026#34;George\u0026#34;)) .styled(\u0026#34;test-border3\u0026#34;) .padded(10.0) .aligned(Pos.CENTER) } } fun \u0026lt;T : Node\u0026gt; T.styled(newStyleClass: String): T = apply { styleClass += newStyleClass } fun \u0026lt;T : Region\u0026gt; T.padded(padSize: Double): T = apply { padding = Insets(padSize) } fun \u0026lt;T : Labeled\u0026gt; T.bound(otherProperty: ObservableValue\u0026lt;String\u0026gt;): T = apply { textProperty().bind(otherProperty) } fun HBox.aligned(pos: Pos): HBox = apply { alignment = pos } For this, however, it\u0026rsquo;s important to remember that extension functions are evaluated statically. This means that if you want a decorator to return the original type for any of the subclasses of a type you need to use the \u0026lt;T : Class\u0026gt; T. declaration. In this example, the setAlignment() method is included at the HBox class itself, so you can declare the decorator directly at HBox; getStyleClass() is defined at the Node level, so \u0026lt;T: Node\u0026gt; T is used; and setPadding() is defined at the Region level, so \u0026lt;T: Region\u0026gt; T is used.\nPackage Functions # Another cool feature in Kotlin is \u0026ldquo;Package Functions\u0026rdquo;. These are methods that aren\u0026rsquo;t part of any specific class, they\u0026rsquo;re just functions attached to a package. In this way they are very similar to static methods in Java, but you don\u0026rsquo;t have the baggage of having to call them with ClassName.staticMethod() format. This makes a dramatic difference in how the resulting code looks:\nfun labelOf(boundValue : StringProperty) : Label = Label().apply {textProperty().bind(boundValue)} . . . val hbox = HBox(10.0, Label(\u0026#34;Name:\u0026#34;), labelOf(model.nameProperty)) Operator Overloading # Operator Overloading is another nifty idea that Kotlin brings to the table which can simplify your layout code. The idea is that a specific set of operators are automatically associated with specific functions if they are defined in a class as operator. So the \u0026ldquo;+\u0026rdquo; operator is associated with class.plus() if it exists.\nBut here\u0026rsquo;s the cool part: The operator functions can be extension functions!\nThis means that you can add operator overloads to any of the JavaFX classes. Let\u0026rsquo;s see how this works. To me, the \u0026ldquo;+=\u0026rdquo; operator seems like a good fit to represent binding on a Property. Here\u0026rsquo;s how you would do that:\noperator fun StringProperty.plusAssign(otherProperty: StringProperty) = this.bind(otherProperty) . . . val model : StringProperty = SimpleStringProperty(\u0026#34;ABC\u0026#34;) val label = Label() label.textProperty() += model Or, if you prefer\u0026hellip;\noperator fun Labeled.plusAssign(otherProperty: StringProperty) = this.textProperty().bind(otherProperty) . . . val model : StringProperty = SimpleStringProperty(\u0026#34;ABC\u0026#34;) val label = Label() label += model Infix Functions # If a function of a class has a single parameter, then you can declare it as Infix. This means that you can drop the \u0026ldquo;.\u0026rdquo; and the brackets when making the call. The result is something that looks very declarative. This is especially true if you implement decorator functions as infix:\ninfix fun \u0026lt;T : Labeled\u0026gt; T.styledAs(labelStyle: String) = apply { styleClass += labelStyle } infix fun \u0026lt;T : Labeled\u0026gt; T.boundTo(value: ObservableStringValue) = apply { textProperty().bind(value) } fun testIt(): Unit { val fred: StringProperty = SimpleStringProperty(\u0026#34;abc\u0026#34;) val label1 = Label() styledAs \u0026#34;label-prompt\u0026#34; boundTo fred val label3 = Label().styledAs(LabelStyle.PROMPT).boundTo(fred) } The first two methods are declared as infix, but you don\u0026rsquo;t have to use them that way. These are all also decorator style methods that return the enclosing object. That means that you can string them together. The first calling example shows how this works, and the second shows it without the infix calling style. The two versions are functionally equivalent.\nYou have to be a little bit careful with the order of operations with infix, but chaining the decorators together like this works nicely.\nPutting it All Together # If you\u0026rsquo;re looking to create a standard toolkit that you can use over a variety of projects, then your best approach is to create Kotlin files in their own package to house your functions. You can call it FxExtentions.kt or ExtensionsFX.kt or WidgetsFX.kt. You don\u0026rsquo;t need to have a class of the same name, you can just drop all of your package functions and extension functions in there as you like. You can even add some custom classes, Kotlin\u0026rsquo;s cool with that.\nThis will get all this stuff right out of your layout code, and out of your mind - where you don\u0026rsquo;t want it.\nMy approach is to create a complementary CSS file to go with the code and then write the various utility functions to work with that CSS file.\nNaming becomes very important, and you have to think about how the code will read using the names that picked. This could also depend on how you intend to use the utilities. Consider this:\nval label = Label() styledAs LabelStyle.DATA boundTo model.someDataProperty Here the decorators have all been name as past-tense verbs, essentially declaring their effects. I think this reads nicely as a description of the Node; it\u0026rsquo;s a Label styled as data and bound to a property.\nHowever, you could use this:\nval label = Label() styleAs LabelStyle.DATA bindTo model.someProperty Now the decorators read as commands. This version is more declarative, and not descriptive.\nBut, if you don\u0026rsquo;t like the infix usage, then you would have:\nval label = Label.styledAs(LabelStyle.DATA).boundTo(model.someDataProperty) or\u0026hellip;\nval label = Label.styleAs(LabelStyle.DATA).bindTo(model.someDataProperty) Since we\u0026rsquo;re used to traditional function call being named as actions, the second version feels more natural. But both ways could possibly work.\nUtility Library Files # With these techniques you can create a toolkit that will make your Kotlin layout code look much more streamlined and declarative than Java code. The trick is to find all the \u0026ldquo;pain points\u0026rdquo; in your layout code, the places that you get bogged down in details, and move them into your library of functions.\nHere\u0026rsquo;s what I came up with in just a few minutes. First, WidgetFX.kt:\npackage ca.pragmaticcoding.widgetsfx fun Scene.addWidgetStyles() = apply { object {}::class.java.getResource(\u0026#34;widgetsfx.css\u0026#34;)?.toString()?.let { stylesheets += it } } fun \u0026lt;T : Parent\u0026gt; T.addWidgetStyles() = apply { object {}::class.java.getResource(\u0026#34;widgetsfx.css\u0026#34;)?.toString()?.let { stylesheets += it } } enum class TestStyle(val selector: String) { BLUE(\u0026#34;test-blue\u0026#34;), RED(\u0026#34;test-red\u0026#34;), GREEN(\u0026#34;test-green\u0026#34;) } infix fun \u0026lt;T : Node\u0026gt; T.testStyleAs(nodeStyle: TestStyle) = apply { styleClass += nodeStyle.selector } infix fun \u0026lt;T : Region\u0026gt; T.padWith(padSize: Double): T = apply { padding = Insets(padSize) } infix fun \u0026lt;T : Node\u0026gt; T.addStyle(newStyleClass: String): T = apply { styleClass += newStyleClass } fun textFieldOf(value: StringProperty) = TextField().apply { textProperty().bind(value) } infix fun TextField.bindTo(value: StringProperty) = apply { textProperty().bind(value) } fun buttonOf(text: String, handler: EventHandler\u0026lt;ActionEvent\u0026gt;) = Button(text) addAction handler operator fun Pane.plusAssign(newChild: Node) { children += newChild } infix fun HBox.alignTo(pos: Pos): HBox = apply { alignment = pos } infix fun \u0026lt;T : ButtonBase\u0026gt; T.addAction(eventHandler: EventHandler\u0026lt;ActionEvent\u0026gt;): T = apply { onAction = eventHandler } This is just a grab-bag of functions and utilities that aren\u0026rsquo;t necessarily related to each other.\nThere are decorators to add the widgetsfx.css stylesheet to either a Scene or a Parent. Since these are declared at a package level, there\u0026rsquo;s no class to provide getResource() to find the stylesheet. Instead the function declares an anonymous Object that will have the correct package which can provide the getResource() method.\nRegion.padWith() is super useful. It gets absolutely tedious writing setPadding(new Insets(10)) over and over and over. The vast majority of the time, you just want all of the sides to have the same padding so a function that just takes a single Double parameter makes a huge difference.\nand then Labels.kt\npackage ca.pragmaticcoding.widgetsfx enum class LabelStyle(val selector: String) { PROMPT(\u0026#34;label-prompt\u0026#34;), HEADING(\u0026#34;label-heading\u0026#34;) } infix fun \u0026lt;T : Labeled\u0026gt; T.styleAs(labelStyle: LabelStyle) = apply { styleClass += labelStyle.selector } infix fun \u0026lt;T : Labeled\u0026gt; T.bindTo(value: ObservableStringValue) = apply { textProperty().bind(value) } fun promptOf(value: ObservableStringValue) = Label() styleAs LabelStyle.PROMPT bindTo value fun promptOf(value: String) = Label(value) styleAs LabelStyle.PROMPT fun headingOf(value: String) = Label() styleAs LabelStyle.HEADING operator fun Labeled.plusAssign(otherProperty: StringProperty) = run { textProperty() += otherProperty } For this file, I\u0026rsquo;ve grouped together all of the functions that deal with Labels strictly as an organizational tool.\nIn both of these files, I\u0026rsquo;ve declared an Enum to abstract the CSS selector classes so that they don\u0026rsquo;t need to be known outside the utility files.\nObviously, these functions require a complementary style sheet to work with:\n.root { -theme-colour: #113969; -contrast-colour: #C6522F; -complementary1: #43AA8B; -complementary2: #B4CDED; -complementary3: #80A4ED } .test-blue { -fx-border-color: blue; -fx-background-color : lightcyan; } .test-red { -fx-border-color: firebrick; -fx-background-color : lavenderblush; } .test-green { -fx-border-color: green; -fx-background-color : derive(yellowgreen, +50%); } .label-prompt { -fx-text-fill: -theme-colour; -fx-font-weight: bold; -fx-font-size: 15px; } .label-heading { -fx-text-fill: -contrast-colour; -fx-font-weight: bold; -fx-font-size: 32px; } Using It # Good layout code should make it easy for any reader to understand at a glance how a layout works. This means keeping the configuration out of the way, as this is what causes confusion. But how you use these library functions can make a big difference to readability.\nFor instance, this is hard to read (horrible, actually):\nprivate fun createContent(): Region = BorderPane().apply { top = headingOf(\u0026#34;Test Screen\u0026#34;) center = VBox(20.0).apply { children += HBox(10.0).apply { children += Label(\u0026#34;Name\u0026#34;) styleAs LabelStyle.PROMPT children += TextField() bindTo nameProperty } padWith 10.0 alignTo Pos.CENTER_LEFT children += Button(\u0026#34;Click Me\u0026#34;) addAction { buttonAction() } } } testStyleAs TestStyle.BLUE padWith 20.0 and this is maybe a bit easier, but still too difficult:\nprivate fun createContent(): Region = (BorderPane() testStyleAs TestStyle.BLUE padWith 20.0).apply { top = headingOf(\u0026#34;Test Screen\u0026#34;) center = VBox(20.0, HBox(10.0, Label(\u0026#34;Name\u0026#34;) styleAs LabelStyle.PROMPT, TextField() bindTo nameProperty) padWith 10.0 alignTo Pos.CENTER_LEFT, Button(\u0026#34;Click Me\u0026#34;) addAction { buttonAction() }) } but specific builders work best, so this is probably clear enough:\nprivate fun createContent(): Region = BorderPane().apply { top = headingOf(\u0026#34;Test Screen\u0026#34;) center = VBox(20.0, createNameRow(), buttonOf(\u0026#34;Click Me\u0026#34;) { buttonAction() }) this testStyleAs TestStyle.BLUE padWith 20.0 } private fun createNameRow() = HBox(10.0, promptOf(\u0026#34;Name\u0026#34;), textFieldOf(nameProperty)) padWith 10.0 alignTo Pos.CENTER_LEFT But this is probably best of all:\nprivate fun createContent(): Region = BorderPane().apply { top = headingOf(\u0026#34;Test Screen\u0026#34;) center = VBox(20.0, createNameRow(), createButton()) } testStyleAs TestStyle.BLUE padWith 20.0 private fun createButton() = buttonOf(\u0026#34;Click Me\u0026#34;) { buttonAction() } private fun createNameRow() = HBox(10.0, promptOf(\u0026#34;Name\u0026#34;), textFieldOf(nameProperty)) padWith 10.0 alignTo Pos.CENTER_LEFT There\u0026rsquo;s no doubt that this last version achieves the \u0026ldquo;at a glance\u0026rdquo; objective of the layout code. You can see that the BorderPane has the top and centre occupied, and you can see that the centre has two rows in it. If you\u0026rsquo;re curious about the \u0026ldquo;Name\u0026rdquo; row, or the Button you can click through to them in the code. This is really no different than the best approach in Java, but the apply{}, the builders, and the infix functions strip out all of the extraneous boilerplate that would otherwise bloat the layout code.\nCoroutines # Coroutines are a neat feature that allow you to run multiple blocking operations on a single thread. When one task blocks, it doesn\u0026rsquo;t halt the thread, but is \u0026ldquo;parked\u0026rdquo; and the thread is freed up to run other tasks. As each task blocks, Kotlin can then \u0026ldquo;un-park\u0026rdquo; a previously parked task that has become unblocked, and run it on the thread until it either completes or becomes blocked again.\nThere is a special library (part of the Kotlin companion libraries for coroutines) that allows you to run coroutines on the FXAT. So that you can do something like this:\nprivate fun createContent(): Region = BorderPane().apply { padding = Insets(20.0) center = VBox(20.0, HBox(10.0, Label().bound(model)) .styled(\u0026#34;test-border3\u0026#34;) .padded(10.0) .aligned(Pos.CENTER), Button(\u0026#34;ClickMe\u0026#34;).apply { onAction = EventHandler { _ -\u0026gt; buttonAction() } }) } private fun buttonAction() { GlobalScope.launch(Dispatchers.JavaFx) { println(\u0026#34;Starting the function\u0026#34;) counter += 1 model.value = counter.toString() delay(10000) println(\u0026#34;Ending the function\u0026#34;) counter += 100 model.value = counter.toString() } } The Dispatchers.JavaFX tells it to launch the coroutine on the FXAT. Because of this, you can update model even though it\u0026rsquo;s bound to a screen element. The call to delay() is used to simulate a blocking operation. While the delay() is running, you can continue to use the GUI, and you can, in fact, click the Button again and launch another coroutine on the FXAT.\nAs you can see, running the code as a coroutine simply requires wrapping it in a call to GlobalScope.launch with the correct dispatcher. It\u0026rsquo;s really that trivial.\nPotentially, you could do any number of blocking operations that you\u0026rsquo;d normally use Task for using coroutines and it should work just fine. However you should keep a couple of things in mind:\nThis is only for blocking operations, like accessing an external API over a network. Running a very long process that simply takes a long time to complete is still going to hang your GUI.\nUsing this technique introduces the possibility of some of the concurrency issues that the single threaded FXAT is designed to avoid. This particular example is designed to demonstrate that each click of the button messes with the value of counter. If you had code that assumed that counter was equal to 101 after completion, it could fail.\nTo expand on the \u0026ldquo;concurrency issues\u0026rdquo; part: If you ran the same code without using coroutines, then the GUI would definitely hang for 10 seconds after you click the button. No jobs would get processed of the FXAT and no events would get handled. But you could guarantee that the state of the GUI would be exactly the same after the delay as it was before the delay(), and this includes model.value and counter. But with coroutines, when the execution continues after the delay() there\u0026rsquo;s no reason to believe that these things won\u0026rsquo;t have changed.\nCoroutines are generally considered an advanced technique in Kotlin, and are really included here for the sake of completeness. There are places where they can be of value, but you should probably continue to use Task for most situations.\n","date":"28 January 2023","externalUrl":null,"permalink":"/kotlin/2023-01-28-kotlinforjavafx/","section":"Kotlin","summary":"Introduction # I’ve been using Kotlin for a while now, and I’ve been using it to write JavaFX applications. What I’ve found is that the things that make Kotlin better than Java, make Kotlin way, way better for JavaFX.\n","title":"Kotlin For JavaFX","type":"kotlin"},{"content":" Introduction # The first time I ever looked at a Kotlin program was when one of my developers found a cool JavaFX library called \u0026ldquo;DirtyFX\u0026rdquo;. We wanted to understand it worked, but when we looked at the source code on GitHub we were stymied because it was in Kotlin.\nIt seemed impenetrable.\nYears later, I\u0026rsquo;ve gone back to Kotlin and learned it. It\u0026rsquo;s awesome. And, if you\u0026rsquo;re a competent Java programmer, you can learn enough to start using it with a 4 hour YouTube tutorial. Then it takes about a week or so to get comfortable, and somewhat longer to get proficient at it.\nThis article isn\u0026rsquo;t designed to replace a 4 or 5 hour YouTube tutorial. It\u0026rsquo;s intended to give a typical Java programmer enough information to understand a typical Kotlin program in a much shorter period of time. It\u0026rsquo;s also intended to give a typical Java programmer a feel for how Kotlin works, and a start to understanding why many Kotlin programmers think it\u0026rsquo;s the way forward in the JVM world.\nI\u0026rsquo;ve also provided links to the Kotlin official documentation as part of the text. So if you want to look into some aspect of Kotlin a little more deeply, it should be easy for you to do so. The Kotlin docs are really well written and easy to understand, too.\nOne more thing: If you study Kotlin for any length of time, you\u0026rsquo;ll come across the term \u0026ldquo;idiomatic Kotlin\u0026rdquo;. This is the idea that while Kotlin shares the JVM, just writing code the way you would in Java but in Kotlin isn\u0026rsquo;t going give you something that feels like Kotlin. There\u0026rsquo;s definitely a Kotlin approach to coding, and coding in that way gives you \u0026ldquo;Idiomatic Kotlin\u0026rdquo;. In order to write code this way, you do really have to have a solid grasp of a lot of Kotlin, but once you get used to some of the quirky things you\u0026rsquo;ll see in Kotlin code you should be able to easily read and understand it.\nWhy Kotlin # When I learned Kotlin I realized that there was lots of stuff in Java that bugged me (or should have) that Kotlin just fixes in a seamless and natural way. Null safety is a clear example of that. Sure, in Java you can use Optional, but Kotlin\u0026rsquo;s approach is nicely integrated into the language in such a way that Null safety is virtually mandatory in any code you write. From what I\u0026rsquo;ve seen, just about every Java programmer who takes the time to learn enough Kotlin to become somewhat proficient in it wishes that they could switch over to Kotlin full time.\nIn general, Kotlin code is shorter and more intuitive than the equivalent in Java, and is therefore easier to read and understand. One example you\u0026rsquo;ll see here is how Kotlin eliminates the need for writing getters and setters, while providing the same functionality and separating the public interface from the internal workings of your class. Then Kotlin goes one step further by interpreting external references to member properties as calls to the getters and setters. In the end, Kotlin allows you to write code exactly the way that you always wanted to, and does all the work, behind the scenes, of the boilerplate code you have to write in Java.\nClass Structure # Let\u0026rsquo;s start at the top, class structure. We\u0026rsquo;ll use this sample code to talk about a few topics:\nclass MyClass(val property1 : Int, private var property2 : Int = 0) : SomeInterface { var property3: SomeInterface = SomeImplementation() protected val property4 = mutableListOf(\u0026#34;abc\u0026#34;, \u0026#34;def\u0026#34;) private var property5: Int init { property5 = property1 + 25 } fun someMethod(param1 : String, param2 : Double = 3.2) : Boolean { val localVariable : Int = 44 . . . return booleanValue } } Declaring Types # The first thing that you\u0026rsquo;ll notice is that there aren\u0026rsquo;t any semi-colons at the ends of the lines. You can use them, but you don\u0026rsquo;t need to.\nKotlin basically supports all the same types as Java, although the primitive types aren\u0026rsquo;t used. So anything that would be int or Integer is Int in Kotlin.\nThe second thing that you\u0026rsquo;ll notice is that type declarations are of this structure:\nkeyword name : type This structure is the same whether you\u0026rsquo;re declaring a field, a variable, a class or a method.\nIf you are initializing a variable and it can be reasonably inferred as to what it is, then you do not need to explicitly specify the type:\nvar abc = 5 Will give you Int. However, if you want Long, then you need to specify it unless the initial value is too large for Int:\nvar abc : Long = 5 The same structure follows when you want to define a variable as an interface when the initialization calls the constructor of an implementation of that interface.\nInstance Variables # Here, we\u0026rsquo;re very specifically using the term \u0026ldquo;instance variables\u0026rdquo; because Kotlin has a different structure for static elements, and these instance variables are very different from Java fields. Kotlin calls instance variables, \u0026ldquo;Properties\u0026rdquo;.\nA Kotlin property is actually a structure that is backed by a \u0026ldquo;field\u0026rdquo;. There is a default getter and setter for the property, and should you chose to override them you can access the backing field inside your getter/setter code. For now, this is all you need to know about this.\nOne advantage to Kotlin is that it automatically invokes the getter or setter for a property when it is directly accessed via dot notation in your code. Let\u0026rsquo;s look at this snippet:\nmyClass.property3 = myClass.property4[2] This is completely proper and valid Kotlin code. It calls the setter for property3 and the getter for property4. On top of that, it calls List.get() by using [] construct. You can also do this:\nmyClass.property4 += \u0026#34;xyz\u0026#34; Here it accesses the getter for property4 and then calls List.add() via the += operator. This is called \u0026ldquo;Operator Overloading\u0026rdquo;. You can define your own overloaded operators by defining methods on a class with particular names.\nIf this alone doesn\u0026rsquo;t sell you on Kotlin, I don\u0026rsquo;t know what will. You get all of the benefits of getters and setters with none of the overhead! And the operator overrides make your code look the way you always wanted it to in Java, but couldn\u0026rsquo;t be.\nMutability # By now you should have noticed the val and var prefixes for property and variable declarations. These indicate whether or not the reference is mutable. It\u0026rsquo;s not optional in Kotlin to specify this. Functionally, val is very close to the final keyword in Java, with the exception that there\u0026rsquo;s no default to \u0026ldquo;not final\u0026rdquo;.\nThe convention is to declare everything as immutable using val unless you specifically have a need for it to be mutable. In practice, this makes a huge difference in the readability of code since you don\u0026rsquo;t need to chase through the code looking for any places that any variable might have been changed. This is also a boon when writing multi-threaded code.\nPersonally, I find that 95%+ of my variables are declared with val. I almost feel guilty when I use var.\nLists are generally considered immutable unless you declare them as mutable. Remember that declaring a List variable as mutable is not the same as a mutable List.\nConsider this:\nval list1 = listOf(\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;) var list2 = listOf(\u0026#34;d\u0026#34;, \u0026#34;e\u0026#34;) val list3 = mutableListOf(\u0026#34;x\u0026#34;, \u0026#34;y\u0026#34;, \u0026#34;z\u0026#34;) The only operations you can perform on list1 are List.get() type operations. You cannot add, remove or change any of the elements. The same restrictions apply to list2, however you can replace it with a new immutable list, meaning that:\nlist2 = listOf(\u0026#34;dog\u0026#34;, \u0026#34;cat\u0026#34;, \u0026#34;budgie\u0026#34;) would be allowed. You can add or remove elements to list3, but but you cannot instantiate a new List as list3.\nMethods # Methods in Kotlin are called Functions and declared using the fun keyword. The return type of a function is declared the same way as for a variable and the body of the method is enclosed in {} if there is more than one line. If the function body is an expression, then you can just use = and put the code. For instance:\nfun doubleIt(x: Int): Int { return x * 2 } fun double(x: Int): Int = x *2 are equivalent. In the second case, the type declaration can be left out as the compiler can infer it.\nVoid functions either return Unit or just leave out the return type altogether.\nFunctions can have any number of parameters declared and those parameters can have default values. Consider this:\nfun multiplyIt(x: Int, multiplier: Int = 2) = x * multiplier Which can be called in the following ways:\nval answer = multiplyIt(3,2) val answer = multiplyIt(x = 3, multiplier = 2) val answer = multiplyIt(multiplier = 2, x = 3) val answer = multiplyIt(3) val answer = multiplyIt(x = 3) And all of these will return the same result.\nClass Declarations # Now that we\u0026rsquo;ve covered all of that, you can understand the class declaration itself.\nLooking at the code at top of this article, you\u0026rsquo;ll notice that the class declaration looks almost like a constructor. That\u0026rsquo;s because it is, but in most case you can skip the constructor keyword for the primary constructor.\nOne thing that\u0026rsquo;s different from a Java constructor is that all of the parameters listed as val or var automatically become properties of the class. Just like with Functions, you can declare default values for those properties, if they are not specified in the constructor call.\nPrimary constructors in Kotlin do not contain any code. However, you can declare init {} blocks in your class and these will be executed, in the order that they appear, from the primary constructor. You can do just about anything that you would expect to do, including initializing the value for a val property in an init block.\nIf a class implements an Interface, or extends another class, then that can be specified using the type style declaration that was used for functions.\nStatic elements are created by including them in a \u0026ldquo;Companion Object\u0026rdquo; inside the class.\nVisibility Modifiers # While the default visibility for Java is package-protected, there is no equivalent for this in Kotlin and the default visibility is public. There is also private, which does what you\u0026rsquo;d expect, and protected which exposes the class member to any sub-classes.\nBy default, all functions in a Kotlin class are final, and must be declared with the open modifier in order to be overridden by a subclass.\nLanguage Features # At this point, you should have enough information to open up a Kotlin class file and navigate around it, understanding at least the structure of the class. Now let\u0026rsquo;s look at some of the features of the Kotlin language\u0026hellip;\nLambdas # Lambdas are much more tightly integrated into Kotlin than Java. The syntax is similar:\nval lambda = {param: Int -\u0026gt; some code here} The entire thing is enclosed in the {}. If you aren\u0026rsquo;t going to use the parameter, then you can just replace it with _.\nval lambda = {_ -\u0026gt; some code here} If the parameter can be inferred, then you can leave out the param -\u0026gt; entirely and reference it as it. We\u0026rsquo;ll come back to that shortly.\nThat\u0026rsquo;s the basic syntax. One thing that\u0026rsquo;s handy is that if the last parameter in any function declaration is a function, then you can pass it as a \u0026ldquo;trailing lambda\u0026rdquo;, and if there are no other parameters you can leave out the parentheses entirely.\nSo, you can do something like this:\nval x = doSomething(3, 4){x:Int -\u0026gt; x*2} . . . fun doSomething(a: Int, b: Int, func : (Int) -\u0026gt; Int): Int { val intermediate = (a*3) - (b *12) return func(intermediate) } You can also use method references much the same way as in Java.\nYou\u0026rsquo;ll also see the term \u0026ldquo;higher order function\u0026rdquo; in a lot of documentation. This is simply a function that takes another function as a parameter. It\u0026rsquo;s far more common in Kotlin than Java\u0026hellip;\nFunctions as Data # This is probably one of the biggest features of Kotlin that should have a impact on the \u0026ldquo;feel\u0026rdquo; of programs written in Kotlin. Kotlin makes it very easy to pass snippets of code around from place to place, very much like data. Java has headed a little bit in this way with the introduction of \u0026ldquo;Functional Interfaces\u0026rdquo; and lambdas, but Kotlin bakes the concept in from the beginning.\nFirst off, you can declare a function as a data type just by specifying its inputs and outputs, in a format that looks a little bit like a cross between a lambda and a generic type declaration. For instance:\nvar abc : (String, Int) -\u0026gt; Double Here, the variable abc is declared to be a function that takes a String and an Int as input and returns a Double. There\u0026rsquo;s no need to declare a Functional Interfaces like Function, Predicate or Consumer as you do in Java.\nYou can even declare a \u0026ldquo;Type Alias\u0026rdquo; to give a name to your function type:\ntypealias NumberFinder = (String, Int) -\u0026gt; Double var abc : NumberFinder It\u0026rsquo;s possible to get a little lost between functions declared via fun and functions instantiated as lambdas or as variables. In truth, Kotlin really does treat them very much the same. For in every instance below, doubler is the same:\nval doubler : (Int) -\u0026gt; Int = {x:Int -\u0026gt; x*2} val doubler : (Int) -\u0026gt; Int = {it *2} val doubler : (Int) -\u0026gt; Int = {doubleIt(it)} val doubler : (Int) -\u0026gt; Int = ::doubleIt val doubler = {x:Int -\u0026gt; x*2} val doubler = {x: Int -\u0026gt; doubleIt(x)} val doubler = ::doubleIt . . . fun doubleIt(x: Int) = x * 2 The last two versions really make it clear, since you don\u0026rsquo;t need to even specify the type, and doubler as just a pointer for the local function really lays it bare.\nFinally, you can treat doubler from above just like doubleIt() in your code:\nval y = doubler(abc) val j = doubleIt(abc) Both would work just fine.\nExtension Functions # It\u0026rsquo;s possible to add a function to a class without creating a subclass. This is called an \u0026ldquo;Extension Function\u0026rdquo;. For instance, you can add a function to Int to determine if it is even:\nfun Int.isEven() = {this %2 == 0} An extension function is an example of a \u0026ldquo;receiver function\u0026rdquo;. A receiver function is one that is automatically passed a parameter which is known inside the function by a standard name, usually this. In the case of a receiver called this, if it can be inferred by the compiler the this can be left out of a statement when accessing class members (very much like in Java).\nScope Functions # Scope Functions are a special class of \u0026ldquo;receiver\u0026rdquo; functions.\nThere are 5 Scope Functions, and 4 of them are Extension Functions. So we\u0026rsquo;ll look at them first.\nTransformation Functions (My Term) # These two functions take the object as a parameter and return some other value. The differ only in only how they name the received object. They are let and run.\nLet\u0026rsquo;s look at run first:\nval stringLength: Int = \u0026#34;This is a String\u0026#34;.run{this.length} That\u0026rsquo;s pretty banal, but you get the idea. The received object is referred to as this. You should also note that when it\u0026rsquo;s clear, the this can be inferred by the compiler and left out of the code\u0026hellip;\nval stringLength: Int = \u0026#34;This is a String\u0026#34;.run{length} Now, let:\nval stringLength: Int = \u0026#34;This is a String\u0026#34;.let{it.length} You can see that run and let are pretty much the same, except that the received object is referred to as it in let. When a Scope Function uses it you can use the normal lambda syntax to change it to something more meaningful if you like.\nConfiguration Functions (Also, My Term) # These two functions take the object as a parameter and return it back, allowing you to configure an item without instantiating it as a variable. They are apply and also.\nI use apply all the time with JavaFX, as lots of JavaFX Nodes require pretty standard set-up which, in Java, requires instantiating them as a variable just to do something simple:\nhBox = HBox(5, Label(\u0026#34;Hello\u0026#34;).apply{styleClass += \u0026#34;label.prompt\u0026#34;}) Once again, the this can be inferred by the compiler and left out of the code. For those not familiar with JavaFX this is the equivalent of this.getStyleClass().add(\u0026quot;label-prompt\u0026quot;), as getStyleClass() returns a MutableList.\nStill though, that can get ugly, but the same structure can be moved to a function:\nhBox = HBox(5, promptLabel(\u0026#34;Hello\u0026#34;)) . . . private fun promptLabel(text: String) = Label(text).apply{styleClass += \u0026#34;label.prompt\u0026#34;} Which is a structure I use all the time.\nThe Non-Receiver Function - with # If you\u0026rsquo;re old enough to remember the Pascal programming language, then you might be familiar with with. It\u0026rsquo;s a great way to clean up a block of code that has many references to members of a single object. Whatever parameter passed to with becomes this in the associated lambda and, of course, the this can then be omitted in references to that objects members. So you can do something like this:\nval theResult: String = with(myClass) { name = \u0026#34;Fred\u0026#34; val x = age + 20 doSomething(\u0026#34;abc\u0026#34;) } Where name, age and doSomething() are members of myClass.\nSince with is a function, it can return a value, in this case the result of MyClass.doSomething().\nThe Non-Extension Version of run # This version of run is used when a situation requires an expression, but you want to put multi-line code instead. The format is run {}. There is no receiver object in this case.\nNull Safety # Null Safety is one of the biggest features of Kotlin! There is no reason at all to ever get an NPE in Kotlin. Ever.\nAs important as it is, it had to be left until this point because you need to understand how lambdas and Scope Functions work first, which shows you how tightly integrated into the language null safety is.\nNone of the normal data types, like Int, or Double or String are allowed to have a Null value. This means that they must always be initialized when declared, and you cannot put a Null value into them.\nIf you want to entertain the idea of having a Null value, then you must declare them as \u0026ldquo;Nullable\u0026rdquo;. Nullable types are specified by putting a ? after the type name. So Int?, Double? and String? are all types that are allowed to hold Null values.\nBut note that Int? is actually a different type from Int. You cannot perform a mathematical operation on Int? directly, nor can you use an Int? in an operation to assign to Int. One more time, Int? is not Int.\nIn fact, Int? is closer to the Java Optional\u0026lt;Integer\u0026gt; than anything else.\nIn order to use the value in a Nullable type, you need to (effectively) extract it into its non-nullable type. This is generally done via the ?. operator, and the ?: (called the \u0026ldquo;Elvis operator\u0026rdquo;). The Elvis operator is a little bit like the ternary operator in Java and, in Java would work like this:\nint x = (nullableInt.isNotNull()) ? nullableInt.getIntValue() : y; and looks like this in Kotlin:\nval x : Int = nullableInt ?: y In both cases, x would be whatever integer value was in nullableInt if it was non-null, and y if it was null.\nJust as Java\u0026rsquo;s Optional has map(), Kotlin has ?.{}. The code in the {} will be executed with the value passed as a parameter if it\u0026rsquo;s non-null, otherwise nothing happens and the value remains Null.\nval x : String? = getSomething() val y : Double? = x?.length()?.run{ val abc = this * 3 return abc / 7.0 } . . . fun getSomething(): String? {} In the expressions for y the code after ?. is only executed if the value is non-null.\nLanguage Structure # This is a quick survey of some of the common language elements that are used in Kotlin that are different from Java.\nString Templates # You can use templates to create strings:\nval x: Int = 123 val string: String = \u0026#34;There are $x carrots\u0026#34; The value of string will be \u0026ldquo;There are 123 carrots\u0026rdquo;. For more complex expressions, use {}. Also, println is part of the standard library, so there\u0026rsquo;s no need for System.out.println()\u0026hellip;\nprintln(\u0026#34;There are ${fridge.carrots + table.carrots} carrots\u0026#34;) Equals # No surprise here, in Kotlin \u0026ldquo;==\u0026rdquo; does what you\u0026rsquo;ve always wanted it to do in Java! \u0026ldquo;==\u0026rdquo; invokes the equals() method for any non primitive class. If you want referential equality, use \u0026ldquo;===\u0026rdquo;.\nIf Statements # For the most part, if works exactly as in Java. However, if can also be used as an expression that returns a value.\nval x = if (y \u0026gt; 30) 23 else 100 is valid and will assign 23 to x if y is greater than 30, otherwise x will be assigned 100.\nFor this reason there is no Ternary operator in Kotlin. This is just about the only case where Kotlin is a little bit more verbose than Java. However, if as an expression is used frequently when the branches are blocks of code.\nWhen Expressions # The when expression is very similar to the new form of the Java switch statement. Like if, when can be used as either a statement or an expression so it can return a value. When the subject of a when statement is an Enum or a sealed class, then the branches of the when must be exhaustive, or include an else branch.\nFor Loops and Range Expressions # In Kotlin for loops always work across a collection. To increment a value over a range, Kotlin has a type of Collection called a Range. The Java code:\nfor(int x = 0; x \u0026lt; 6; x++) {} would become:\nfor(x in 0..5) {} If you want to iterate over a Collection but also have an index, there\u0026rsquo;s a way to do that, too:\nfor((index, item) in collection.withIndex()){ println(\u0026#34;Item # $index is ${item.description}\u0026#34;) } Collection Operations and Sequences # In Kotlin, you can do almost all of the operations you would do with Streams directly on any Collection. This means that there\u0026rsquo;s no need to perform .stream() on a List or any other Collection most of the time. Operations performed on a Collection create a new Collection with the elements transformed in some way.\nWhen you do want to process in a manner similar to Java Streams, Kotlin has Sequences. You can use the Collection.asSequence() function to do this. There are some cases where the performance of a Sequence might be better than a series of Collection operations. There are also some function that are only available as Sequence operations.\nYou can also create an infinite Sequence with the generateSequence() function that specifies a starting value and a function to create the next value. You can then treat it like any other Sequence. Sequence has a function called take(x) that allows you to take the first x values of a Sequence.\nMaps and Pairs # Maps in Kotlin are made up of tuples, just as in Java, but they are thought more of as a collection of tuples than key/value containers. Tuples are constructed and then added to the Map as in the following code:\nval testMap = mutableMapOf(\u0026#34;x\u0026#34; to 27, \u0026#34;abc\u0026#34; to 55, \u0026#34;hello\u0026#34; to 74) testMap.put(Pair(\u0026#34;fred\u0026#34;, 18)) Additionally, you can use the [] shorthand operator instead of put() and get() on a Map\ntestMap[\u0026#34;george\u0026#34;] = 100 println(testmap[\u0026#34;fred\u0026#34;]) ","date":"19 December 2022","externalUrl":null,"permalink":"/kotlin/2022-12-19-kotlinforjava/","section":"Kotlin","summary":"Introduction # The first time I ever looked at a Kotlin program was when one of my developers found a cool JavaFX library called “DirtyFX”. We wanted to understand it worked, but when we looked at the source code on GitHub we were stymied because it was in Kotlin.\n","title":"Kotlin For Java Programmers","type":"kotlin"},{"content":" Introduction # This article is an introduction to and a \u0026ldquo;how to\u0026rdquo; for the framework that I\u0026rsquo;ve developed for building Reactive JavaFX applications. It\u0026rsquo;s called \u0026ldquo;Model-View-Controller-Interactor\u0026rdquo;, or just, \u0026ldquo;MVCI\u0026rdquo;.\nMVCI was created as a practical solution to the challenges that are posed by JavaFX, specifically when it\u0026rsquo;s used as a Reactive programming tool. It\u0026rsquo;s easier to understand than the other frameworks while not making any compromises on functionality or technical correctness.\nMotivation and Development # My introduction to JavaFX was building GUI applications to augment and extend a complicated legacy enterprise application. My team had some experience with Swing, but decided to move over to JavaFX when version 2 came out in 2014. We felt that Swing screens looked dated tired, and JavaFX had a much more modern look and feel - as well as a more modern programming approach - than Swing did. The dated look and feel of Swing might have had much to do with our lack of skill to create anything better, but the JavaFX screens looked better right out of the box.\nRight or wrong, that was the direction I chose and we began a multi-year journey to learn how to deal with JavaFX while under pressure to deliver new functionality to the organization at an acceptable pace.\nWhat we didn\u0026rsquo;t realize was just how difficult JavaFX is to learn by yourself and, since it was brand new, there was nobody out there to teach it, or write about it with real knowledge and experience. Inevitably our process was full of mistakes, horrible code and lots of places where we had to re-think our approach to various challenges.\nOver the course of this I learned not to bother with FXML since it just complicated our code. I tried to implement MVC, as best we understood it, and this added a lot of structure to our code and made things cleaner, but still left a lot of things unsolved.\nI learned that scraping data out of the screen Nodes and loading them back into the Model every time the \u0026ldquo;Save\u0026rdquo; Button was pressed was a losing strategy. Binding the value properties of the Nodes to the Model was the way to go! This was a game-changer, as it totally changed our approach to screen building. I had started down the path to Reactive design without even knowing it!\nI learned to bind the other properties of the Nodes to the Model, and I learned to bind, whenever possible, to the Model and not between the screen Nodes. This made our layout components independent of each other.\nI also learned to put ALL of the business logic in one place. Don\u0026rsquo;t put logic to disable a Button in the layout code, but put it somewhere else and have it reflected in the Model. Then bind the Button.disableProperty() to that Model element.\nAs this went on, we had a set of guidelines and general principles to follow when building screens and functions but I still saw it as trying to fit into one of the accepted frameworks, usually Model-View-Controller. Some of the things we did seemed to break the designs of these frameworks, but they did result in better applications.\nFinally, in the past couple of years, I\u0026rsquo;ve come to realize that what I had developed was something unique, and not an adaptation of any of the existing patterns. I think it\u0026rsquo;s worth sharing with other programmers that want to build better JavaFX applications.\nReactive Programming # Let\u0026rsquo;s talk a little bit about \u0026ldquo;Reactive\u0026rdquo; programming. What is it?\nThere\u0026rsquo;s a few ways to look at it:\nStatic Layouts that Behave Dynamically This is a really important concept. Reactive systems allow you to create a layout as static elements, meaning that you don\u0026rsquo;t actually change the Nodes that are on the screen, add new Nodes or take any away. Instead the screen Nodes are connected to data elements that control how they work. Changes in those data elements result in dynamic changes to the screen that the user sees. At the back-end of the system, there are business rules that update those data elements as nothing more than data, without any knowledge of how those data elements will impact the screen. State Data is a Pipeline Between the Business Logic and the View This is another way to look at it. The \u0026ldquo;State\u0026rdquo; of the GUI is represented by some collection of data which is connected to the various properties of the elements of the View. Either end can potentially change some of that data, at which point it is available to the other end instantly. Both ends of the pipeline are free to interpret that data in a context which has meaning to it, without understanding what it means to the other end. So a layout may use a Boolean State element to control whether a Button is enabled, but the business logic end may interpret that same Boolean State element to mean that a date value somewhere is invalid. In JavaFX this means that you have an Object composed of Observables of some sort. These can be Properties, Bindings, ObservableLists or any their relatives. These Observables are bound to various properties of the Node elements that make up the layout.\nGeneric Reactive programming descriptions talk about \u0026ldquo;streams\u0026rdquo;, which is essentially the function provided by the Observables and the Binding classes.\nThe simple truth is that JavaFX provides all of the tools to build Reactive applications and, in fact, it works best when you use it that way.\nWhy Use a Framework? # Frameworks are designed to limit coupling between the GUI, the business logic and the control logic. Excessive coupling is by far the biggest issue with application design, and it makes it very difficult to understand how an application works, to make changes, find and fix bugs, and to extend the application.\nOne of the ways to evaluate coupling is to look at how much of its functionality a component exposes to the rest of the application, and how much it \u0026ldquo;knows\u0026rdquo; about the functionality of other components of the application. The more of this that you have, the more coupling that you have.\nWhen a component exposes some of its functionality to the rest of the application and that functionality is changed, then you have to look at all of the places that knowledge of that functionality is used, and evaluate how it needs to be changed as well. Often, you\u0026rsquo;ll find that some of those places are in functionality of other components that are also exposed to the rest of the application, so you need to track down where that knowledge is used and change them too. And so on, and so on\u0026hellip;\nThese couplings are also called \u0026ldquo;dependencies\u0026rdquo;. Dependencies also have direction and we can say that, \u0026ldquo;This component is dependent on this aspect of this other component\u0026rdquo;. Coupling becomes even worse when you have multiple dependencies between two components that go in either direction. A good framework should also try to manage the direction of the dependencies, in order to limit complexity of the coupling.\nAt the end of the day, a framework is just a \u0026ldquo;Design Pattern\u0026rdquo;. That is to say, an accepted way of coding something that has already been thought out so that you don\u0026rsquo;t have to \u0026ldquo;reinvent the wheel\u0026rdquo; in your application code. Other programmers can look at your code and say, \u0026ldquo;I recognize this\u0026rdquo;, even if they\u0026rsquo;ve never seen your code before.\nWhy Use MVCI? # Because it works, it dovetails nicely with Reactive JavaFX, and it\u0026rsquo;s easy to understand.\nWe\u0026rsquo;re not going to talk a great deal about the other frameworks here, but there are two questions that need to be addressed:\nWhy not use Model-View-Controller? For one simple reason: MVC does not allow for Reactive programming. You can bind View elements to the Model, but exclusively in a Read-Only mode. Any changes to the Model from the View have to be transmitted through the Controller. It\u0026rsquo;s a basic element of this framework. You can ignore this, but then you\u0026rsquo;re not using MVC any more. Why not use Model-View-ViewModel? MVVM does provide for binding between the ViewModel and the View elements, so that\u0026rsquo;s a step in the right direction. However, there\u0026rsquo;s no Reactive connection allowed for between the Model and the ViewModel. You end up with a lot of methods to handle data transfer between the ViewModel and the Model which creates an enormous amount of coupling. This gets very confusing very quickly. Both of these answers sound a bit like technical nit-picking that you could probably just ignore. In practice though, these represent issues that you end up having to work around - even if you don\u0026rsquo;t realize it. It gets messy very quickly.\nIf you use MVCI, you really don\u0026rsquo;t have to worry about these things. They\u0026rsquo;re just not a factor. MVCI deals with it for you and you\u0026rsquo;re not going to get tangled up in unexpected consequences of your design decisions.\nWhat Does MVCI Look Like? # MVCI has four components: a Model, a Controller, a View and an Interactor. Let\u0026rsquo;s look at what each of these do:\nThe Model The Model is the data representation of the \u0026ldquo;State\u0026rdquo; of the GUI. It\u0026rsquo;s just a POJO with the fields composed of JavaFX Observable types. There\u0026rsquo;s no logic, or any other code that\u0026rsquo;s not directly related to sharing the data fields. The View The View is not just a passive layout, but a complete user interface for the framework. This means that it has all of the logic to handle user clicks and to capture and handle any GUI events. The View is passed a reference to the Model, and it binds the properties of the various Nodes contained in the layout to the properties contained in the Model. The Controller The Controller is responsible for \u0026ldquo;how\u0026rdquo; things happen in the framework. It instantiates all of the other components, provides for integration with other parts of the application, defines \u0026ldquo;actions\u0026rdquo; for GUI events, and handles all of the threading. The Interactor The Interactor is the \u0026ldquo;business logic\u0026rdquo; component of MVCI. This is the place where Domain Objects can exist, and where calls to external API\u0026rsquo;s, persistence layers and other services are made. It\u0026rsquo;s able to see the Model, so it also contains the logic which updates the Model from data retrieved from services via Domain Objects. Here\u0026rsquo;s a diagram of how it all goes together:\nDependencies in MVCI # Since managing coupling is the whole point of a framework, let\u0026rsquo;s look at how the dependencies work in MVCI.\nDependencies almost always manifest themselves in non-private methods, including the constructors. Every time you see a non-private method, you\u0026rsquo;re looking at a potential dependency. It also tells you the direction of the dependency because the other components become dependent on that method. At the same time, the parameters required by a non-private method represent dependencies in the other direction, as these are things that the calling objects need to provide.\nFrom this perspective, you can consider constructors with parameters to be dependencies from the object back to the object that constructs it. Because every class needs a constructor, having one doesn\u0026rsquo;t increase the coupling in that direction at all, but adding parameters creates dependencies back to the constructing class.\nLet\u0026rsquo;s look at where the non-private methods are found in MVCI:\nThe Model You can see from the diagram above that all of the other three components have access to the Model. In fact, this is the main dependency in MVCI as the View, the Controller and the Interactor all have it as a dependency. As a POJO, it\u0026rsquo;s going to consist entirely of a bunch of field declarations, plus all of the accompanying getters and setters to allow other objects to access those fields. I\u0026rsquo;ve never seen a reason to have any constructor parameters in a Model. The Controller We\u0026rsquo;ll look at this a bit later, but the only non-private method in the Controller is something like a getView() method that returns a reference to the View as a Node or a Region (usually Region). In a complex application with multiple MVCI frameworks that need to share data or functionality, it is possible to have constructor parameters in Controllers, essentially creating dependencies on those external frameworks. The Interactor The Interactor gets a reference to the Model passed to it from the Controller via a parameter in its constructor. This is generally the only parameter in the Interactor\u0026rsquo;s constructor. In order to do work, the Interactor needs to have a number of non-private methods that Controller can call. These all create dependencies on the Interactor in the Controller. The View From the perspective of non-private methods, the View as an instance of Region has no dependencies at all (other than those of Region). However, it\u0026rsquo;s usually implemented via a Builder, and that has at least one constructor parameter - the Model. Additionally, the ViewBuilder can have constructor parameters to provide handlers for actions. These put dependencies from the ViewBuilder to the Controller. In order to create the View, the Controller must pass a reference to the Model plus any action handlers required. What Goes Where in MVCI? # Now let\u0026rsquo;s look at this from the other direction. How do you know where to put various pieces of functionality? MVCI is designed to make this extremely easy, so let\u0026rsquo;s look at the main items:\nLayout Layout goes in the View. This includes creating binding between the Model and the properties of the Nodes in the layout. Event Handlers Events are GUI elements, and their handlers go in the View. When EventHandlers need to perform actions that involve something other than the layout, they invoke \u0026ldquo;Action Performers\u0026rdquo; provided by the Controller. Action Performers Action Performers are functional elements that perform some kind of action. These are defined in the Controller. If an Action Performer is needed by the View so that it can invoke it from an EventHandler, then it will be passed to the View via a constructor parameter. Threading In JavaFX, threading is generally implemented using something like Task. Creation and configuration of Task objects and running them in background Threads is handled in the Controller. Business/Application Logic Anything resembling business logic goes into the Interactor. This includes the initialization of Bindings in the Model and linking various Model elements to each other (unless such connections are so rudimentary that they are clearly not business logic). Domain Stuff This goes somewhere else. Create a \u0026ldquo;Service\u0026rdquo;, \u0026ldquo;Broker\u0026rdquo; or \u0026ldquo;DAO\u0026rdquo; to handle it and call it from your Interactor. ChangeListeners These can go in one of two places. If the actions performed by the ChangeListener are entirely related to the View, then put it there. Otherwise it goes in the Controller, which will probably call a method in the Interactor to do the work. The ViewBuilder # The MVCI framework doesn\u0026rsquo;t specifically call for a ViewBuilder, but I\u0026rsquo;d call this a \u0026ldquo;best practice\u0026rdquo;.\nThere\u0026rsquo;s a general rule in JavaFX:\nExtend a class to add new functionality, use a Builder when all you are doing is configuring an existing Node subclass.\nWhat does \u0026ldquo;add new functionality\u0026rdquo; mean? In this case it really boils down to adding new non-private methods. In practice, you\u0026rsquo;re usually not going to add functionality, you\u0026rsquo;re just going to configure a Node or create a layout by adding configured Nodes via getChildren(). So builders are usually the way to go.\nA \u0026ldquo;Builder\u0026rdquo; can be any method that returns a Node subclass. JavaFX provides a handy interface called Builder which just defines a single method, build(). It\u0026rsquo;s generic, so you specify the type for the returned Node subclass.\nIn MVCI we\u0026rsquo;re going to create a Region subtype (like Pane, StackPane, VBox or BorderPane) that we\u0026rsquo;re going to return as an instance of Region. So in the Controller, we\u0026rsquo;ll instantiate a ViewBuilder that implements Builder\u0026lt;Region\u0026gt; and pass any dependencies, like the Model to it in its constructor. Then we\u0026rsquo;ll call ViewBuilder.build() to get the View.\nWhat I usually do is set the ViewBuilder as a field in the Controller and instantiate it in the Controller\u0026rsquo;s constructor. Then I create a delegate method in the Controller like this:\npublic Region getView() { return viewBuilder.build(); } If you\u0026rsquo;re paying even the tiniest bit of attention, you\u0026rsquo;ll realize that this means that there\u0026rsquo;s no reference to the View itself that\u0026rsquo;s maintained inside of the MVCI framework! So there\u0026rsquo;s no formal dependencies to or from the View anywhere inside the framework. Of course, the Bindings used in the layout to create the View create coupling between the View and the Model, but this can be completely managed by ignoring them in the View, and concentrating on the ViewBuilder and its dependencies.\nThe other important thing to note is that the Builder allows us to return a Region instead of whatever actual class was used to create the layout. Region only exposes a small number of methods that are useful for controlling its presentation in another layout. Things like Region.setMaxWidht(), or Region.setPadding(). You can get rid of those by returning Node instead.\nThis is important because it turns your View into a \u0026ldquo;black box\u0026rdquo; screen component that you can use anywhere you can use any other Node or Region. And you can do this without worrying about about what\u0026rsquo;s going on inside it, or any of the other parts of the framework, because they don\u0026rsquo;t matter from this perspective.\nAn Example # Let\u0026rsquo;s look at how you\u0026rsquo;d actually code this up. This is just a simple example with enough data in the Model and enough features to give an idea of how it all goes together and works.\nThe Model # We\u0026rsquo;ll look at the Model first, since it\u0026rsquo;s the simplest class:\npublic class Model { private final StringProperty property1 = new SimpleStringProperty(\u0026#34;\u0026#34;); private final StringProperty property2 = new SimpleStringProperty(\u0026#34;\u0026#34;); private final BooleanProperty property3 = new SimpleBooleanProperty(false); public String getProperty1() { return property1.get(); } public StringProperty property1Property() { return property1; } public void setProperty1(String property1) { this.property1.set(property1); } public String getProperty2() { return property2.get(); } public StringProperty property2Property() { return property2; } public void setProperty2(String property2) { this.property2.set(property2); } public void bindProperty3(BooleanBinding binding) { property3.bind(binding); } public ObservableBooleanValue property3Property() { return property3; } public String getProperty3() { return property3.get(); } } This is basically the JavaFX version of a \u0026ldquo;Bean\u0026rdquo;. Each field is final and private, and there are delegate methods for getting and setting the values. Finally, there are getters for references to the properties themselves. Note that there is no logic or any relationships between the fields established in the Model - it\u0026rsquo;s just a wrapper for Observable data.\nThe field property3 is a bit different. Since this is intended to be a read-only value based upon one or more of the other fields, it does not have delegate setter for the value. The getter for the property returns a type of ObservableBooleanValue which means that it\u0026rsquo;s read only, so no program can attempt to set its value via set(). There\u0026rsquo;s a method to allow the Binding to be set on the property. We\u0026rsquo;ll see how this works in the Interactor.\nThe Controller # public class Controller { private final Model model; private final Interactor interactor; private final ViewBuilder viewBuilder; public Controller() { model = new Model(); interactor = new Interactor(model); viewBuilder = new ViewBuilder(model, this::saveData); setProperty1Listener(); } private void saveData(Runnable postActionGuiCleanup) { Task\u0026lt;Void\u0026gt; saveTask = new Task\u0026lt;\u0026gt;() { @Override protected Void call() { interactor.saveData(); return null; } }; saveTask.setOnSucceeded(evt -\u0026gt; { interactor.updateModelAfterSave(); postActionGuiCleanup.run(); }); Thread saveThread = new Thread(saveTask); saveThread.start(); } private void setProperty1Listener() { model.property1Property().addListener(ob -\u0026gt; interactor.updateChangeCount()); } public Region getView() { return viewBuilder.build(); } } The constructor for the Controller is the bootstrap for the framework. It instantiates the Model first, then passes it to the constructors of the Interactor and the ViewBuilder. The Controller doesn\u0026rsquo;t actually do anything itself, but it does control how things are done. The saveData() method handles the background threading for the \u0026ldquo;Save\u0026rdquo; action, and calls Interactor methods to do the various stages of the work.\nThe method getView() is just a delegate to the build() method of the ViewBuilder. It\u0026rsquo;ll create a new view every time you call it, which shouldn\u0026rsquo;t really cause any problems as far as the framework is concerned. You could convert this to a \u0026ldquo;lazy load\u0026rdquo; model if you wanted to limit it to a single instance of the View.\nFinally, just to show how it would work, there\u0026rsquo;s an InvalidationListener installed on Model.property1. You can see how it just calls a method in the Interactor to do the actual work.\nThe ViewBuilder # public class ViewBuilder implements Builder\u0026lt;Region\u0026gt; { private final Model model; private final Consumer\u0026lt;Runnable\u0026gt; actionHandler; public ViewBuilder(Model model, Consumer\u0026lt;Runnable\u0026gt; actionHandler) { this.model = model; this.actionHandler = actionHandler; } @Override public Region build() { BorderPane results = new BorderPane(); results.setCenter(createMainBox()); results.setBottom(createButton()); results.setMinWidth(300); results.setMinHeight(200); return results; } private Node createMainBox() { VBox results = new VBox(10, new HBox(6, new Label(\u0026#34;Value 1:\u0026#34;), createBoundTextField(model.property1Property())), new HBox(6, new Label(\u0026#34;Value 2:\u0026#34;), createBoundTextField(model.property2Property())) ); results.setPadding(new Insets(20)); return results; } private Node createBoundTextField(StringProperty boundProperty) { TextField results = new TextField(); results.textProperty().bindBidirectional(boundProperty); return results; } private Node createButton() { Button button = new Button(\u0026#34;Save\u0026#34;); BooleanProperty saveRunning = new SimpleBooleanProperty(false); button.disableProperty().bind(Bindings.createBooleanBinding(() -\u0026gt; (!model.property3Property().get() || saveRunning.get()), model.property3Property(), saveRunning)); button.setOnAction(evt -\u0026gt; { saveRunning.set(true); actionHandler.accept(() -\u0026gt; saveRunning.set(false)); }); HBox results = new HBox(button); results.setAlignment(Pos.CENTER_RIGHT); return results; } } We\u0026rsquo;re not going to look at this too closely, because this article isn\u0026rsquo;t about creating layouts. The result is a BorderPane with a couple of Labels and TextFields in the centre, and a Button at the bottom. The two TextFields have their text properties bound to the two StringProperties in the Model, and the Disable property of the Button is bound to the BooleanProperty in the Model.\nThe OnAction EventHandler on the Button might need some explanation. One of the big problems with Buttons is that people can double click them - or at least click them while the action is still running. Unless you\u0026rsquo;re OK with that, you need to disable a Button as soon as it\u0026rsquo;s clicked, and then enable it when the action is completed.\nSince we\u0026rsquo;re going to have some binding logic that disables the Button if Model.property1 is empty, we can\u0026rsquo;t directly disable the Button when it\u0026rsquo;s clicked. So we introduce a BooleanProperty that indicates that the action is running, and then we Bind the Disable property of the Button to a combination of the two properties. Then we control that property that indicates the action is running to ensure that the Button stays disabled.\nIf you go back to the Controller, you can see how the Runnable that sets that BooleanProperty back to false is invoked when the Task has completed.\nThe Interactor # public class Interactor { private final Model model; private int changeCount = 0; private DomainObject domainObject; private Service service = new Service(); public Interactor(Model model) { this.model = model; createModelBindings(); } private void createModelBindings() { model.bindProperty3(Bindings.createBooleanBinding(() -\u0026gt; !model.getProperty1().isEmpty(), model.property1Property())); } public void updateModelAfterSave() { model.setProperty1(\u0026#34;\u0026#34;); model.setProperty2(domainObject.getSomeValue()); changeCount = 0; } public void saveData() { domainObject = service.saveDataSomewhere(model.getProperty1() + \u0026#34; --\u0026gt; \u0026#34; + changeCount); } public void updateChangeCount() { changeCount++; } } First, take a look at the constructor, and you can see how the Interactor contains the business logic to Bind the value in Model.property1 to the value in Model.property3. This idea, that the save action shouldn\u0026rsquo;t be allowed if the value in Model.property1 is empty, is business logic - so it belongs here, in the Interactor. No way should this Binding be defined in the View.\nThere\u0026rsquo;s also a method, updateChangeCount(), that supports the InvalidationListener in the Controller.\nFinally, we have the two methods that handle the save. The first, saveData() is the code that runs on the background thread. It can read data from the Model, but it cannot update it (that has to happen on the FXAT), while it can freely update other data stored as fields in the Interactor. The other method, updateModelAfterSave() is intended to run on the FXAT, it can freely read and write data in the Model, as well as all of the other fields in the Interactor.\nNote that both of these methods are intended to run specifically on either a background thread or the FXAT, but they don\u0026rsquo;t have any logic (or knowledge at all, really) about the threads contained within them.\nDomain Stuff # Just so that you don\u0026rsquo;t have to use your imagination to see how the Interactor interacts with services and domain objects, we\u0026rsquo;ve got some of that stuff too.\npublic class Service { public DomainObject saveDataSomewhere(String string) { try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } return new DomainObject(string + \u0026#34; - Saved\u0026#34;); } } The service is very simple, it only has one method, saveDataSomewhere(), and it just waits for 3 seconds and returns an instance of the DomainObject.\npublic class DomainObject { private final String someValue; public DomainObject(String someValue) { this.someValue = someValue; } public String getSomeValue() { return someValue; } } The DomainObject is just a POJO to hold a single data value.\nUsing the Framework # So, how do you get all this stuff on the screen?\nYou need to get the JavaFX engine up and running, and that means using the Application class, and using Application.start() to configure your Stage and Scene:\npublic class MvciApplication extends Application { @Override public void start(Stage stage) throws Exception { stage.setScene(new Scene(new Controller().getView())); stage.show(); } } This is pretty simple, instantiate the Controller and then call getView() to get the View. Put the View in a Scene which is, in turn, put into the Stage. Show the Stage. Voila!\nWhat it Looks Like # At the beginning:\nWhile it\u0026rsquo;s running:\nWhen it\u0026rsquo;s done:\nUsing MVCI with FXML # If, for some reason, you feel compelled to use FXML then you can still use this framework. The trick is to see the FXML file, the FXML Controller and the FXMLLoader together as the View component.\nThe hardest part is that you can\u0026rsquo;t have dependency injection via the constructor with the FXML Controller, since it\u0026rsquo;s instantiated via the FXMLLoader. So what you\u0026rsquo;ll need to do is run the FXMLLoader, pull the FXML Controller out of it and then cast it to whatever class you\u0026rsquo;ve actually made it. Then provide some setter methods to allow you to pass a reference to the Model and any of the action performers that your Controller defines. In the setter for the Model, you\u0026rsquo;ll need to include code to perform all of the bindings to the screen Node properties.\nIf you have little enough experience with FXML that the paragraph above sounds like mumbo-jumbo to you, then I\u0026rsquo;d suggest that you just put aside FXML and code your screens without it.\nOther than that, you can put all of that FXMLLoader logic into Controller.getView(). Not a stitch of code in any other place in the framework needs to be changed to use FXML.\nConclusion # If you\u0026rsquo;re going to use JavaFX as a Reactive platform, which you should, then Model-View-Controller-Interactor is the way to go. It\u0026rsquo;s easy to understand and yet deals with all of the issues that you\u0026rsquo;re likely to encounter in a logic and straight-forward fashion.\nPersonally, I find it so easy to implement MVCI that even when I\u0026rsquo;m writing the simplest of example code I immediately just create the 4 classes that you need and go from there. I don\u0026rsquo;t feel any temptation to skip those classes and chuck everything into one place - there\u0026rsquo;s literally no advantage to that.\n","date":"7 November 2022","externalUrl":null,"permalink":"/javafx/techniques/mvci/2022-11-06-mvciintro/","section":"JavaFX","summary":"Introduction # This article is an introduction to and a “how to” for the framework that I’ve developed for building Reactive JavaFX applications. It’s called “Model-View-Controller-Interactor”, or just, “MVCI”.\n","title":"An Introduction to Model-View-Controller-Interactor","type":"javafx"},{"content":" Projects With Multiple Screens and Functions # One of the questions that seems to come up fairly often is about how to create a project that is composed of a number of different screens and functions.\nHow do you connect them together? How do they share data?\nThis seems at first glance to be daunting. Large applications, by definition, tend to be complicated with lots of shared functionality and cross-talk between components. In practice, it can be straight-forward.\nThis is a fairly long article, but mostly because we\u0026rsquo;re going to look at variety of different situations, and a couple of different approaches to some of them. We\u0026rsquo;re going to concentrate here on Model-View-Controller-Interactor, because its structure fits naturally with JavaFX and makes it easy to visualize how the pieces go together. But there\u0026rsquo;s no reason why the ideas here cannot be applied successfully to MVC and MVVM as well.\nSample Project # All of the situations and approaches described in the article are available in a sample project on GitHub. Not all of the code for any of the scenarios is reproduced in this article, so if you want to get a real feel for how the ideas are implemented, you should download the project and look at the code. Each scenario is organized into its own package, so it should be easy to find what you\u0026rsquo;re looking for.\nThe application is built like a typical demo, with a set of a selector buttons for each scenario. It looks like this:\nGo to the Project on GitHub{: .btn .btn\u0026ndash;info}\n\u0026ldquo;Soup to Nuts\u0026rdquo; # The key is to think of each function as a complete \u0026ldquo;mini-application\u0026rdquo; of its own, with its own MVCI structure. So you\u0026rsquo;ll have a structure that looks like this for each function:\nNow, the only question is, \u0026ldquo;How do you link all of these into a single application?\u0026rdquo; The answer is to connect them via the Controllers.\nIt\u0026rsquo;s really no different from how you would compose a simple, single-screen, application where you have an Application class implementation that looks like this:\npublic class SimpleApplication extends Application { @Override public void start(Stage stage) { stage.setScene(new Scene(new SimpleController().getView())); stage.show(); } } The recurring idea is that you instantiate the Controller somewhere, then you call its getView() method to get a JavaFX Region or Node that you can put into another layout or Scene in your application. This technique can be used as part of a Menu or as an action on a Button, or even just a sub-screen in some other layout.\nA good way to think of this idea is that each of these MVCI constructs is a \u0026ldquo;soup to nuts\u0026rdquo; mini-application that presents itself as a custom Node. Once you\u0026rsquo;ve followed this structure, adding on of these functions to a screen is really no different from putting a TableView or a TextField (or any other Node, really) into your layouts.\nAnother important concept is that you\u0026rsquo;ll generally find the \u0026ldquo;shared\u0026rdquo; functionality of your application living in the \u0026ldquo;Domain\u0026rdquo; section. This is the place where you find services, API calls, brokers and business objects that are used throughout the application. You might have multiple screens and functions that require your application to interact with, say, a customer database, and the actual code that does connects to that database is going to be located in the \u0026ldquo;Domain\u0026rdquo; area. This code is generally things that you\u0026rsquo;ll call \u0026ldquo;brokers\u0026rdquo; or \u0026ldquo;services\u0026rdquo; and they accept and deliver \u0026ldquo;business objects\u0026rdquo;, or \u0026ldquo;domain objects\u0026rdquo; How you invoke those services, and how you use the results from them are going to be contained inside the Interactors. The important thing is to remember that the Interactors are where the \u0026ldquo;business logic\u0026rdquo; for specific functions that use those services are located.\nThe Sample Project # In order to show the different ways that you can integrate MVCI frameworks together I\u0026rsquo;ve created a sample application that has a BorderPane main screen with a set of Buttons that will control the content of the Centre region. Each possible content is composed of it\u0026rsquo;s own MVCI construct that can, in turn, contain another MVCI construct.\nTo make a point here, I\u0026rsquo;ve basically taken the entire content of my SWFX project and dropped it into this sample project. this project uses the awesome swapi.dev web API to lookup Star Wars character names. SWAPI is really useful for this kind of demonstration because it\u0026rsquo;s a real website with nifty content and it doesn\u0026rsquo;t require API keys. Also the SWFX project had the API calls split out from the Interactor so that it behaves as a service inside the application, meaning that the Interactor code really just contains logic related to the screens.\nIn the original SWFX project some of the code was in Kotlin, so I\u0026rsquo;ve converted the rest of it into Kotlin. I think this helps to make the point that the encapsulated content is really autonomous. It\u0026rsquo;s even in a different language! Kotlin might be new to you, but I\u0026rsquo;ll review the code a bit to explain anything that might not be clear.\nDifferent Scenarios # In almost any application that you\u0026rsquo;ll build, you\u0026rsquo;re likely to have a \u0026ldquo;Main\u0026rdquo; MVCI construct that runs the show. That means, of course, that it will have its own View, and that where you\u0026rsquo;re going to insert the View of any subsequent MVCI constructs that you create - unless you\u0026rsquo;re going to put them into their own Scenes (more about this later).\nFor the sake of clarity, we\u0026rsquo;ll refer to the outer MVCI - probably the \u0026ldquo;Main\u0026rdquo; MVCI - as the \u0026ldquo;encapsulating\u0026rdquo; framework, and the sub-functions that are invoked from it the \u0026ldquo;encapsulated\u0026rdquo; functions.\nWhile the basic ideas remain the same for just about any circumstance you might come up with to use this idea, there are some variants that you\u0026rsquo;ll need to think about\u0026hellip;\nFully Autonomous Screens # The first situation that we\u0026rsquo;ll look at is the fully autonomous screen element. As mentioned earlier, we\u0026rsquo;ve basically got the entire content of the SWFX project dropped it into this sample project. Some of it was in Kotlin, so I\u0026rsquo;ve converted the rest of it into Kotlin. From this, you should be able to see how this screen comprises autonomous functionality, and it\u0026rsquo;s just inserted into the larger application via its Controller, which is the only component that\u0026rsquo;s accessed from the encapsulating framework.\nYou can see how similar this looks to the Application example. SwController is the Controller for an autonomous MVCI framework from the SWFX project. It\u0026rsquo;s this class that instantiated to create the MVCI framework, and then the View is grabbed and passed to the ViewBuilder for this layout.\npublic class Function1Controller { Builder\u0026lt;Region\u0026gt; viewBuilder; public Function1Controller() { viewBuilder = new Function1ViewBuilder(new SwController().getView()); } public Region getView() { return viewBuilder.build(); } } And the ViewBuilder for the encapsulating MVCI looks like this:\npublic class Function1ViewBuilder implements Builder\u0026lt;Region\u0026gt; { private final Region swContent; public Function1ViewBuilder(Region swContent) { this.swContent = swContent; } @Override public Region build() { StackPane results = new StackPane(swContent); results.setPadding(new Insets(4)); return results; } } You can see here that this is a whole lot of almost nothing. The StackPane is just there to have it not be absolutely nothing. In fact you, could do it like this:\npublic class Function1ViewBuilder implements Builder\u0026lt;Region\u0026gt; { private final Region swContent; public Function1ViewBuilder(Region swContent) { this.swContent = swContent; } @Override public Region build() { return swContent; } } Which works just as well. From this it should be clear that we\u0026rsquo;re just stuffing the visual component from an autonomous MVCI onto the screen, without interacting with it in any other way.\nYou can actually do away with Function1 entirely, and just instantiate SwController in the MainMvciController. It\u0026rsquo;s left the way it is for consistency with the other examples.\nOne last thing to note here\u0026hellip;where\u0026rsquo;s the Kotlin? There\u0026rsquo;s not one stitch of Kotlin code that you need to look at in this example because it\u0026rsquo;s irrelevant. It\u0026rsquo;s all in the SwController, and we just don\u0026rsquo;t care about how it works. So long as it has a getView() method that returns a Region, that\u0026rsquo;s all we need.\nWhere to Instantiate the Encapsulated Controller? # This is an interesting question, and I\u0026rsquo;m sure there will be different opinions on it. In a simple application, it can be such a trivial decision that it\u0026rsquo;s not worth wasting time on while in a complicated application it can become one of the most critical design decisions that you make.\nThere are two choices:\nIn the Controller, and pass the View to the encapsulating ViewBuilder In the encapsulating ViewBuilder The second option seems simpler. Just instantiate the encapsulated MVCI in the ViewBuilder and put its View in the layout. But there\u0026rsquo;s one big consideration: Side Effects.\nIf this encapsulated MVCI does something, especially update data, then it\u0026rsquo;s not really just View any more, is it?\nThat sounds like we\u0026rsquo;re just nit-picking on semantics. But it can be important. View is where we see user interaction, and just user interaction. If we\u0026rsquo;re adding components that do more than that, then we\u0026rsquo;re adding back coupling in the View that we\u0026rsquo;ve worked so hard to avoid. And that coupling always makes it harder to work with an application.\nIt can get even worse. Let\u0026rsquo;s say that this encapsulated MVCI can independently update data that also appears elsewhere in the View. Now we run the risk that data on the screen is out of date because of something that happened on the same screen! And if you put anything in the View that tries to cope with that, then you\u0026rsquo;re getting even further away from having the View be just a View.\nOn the other hand, if you instantiate the encapsulated Controller in the encapsulating Controller than you can treat that as a Controller. If there\u0026rsquo;s data sharing that needs to be coped with, then the encapsulating Controller can deal with it. Then you just pass the encapsulated View to the encapsulating View, and it can remain just a View.\nFor a fully autonomous example like this one, it basically moot. But in terms of establishing a consistent coding technique you\u0026rsquo;re better off instantiating the encapsulated Controller in the encapsulating Controller and just do it that way every time.\nFunctional Custom Node # The next example is to create a \u0026ldquo;custom component\u0026rdquo; that comprises an MVCI framework. For this we\u0026rsquo;re sticking with the SWFX functionality, but creating another class which will become our custom component.\nThe first challenge here is that we need to have our custom class extend some kind of Node. Which Node you pick depends on how close you want your custom class to be handled like a regular JavaFX Node class. Specifically, whether or not you want to instantiate it with new or if you\u0026rsquo;ll supply some static method that returns a Node class.\nFor this example, I wanted to have it act exactly like any other Node, so it\u0026rsquo;s instantiated with new. This means that it cannot just extend Region, because that doesn\u0026rsquo;t have the properties and methods to be a custom layout. So you need to pick a class that does have these methods. This example class extends StackPane, as that\u0026rsquo;s one of the easiest classes to deal with, and doesn\u0026rsquo;t have a lot of complex layout methods, but yet does enough. In this case, we\u0026rsquo;re only going to use the getChildren() method.\nThe second challenge is to structure it nicely. It is possible to have a separate Controller and ViewBuilder, and that will work perfectly. As a matter of fact, the JavaFX controls do that, but they call the ViewBuilder the \u0026ldquo;Skin\u0026rdquo;. It\u0026rsquo;s a bit different but the same idea. Rather than go down the Skinnable path for this example, it\u0026rsquo;s been simplified here into a single class that acts as Controller and ViewBuilder and has the name SwLookupBox.\nTo keep with the spirit of MVCI, the method that handles all of the custom layout is called buildView():\nopen class SwLookupBox : StackPane() { private val interactor: SwInteractor protected val viewModel = SwModel() init { interactor = SwInteractor(viewModel) children += buildView() } private fun searchForCharacter(postSearchAction: Runnable) { val searchTask: Task\u0026lt;Void?\u0026gt; = object : Task\u0026lt;Void?\u0026gt;() { @Throws(Exception::class) override fun call(): Void? { interactor.lookupPerson() return null } } searchTask.onSucceeded = EventHandler { interactor.updateModelAfterLookup() postSearchAction.run() } val searchThread = Thread(searchTask) searchThread.start() } private fun buildView(): Region = HBox(6.0).apply { children += Labels.prompt(\u0026#34;Name:\u0026#34;) children += TextField().apply { textProperty().bindBidirectional(viewModel.name) } children += Button(\u0026#34;Search\u0026#34;).apply { onAction = EventHandler { isDisable = true searchForCharacter { isDisable = false } } } } } OK, we actually have some Kotlin here. Here are some things that might confuse you, the rest of the code you can probably figure out:\nDeclarations Declarations are backwards from Java, in the form \u0026ldquo;name : type\u0026rdquo;. This goes for variables, parameters and methods (which are called \u0026ldquo;fun\u0026rdquo; in Kotlin). Values can be assigned, and new isn\u0026rsquo;t needed for constructor calls. Object.apply{} The apply method takes a block of code and passes the object to it as this. In Kotlin, you can leave out the this if it\u0026rsquo;s clear that a method call refers to it. children += These lines equate to this.getChildren().add(). Lambdas as The Last Parameter If the last parameter passed to a method or constructor is a lambda, it can be defined in {} outside the regular parameter list. So EventHandler {} calls the constructor for EventHandler and passes a lambda as its only parameter. Since it\u0026rsquo;s really now a JavaFX Node, it\u0026rsquo;s instantiated from within the ViewBuilder for the encapsulating MVCI:\npublic class Function2ViewBuilder implements Builder\u0026lt;Region\u0026gt; { @Override public Region build() { VBox results = new VBox(6, Labels.h3(\u0026#34;Star Wars Lookup\u0026#34;), new SwLookupBox()); results.setPadding(new Insets(40)); return results; } } You probably need to be careful that this approach doesn\u0026rsquo;t become a \u0026ldquo;slippery slope\u0026rdquo; situation, and start to pick and choose which elements of MVCI framework you discard when it\u0026rsquo;s not convenient. But if you really, really, really want your custom stuff to feel like a standard JavaFX Node, this is something to think about.\nScreens that Accept and Return Values # Generally speaking, unless an encapsulated MVCI framework has side-effects, it\u0026rsquo;s not able to do anything unless it shares data with the encapsulating MVCI framework. Think about a control like a TextField, it needs to have a link to/from its Text property to your model to be of value. Encapsulated MVCI frameworks can work the same way.\nVia Encapsulated MVCI # The general idea is that the encapsulated Controller needs to accept one or more properties from the encapsulating controller that it can bind to its own Model. In this case, we\u0026rsquo;re just going to pass the planet property to the encapsulated controller.\npublic class Function4Controller { Builder\u0026lt;Region\u0026gt; viewBuilder; Function4Model model = new Function4Model(); public Function4Controller() { Function4Interactor interactor = new Function4Interactor(model); SwLookupController planetFinderController = new SwLookupController(model.planetProperty()); viewBuilder = new Function4ViewBuilder(model, planetFinderController.getView(), interactor::convertToUpperCase); } public Region getView() { return viewBuilder.build(); } } Let\u0026rsquo;s have a look at the SwLookupController. It\u0026rsquo;s in the starwars package, so it\u0026rsquo;s in Kotlin too:\nclass SwLookupController(val planetNameProperty: StringProperty) { private val interactor: SwInteractor private val viewModel = SwModel() private val viewBuilder: SwLookupViewBuilder init { interactor = SwInteractor(viewModel) viewBuilder = SwLookupViewBuilder(viewModel) { searchForCharacter(it) } planetNameProperty.bind(viewModel.homePlanet) } private fun searchForCharacter(postSearchAction: Runnable) { val searchTask: Task\u0026lt;Void?\u0026gt; = object : Task\u0026lt;Void?\u0026gt;() { @Throws(Exception::class) override fun call(): Void? { interactor.lookupPerson() return null } } searchTask.onSucceeded = EventHandler { interactor.updateModelAfterLookup() postSearchAction.run() } val searchThread = Thread(searchTask) searchThread.start() } fun getView(): Region = viewBuilder.build() } In our SwModel, we already have a property for homePlanet, so all we need to do to connect it to the encapsulating MVCI is to bind homePlanet to the supplied property from the encapsulating MVCI.\nVia Functional Custom Node # Once again, all of the action takes place in the encapsulating ViewBuilder, rather than the Controller, with the custom Node approach:\npublic Region build() { SwPlanetFinder swPlanetFinder = new SwPlanetFinder(); model.planetProperty().bind(swPlanetFinder.planetProperty()); VBox results = new VBox(20, Labels.h3(\u0026#34;Planet Finder Component\u0026#34;), swPlanetFinder, createUpperCaseBox()); results.setPadding(new Insets(40)); return results; } SwPlanetFinder is simply an extension of SwLookupBox with a getter for the planet property:\nclass SwPlanetFinder : SwLookupBox() { fun planetProperty(): ReadOnlyStringProperty = viewModel.homePlanet } Note that this is backwards from the previous approach. The SwPlanetFinder presents a getter for the planet property, and it\u0026rsquo;s the responsibility of the encapsulating View to handle the binding. This also matches the design of the properties for the standard JavaFX Node classes - provide getters for the properties.\nLoading in a new Scene # This scenario comes up all the time with the FXML crowd. There\u0026rsquo;s something about the way that FXML is understood that makes beginners think that they need to load every layout into its own Scene. Then they get lost about how they might possibly share data between two Scenes.\nYou should be getting the idea by now that how you put your View on the screen has absolutely nothing to do with how the various functions are connected to each other. Load your View into a Panel in another View, use it like a custom component, call it from a MenuItem, or put it into its own Scene - it\u0026rsquo;s all the same in terms of connectivity.\nIn this example, we\u0026rsquo;re going to create an encapsulating MVCI that launches an extra window with our search functionality. There\u0026rsquo;s no attempt to do anything slick here, and the code doesn\u0026rsquo;t contain anything to stop you from opening multiple windows (it actually works fine when you do this), and they open up on top of the main window.\nInstead of passing a View to the encapsulating View, we\u0026rsquo;re going to pass an action to open up the new window:\npublic class Function5Controller { Builder\u0026lt;Region\u0026gt; viewBuilder; Function5Model model = new Function5Model(); SwLookupController planetFinderController; public Function5Controller() { Function5Interactor interactor = new Function5Interactor(model); planetFinderController = new SwLookupController(model.planetProperty()); viewBuilder = new Function5ViewBuilder(model, this::launchWindow, interactor::convertToUpperCase); } private void launchWindow() { Stage stage = new Stage(); stage.setScene(new Scene(planetFinderController.getView())); stage.show(); } public Region getView() { return viewBuilder.build(); } } You can see that the content of the new window is nothing special, just the same structure that we\u0026rsquo;ve used before.\nInside the encapsulating View, we\u0026rsquo;ll add a Button to trigger that action:\npublic Region build() { VBox results = new VBox(20, Labels.h3(\u0026#34;Planet Finder MVCI\u0026#34;), createWindowButton(), createUpperCaseBox()); results.setPadding(new Insets(40)); return results; } private Node createWindowButton() { Button results = new Button(\u0026#34;Launch Window\u0026#34;); results.setOnAction(evt -\u0026gt; windowLauncher.run()); return results; } A Bigger Lesson From This # In reality, you\u0026rsquo;ll probably not be loading up Scenes very often. However, the idea that you might launch an Action to link your View element to the encapsulated MVCI can be useful. Imagine that you have a MenuItem that is supposed to start up an MVCI and load it into the GUI - you would do that through an action.\nThis would mean that you would replace the launchWindow() method above with something that instantiates an MVCI Controller, calls its getView() method and puts that onto the GUI somewhere. Then you pass that method reference to the MenuItem.\nScreens that Use the Parent\u0026rsquo;s Interactor # This is where things really start to get interesting. Sometimes you have piece of functionality that\u0026rsquo;s used in a variety of places, so you don\u0026rsquo;t want to repeat the same code over and over. It has some of its own business logic, but how it works depends on the encapsulating function.\nAn example of this might be a \u0026ldquo;Customer Lookup Box\u0026rdquo;. Pretty much the View and some of the logic is going to be the same no matter where it\u0026rsquo;s used. But if you put it into an invoice inquiry screen, then you only want to see customers with Invoices, and if it\u0026rsquo;s a customer rewards screen, you only want to see customers enrolled in the rewards program. Or maybe it\u0026rsquo;s something way more subtle or complicated.\nThis kind of scenario comes up more often than you might expect in larger applications, and how you handle it can make a big contribution to how easy it is to develop and maintain that application.\nWhat you need to do is to find a way to allow the encapsulated MVCI to delegate at least some of its functionality to the encapsulating MVCI. In just about every case, you\u0026rsquo;ll probably need to share data, too. So that\u0026rsquo;s what this example shows.\nHandling Background Threads # This is the trickiest part. You need to run any blocking service requests off the FXAT and on a background thread. But where to do this? In the encapsulated Controller or the encapsulating Controller?\nFirst, let\u0026rsquo;s look at how a basic situation is handled without multiple MVCI constructs. You have a flow that looks like this:\nThe process is triggered via the Event handling in the View which is going to invoke an action defined in the Controller. The Controller is going to instantiate and configure a Task, which all happens on the FXAT, and then start it up on a background thread. Typically, the Task's call() method is just going to invoke a method in the Interactor. That method will have any business logic that\u0026rsquo;s required to get ready to invoke some Service call, call the Service and the deal with any data that comes back. Then the OnSucceeded EventHandler of the Task is going to be invoked on the FXAT (that\u0026rsquo;s how we get back on the FXAT), and it will generally call another method in the Interactor that deals with the new data and updates the Model according to its business logic. Finally, a Runnable passed from the View to the Controller is invoked to perform any wrap-up stuff that\u0026rsquo;s required in the View.\nNow, with an encapsulated MVCI, you\u0026rsquo;ll still have just one stretch of actions that take place on the background thread, but the flow will be a little bit different:\nYou can see that all of the background stuff is \u0026ldquo;owned\u0026rdquo; by the outer MVCI. So that\u0026rsquo;s where the Task needs to go. Everything that happens on the inner MVCI happens on the FXAT. Just knowing this makes it easier to figure out how to implement it.\nThe example for this is in the sample project, but contains too many classes to include the complete code in this article. It has two MVCI constructs (the inner and outer), and a dummy Broker with a dummy domain object so that the entire process can be easily visualized. The data elements in both of the Models is manipulated just so that it\u0026rsquo;s easy to see what code has been executed as the processing runs.\nLet\u0026rsquo;s take a look at how the boxes in the diagram above are implemented in code:\nEvent Handling This happens in the inner View. Some Node, like a Button triggers an Event which is passed to its EventHandler. This will do some GUI stuff and then call the accept() method of a Consumer from the Inner Controller, passing it a Runnable containing the code to reset the GUI after the work is complete. button.setOnAction(evt -\u0026gt; { button.setDisable(true); buttonAction.accept(() -\u0026gt; { button.setDisable(false); }); }); Pre-Processing (Inner) The inner Controller calls a method in the inner Interactor to handle any set-up that needs to be done before passing control over to the outer MVCI. Then the inner Controller calls the accept() method of the Consumer supplied by the outer Controller, passing in a Runnable containing all of the inner MVCI post Task processing. private void performLookup(Runnable postActionGuiStuff) { interactor.preLookupProcessing(); outerLookupHandler.accept(() -\u0026gt; { interactor.postLookupProcessing(); postActionGuiStuff.run(); }); } This code snippet shows all of the inner Controller handling for the lookup. This covers the steps \u0026ldquo;Model Update (Inner)\u0026rdquo;, and \u0026ldquo;GUI Cleanup\u0026rdquo; described below for the inner Controller. {: .small}\nPre-Processing (Outer) The outer Controller calls a method in the outer Interactor to handle any set-up that needs to be done before passing control over to the Task. private void handleInnerLookup(Runnable innerRunnable) { interactor.preProcessLookup(); Task\u0026lt;Void\u0026gt; task = new Task\u0026lt;Void\u0026gt;() { @Override protected Void call() throws Exception { interactor.performLookup(); return null; } }; task.setOnSucceeded(evt -\u0026gt; { interactor.updateModelFromLookup(); innerRunnable.run(); }); Thread taskThread = new Thread(task); taskThread.start(); } This code snippet shows all of the outer Controller handling for the lookup. This covers all of the next three steps described for the outer Controller. {: .small}\nTask Creation The outer Controller creates a Task to handle the background thread activity. It specifies actions to be performed when the Task completes. Then the outer Controller creates a Thread to run the Task and starts it. Pre-Service Logic, Service Call \u0026amp; Post Service Logic The call() method of the Task simply invokes a method in outer Interactor which handles the background work. This would involve any business logic required to prepare for the call to the external Service, and the actual call to that Service. After the Service call, more logic will deal with the data returned from the Service call. public void preProcessLookup() { model.setProperty4(\u0026#34;Starting lookup\\n\u0026#34;); model.setProperty1(\u0026#34;Pending...\u0026#34;); model.setProperty2(\u0026#34;Pending...\u0026#34;); model.setProperty3(model.getProperty3().toUpperCase()); } This is from the outer Interactor. {: .small}\nModel Update (Outer) The setOnSucceeded() method of the Task specifies first a call to another method in the outer Interactor which will update the outer Model using the data returned from the Service call. Any portion of the outer Model shared with the inner MVCI will be automatically updated. public void updateModelFromLookup() { progressMessages += \u0026#34;Updating the Model\\n\u0026#34;; model.setProperty1(domainObject.getValue1()); model.setProperty2(domainObject.getValue2() + \u0026#34;, \u0026#34; + domainObject.getValue3()); model.setProperty4(model.getProperty4() + progressMessages); } This is from the outer Interactor. {: .small}\nModel Update (Inner) The last step of the setOnSucceeded method in the Task is to invoke the Runnable passed from the inner Controller. This Runnable will first call the inner Interactor to handle any updates to the inner Model required by the business logic. public void postLookupProcessing() { model.setVariable2(\u0026#34;All done\u0026#34;); } This is from the inner Interactor. {: .small}\nGUI Cleanup Finally, the last step of the Runnable passed from the inner Controller is to invoke the Runnable passed from the inner View as part of the first step. This will put the GUI into the state expected while the background Task is not running. How it Looks # Here\u0026rsquo;s the screen before the button is clicked:\nRight after the button is clicked, and while waiting for the \u0026ldquo;Service\u0026rdquo; to complete:\nFrom here you can see that the Button is now disabled, and all of the inner and outer pre-processing has been performed.\nAnd when it\u0026rsquo;s all done:\nNow all of the post processing has been completed and the Button is re-enabled. Note that the intermediate status updates from the background process are only reflected on screen (and in the Model) after that process has completed.\nReasons Not to Do This # There are probably lots of genuinely good reasons to use this approach, but it does add a layer of complexity to your MVCI frameworks.\nThe question is whether or not you should go this way.\nOne alternative is to put *ALL of the business logic into the outer Interactor, and remove the inner Interactor, as it will now be empty. The result of this is that your inner framework is more of just a layout than a framework and you can treat it as such.\nGenerally speaking, if your reason for splitting the business logic between two frameworks is founded in something other the specific business logic of the frameworks, you should handle it some other way. For instance, if your reason is to share a database connection, control resource loads or optimize performance, those things should be handled at your Service level, or even lower. Doing this means that you can put all of the business logic for the inner framework inside the inner Interactor, and there\u0026rsquo;s no need to have any dependency on the outer Interactor - meaning that the inner framework becomes largely autonomous.\nConclusion # This is pretty long article about what turns out to be a fairly simple technique. But it works because of two much more important concepts that you should understand:\nLoose Coupling Essentially, this idea of joining together disparate bits and pieces into an application works because the MVCI framework provides very loose coupling. The layouts have no idea where the data models come from, nor how the action providers work. The layouts also have no knowledge of how they are contained on the screen and no dependencies on that at all. From the other end, the containing frameworks have no knowledge of the inner structure of the enclosed MVCI framework. They do know that they need to provide data elements and action handlers that are specified in the constructor of the enclosed Controller - but that just about it. The only other dependency is that the enclosed Controller will have a getView() method. Custom Nodes Another really big underlying idea is that any layout that you construct, and any sub-layouts inside them can be considered as a \u0026ldquo;Custom Node\u0026rdquo;. Even if you just create an HBox and put a couple of Labels inside it, you can think of that as a Custom Node. Even if it\u0026rsquo;s just a Label with some styling, you can still think of that as a Custom Node. And just like any other Object that you create, its inner workings should be private from the rest of your layouts. This last point is really about your perspective as a JavaFX programmer. Don\u0026rsquo;t think of your GUI\u0026rsquo;s as a mish-mash of components configured and glued together - think of it as a hierarchy of custom components working together. And this idea scales upwards - from configured Labels right up to the top of your application structure. Just make sure each component is complete within itself and hides its innards from the rest of your code.\n","date":"2 October 2022","externalUrl":null,"permalink":"/javafx/techniques/mvci/2022-11-02-multimvci/","section":"JavaFX","summary":"Projects With Multiple Screens and Functions # One of the questions that seems to come up fairly often is about how to create a project that is composed of a number of different screens and functions.\n","title":"Multi-MVCI Projects","type":"javafx"},{"content":" Introduction # I don\u0026rsquo;t really understand where the mania to swap Scenes comes from, but I see tons of posts, videos and questions about how to swap Scenes in JavaFX. Most of the time, it\u0026rsquo;s in the context of FXML, and I suspect that this is because FXML makes it harder to do something more sensible than swap Scenes in a Stage.\nThe Challenge with Swapping Scenes # The \u0026ldquo;hard\u0026rdquo; part about swapping Scenes is that it needs to be done via Stage.setScene().\nWhy is this hard?\nIt\u0026rsquo;s hard because Stage is passed to the start() method in your Application class. If you\u0026rsquo;re making even a half-hearted attempt to organize your application properly, then all of the code that initiates the Scene swap is going to be somewhere else. This means that you somehow have to find a way to have a reference to your Stage somewhere in your layout code.\nThe most obvious (but horribly bad) way to do this is something like this:\npublic class Layout1Builder implements Builder\u0026lt;Region\u0026gt; { @Override public Region build() { Button button = new Button(\u0026#34;Change to Scene 2\u0026#34;); button.setOnAction(evt -\u0026gt; { Scene newScene = new Scene(new Layout2Builder.build()); button.getScene().getWindow().setScene(newScene)); }); VBox results = new VBox(20, new Label(\u0026#34;Welcome to Scene 1\u0026#34;), button); results.setPadding(new Insets(50)); return results; } } Here we call button.getScene().getWindow() which will give you a Window which is the direct ancestor to Stage and which has the setScene() method. This will work, but it breaks the first of the two cardinal rules of JavaFX design:\nDon\u0026rsquo;t reach up into a parent. Don\u0026rsquo;t peak inside a child. As soon as you break either of these rules, you start to cross over into coupling hell. Any separation that you tried to create by organizing your application into separate classes and packages and whatever is now compromised and you might as well have not bothered.\nSo just don\u0026rsquo;t do it.\nThe other problem is that your layout code needs to know about whatever is going to replace it in the Stage. So now we have yet another dependency. This one is because Scene 1\u0026rsquo;s layout code needs to have a reference to another Scene, in order to put it into the Stage. Same thing holds with that other Scene if you want to go back. Almost by definition, these two Scenes have no actual functional relationship to each other since they can\u0026rsquo;t co-exist on the Stage. Yet they now are dependent on each other because of the Scene swapping.\n{% include notice type=\u0026ldquo;primary\u0026rdquo; content=\u0026ldquo;This dependency is just dead weight. It\u0026rsquo;s coupling with no real value that you have to cope with in your code forever.\u0026rdquo; %}\nSo just don\u0026rsquo;t do it.\nHow To Overcome This Challenge # The big problem is that you are making the handling of your Stage dependent on your layout. You need to eliminate that.\nThe best way to do that is a technique called \u0026ldquo;Dependency Inversion\u0026rdquo;, which is going to make the layout dependent on the Application class (the reverse of how it started). Then we\u0026rsquo;re going to make that dependency palatable by using \u0026ldquo;Constructor Dependency Injection\u0026rdquo;.\nWhat these fancy terms mean is that we are going to create a constructor parameter in the layout class which is going to require a functional element that will handle whatever work required to swap Scenes. This functional element can be as simple as a Runnable, and if not that, then a Consumer.\nThe result of this is that the layout is now dependent on some functionality being passed from Application, but that dependency is clearly advertised as a constructor parameter to the layout. So the layout is saying, \u0026ldquo;If you want to use me, you have to supply a Runnable that does something useful.\u0026rdquo; And we name that constructor parameter in some meaningful way so that everyone understands what it\u0026rsquo;s intended to do.\nNow we\u0026rsquo;ve eliminated the dependency of the Application class on the layout classes.\nYou might be saying to yourself, \u0026ldquo;Yes, but the Application is still dependent on the layouts, because that is what triggers the Scene swap\u0026rdquo;.\nThat\u0026rsquo;s not quite true, though.\nFor sure, there needs to be something in the layout that allows the user to trigger the scene swap. But that\u0026rsquo;s a UX (User Experience) dependency, not a programming dependency. Of course, all the requirements of the UX need to be satisfied by the layout, and only the layout, because that\u0026rsquo;s what it\u0026rsquo;s there for. But that dependency is between the layout and the User, and doesn\u0026rsquo;t have anything to do with the relationship between the layout and the rest of the application.\nAn Implementation # We\u0026rsquo;re going to look at how you might implement an approach that does this dependency inversion and constructor dependency injection to allow us to swap between two Scenes by clicking on a Button in each Scene.\nFirst, let\u0026rsquo;s take a look at the Application class:\npublic class SceneSwapDemo extends Application { @Override public void start(Stage primaryStage) throws Exception { final Scene scene1 = new Scene(new VBox()); final Scene scene2 = new Scene(new VBox()); scene1.setRoot(new Layout1Builder(() -\u0026gt; primaryStage.setScene(scene2)).build()); scene2.setRoot(new Layout2Builder(() -\u0026gt; primaryStage.setScene(scene1)).build()); primaryStage.setScene(scene1); primaryStage.show(); } } Nothing complicated there. We need to initialize the Scenes independent of their content at first, since the two Builder classes need a Runnable that references the Scenes in order to create the content. So we get an extra two lines of code.\nThe two Runnables are also very simple. Each one just loads one of the two Scenes that we\u0026rsquo;ve created into the Stage. Scene 1 loads Scene 2, and Scene 2 loads Scene 1.\nNow let\u0026rsquo;s look at the layout builder for Scene 1:\npublic class Layout1Builder implements Builder\u0026lt;Region\u0026gt; { private final Runnable sceneSwapper; public Layout1Builder(Runnable sceneSwapper) { this.sceneSwapper = sceneSwapper; } @Override public Region build() { Button button = new Button(\u0026#34;Change to Scene 2\u0026#34;); button.setOnAction(evt -\u0026gt; sceneSwapper.run()); VBox results = new VBox(20, new Label(\u0026#34;Welcome to Scene 1\u0026#34;), button); results.setPadding(new Insets(50)); return results; } } We are using a Builder here because we\u0026rsquo;re just doing configuration and layout for the VBox that goes into the Scene. We\u0026rsquo;re not adding new functionality to Region (which really means adding new public methods), so there\u0026rsquo;s no reason to extend Region.\nThe value returned from build() is just a VBox with lots of padding and with a Label and a Button in it. The Button is configured to invoke the Runnable passed to the Builder in its constructor.\nand, finally, the layout builder for Scene 2, which should be pretty familiar:\npublic class Layout2Builder implements Builder\u0026lt;Region\u0026gt; { private final Runnable sceneSwapper; public Layout2Builder(Runnable sceneSwapper) { this.sceneSwapper = sceneSwapper; } @Override public Region build() { Button button = new Button(\u0026#34;Change to Scene 1\u0026#34;); button.setOnAction(evt -\u0026gt; sceneSwapper.run()); VBox results = new VBox(20, new Label(\u0026#34;Welcome to Scene 2\u0026#34;), button); results.setPadding(new Insets(50)); return results; } } It starts out looking like this:\nAnd then looks like this when the Button has been clicked:\nImportant Things to Note About This Approach # The first thing to note is that SceneSwapDemo has no knowledge about the workings of contents of either scene1 or scene2. It knows that it needs to provide a Runnable to the builders, and that Runnable should swap the Scenes. But it doesn\u0026rsquo;t know how that Runnable will be used, when it will be used, or how it will be triggered.\nThe sole dependencies are the constructor parameters that are required for the two layout builders.\nOn the other hand, the actual layouts in the Scenes have no knowledge of, and therefore no dependency on, the functionality of the Runnables that they invoke. Nor do they know how the Runnables work nor what they do, nor does it matter to them.\nNeither layout, and neither layout builder, has any knowledge of the existence of - let alone the content of - the other layout. Certainly, no dependency on it.\nThis entire thing is almost ridiculously simple.\nBetter Than Swapping Scenes # I\u0026rsquo;m not a fan of swapping Scenes. The only place I\u0026rsquo;ve ever really seen a need for it is when you have a Login screen. In that particular case, the initial Scene is really just a stop along the way to getting the application up and running and you might as well throw it away when you\u0026rsquo;re done with it.\nGenerally speaking though, you\u0026rsquo;re better off swapping out the root of a Scene, or even better yet, swapping out some element of your layout. This is especially true when your application\u0026rsquo;s screen has some kind of a framework with a global menu, or other things along that line. You can keep those global elements and then swap out selected content. Let\u0026rsquo;s look at some approaches.\nFor myself, I tend to look at both Stage and Scene as application framework and not an active part of the content.\nSwapping Out the Scene Root # The first thing we\u0026rsquo;re going to look at is just having a single Scene and using Scene.setRoot() to change the content of the Scene.\npublic class SceneSwapDemo2 extends Application { private Region root2; @Override public void start(Stage primaryStage) throws Exception { final Scene scene = new Scene(new VBox()); Region root1 = new Layout1Builder(() -\u0026gt; scene.setRoot(root2)).build(); root2 = new Layout2Builder(() -\u0026gt; scene.setRoot(root1)).build(); scene.setRoot(root1); primaryStage.setScene(scene); primaryStage.show(); } } This looks exactly the same as the scene swapping version when it runs.\nDue to the cross-referencing between the two layouts, root2 needs to be declared first before they can be initialized. Using a field feels a bit kludgey, but in a real application you\u0026rsquo;d probably want to get the layout swapping stuff out of start() anyways, and a cleaner way could be implemented.\nSwapping Out a Layout Component # Now we get to something which is probably closer to the way that you\u0026rsquo;d approach this in a real application: Treating the two layouts as custom components which are dynamically swapped inside of a larger layout. For this, we\u0026rsquo;ll need a wrapper layout:\npublic class WrapperLayoutBuilder implements Builder\u0026lt;Region\u0026gt; { Region customComponent2; @Override public Region build() { BorderPane results = new BorderPane(); results.setTop(new Label(\u0026#34;This is The Wrapper\u0026#34;)); Region customComponent1 = new Layout1Builder(() -\u0026gt; results.setCenter(customComponent2)).build(); customComponent2 = new Layout2Builder(() -\u0026gt; results.setCenter(customComponent1)).build(); results.setCenter(customComponent1); return results; } } You\u0026rsquo;ll notice that this is very similar to the root swapper code from the last example. Instead of using Scene.setRoot(), we\u0026rsquo;re using BorderPane.setCenter(). But the logic is the same.\nNow the Application code becomes 100% generic, and trivial, which is nice:\npublic class SceneSwapDemo3 extends Application { @Override public void start(Stage primaryStage) throws Exception { primaryStage.setScene(new Scene(new WrapperLayoutBuilder().build())); primaryStage.show(); } } When it runs, it looks like this:\nOne the button has been clicked once, it looks like this:\nUsing a TabPane # Let\u0026rsquo;s look at doing something different than swapping out elements, let\u0026rsquo;s create connections between components in a layout. Here, we\u0026rsquo;ll put the layouts into a TabPane, and then let the Buttons select the Tabs:\npublic class TabPaneLayoutBuilder implements Builder\u0026lt;Region\u0026gt; { @Override public Region build() { TabPane tabPane = new TabPane(); Tab tab1 = new Tab(\u0026#34;Tab 1\u0026#34;); Tab tab2 = new Tab(\u0026#34;Tab 2\u0026#34;); tabPane.getTabs().addAll(tab1, tab2); tab1.setContent(new Layout1Builder(() -\u0026gt; tabPane.getSelectionModel().select(tab2)).build()); tab2.setContent(new Layout2Builder(() -\u0026gt; tabPane.getSelectionModel().select(tab1)).build()); return tabPane; } } This example might seem a little bit goofy. Why would you have a Button inside Tab content swap to another Tab? Surely users can just click on the Tab? Sometimes you have a workflow, and while you might want users to be able to move around in a freeform manner using the Tabs, you might also have something like a \u0026ldquo;Next\u0026rdquo; Button that walks users through the workflow.\nHere the logic is a little bit simpler since the Button actions reference the TabPane and not the contents of the Tabs. This means there\u0026rsquo;s no issues around referencing variables that haven\u0026rsquo;t been initialized in a lambda. Otherwise it\u0026rsquo;s essentially the same as the BorderPane example in how it works.\nThe Application class is the same as the previous example except it uses TabPaneLayoutBuilder.\nIt looks like this when it\u0026rsquo;s running:\nManipulating Visibility # Personally, I prefer not to manipulate layouts - actually changing the content of layout - after they\u0026rsquo;ve been built. I think it\u0026rsquo;s better to have everything on the screen and control the look using the Visible and Managed properties of the Nodes. So let\u0026rsquo;s take a look at that:\npublic class VisibilityLayoutBuilder implements Builder\u0026lt;Region\u0026gt; { Region customComponent2; @Override public Region build() { BorderPane results = new BorderPane(); results.setTop(new Label(\u0026#34;This is The Wrapper\u0026#34;)); BooleanProperty vBox1Visible = new SimpleBooleanProperty(true); Region component1 = new Layout1Builder(() -\u0026gt; vBox1Visible.set(false)).build(); Region component2 = new Layout2Builder(() -\u0026gt; vBox1Visible.set(true)).build(); component1.visibleProperty().bind(vBox1Visible); component2.visibleProperty().bind(vBox1Visible.not()); results.setCenter(new StackPane(component1, component2)); return results; } } And when it runs, it looks like this:\nHere we don\u0026rsquo;t need have any cross-referencing between the components as Buttons will now manipulate the BooleanProperty which controls the visibility of the two components. With just two components to swap, it\u0026rsquo;s easy to use a BooleanProperty to control the visibility, but you\u0026rsquo;ll need something more elaborate to control 3 or more components - but the idea remains the same.\nAnother aspect of this that I find really appealing is that it\u0026rsquo;s moving much closer to a \u0026ldquo;Reactive\u0026rdquo; design. There\u0026rsquo;s very little empirical code, just the Runnables, and even those simply update the Model (in this case just the BooleanProperty). The UI changes are due to the bindings between the Model and View elements.\nAnother Look at Coupling # We\u0026rsquo;ve just looked at how to swap Scenes and 4 alternatives to swapping Scenes.\nDid you notice that we never made any modifications to Layout1Builder and Layout2Builder after they were written for the Scene swap scenario?\nWhat does that tell us? It tells us that the coupling between the two layouts and the infrastructure that holds them is minimal.\nLook at what we just did. We started out with one approach and then changed it up radically. And then we changed it up radically again. And again\u0026hellip;\n{% include notice type=\u0026ldquo;primary\u0026rdquo; content=\u0026ldquo;At no time did we have to go back to the layouts and change them due to a radical redesign of our application.\u0026rdquo; %}\nThink about what this means for your applications.\nHow many times have you needed to change something - maybe something not so radical - and those changes have rippled through your entire application leading to massive testing and all kinds of new bugs and issues to figure out? That\u0026rsquo;s what coupling does to you.\nBut here we\u0026rsquo;ve avoided all that, simply because we\u0026rsquo;ve isolated the content from the mechanics and the logic of the application by limiting coupling.\nDoing This With FXML # My belief is that the only way you can do this in pure FXML is via the Stage.setScene() or the Scene.setRoot() methods. To do this, you need to create your layout as a \u0026ldquo;Custom Control\u0026rdquo;, as documented at the bottom of this page. This allows you to define a constructor with dependency injection. Now you can instantiate your custom controls with Runnable to swap scenes and you can place each of them as the \u0026ldquo;root\u0026rdquo; of a Scene.\nWhat you cannot do is any of the other, purely layout, techniques with the wrapper layouts defined in FXML - as far as I can tell. This is because you cannot pass constructor parameters to your custom controls through an FXML file.\nI could be wrong about this. I try hard to know as little about FXML as possible. There might be some way to implement custom builders or some other complicated silliness to do the dependency injection, but you probably wouldn\u0026rsquo;t be reading this tutorial if you\u0026rsquo;d already mastered those techniques.\nConclusion # We\u0026rsquo;ve looked at how to swap Scenes in a Stage without having to look up from the layout code into the Stage to mess with its workings. Then we looked at a number of alternate approaches to dynamically changing the content on the screen without swapping \u0026lsquo;Scenes\u0026rsquo;.\nIn the course of doing this, we saw how to use Dependency Inversion and Constructor Dependency Injection to split the mechanics of content swapping away from the layout code and dramatically reduce the coupling in the application.\n{% include notice type=\u0026ldquo;primary\u0026rdquo; content=\u0026ldquo;We created screen layouts that are decoupled from the context in which they are going to be used.\u0026rdquo; %}\nThis gives us immense freedom to use those layouts in a variety of situations without needing to do any customization to the layouts to accommodate the context in which they are used.\n","date":"24 May 2022","externalUrl":null,"permalink":"/javafx/techniques/swap-scenes/","section":"JavaFX","summary":"Taking a look at the three most common design patterns for building systems with user interfaces.  How are they different?  which one is best?  Is there anything better?","title":"How to Swap Scenes Properly","type":"javafx"},{"content":" The JavaFX Layout Techniques You Need to Master # Understanding Nodes and Properties and StyleSheets is one thing, but there are also techniques that you\u0026rsquo;ll need to learn in order to create JavaFX applications.\n","date":"24 May 2022","externalUrl":null,"permalink":"/javafx/techniques/layouts/","section":"JavaFX","summary":"These are the essential layout techniques you need to learn with JavaFX.","title":"Layout Techniques","type":"javafx"},{"content":" Introduction # If you\u0026rsquo;re old enough and you live in North America, you probably remember Ronco and their late night \u0026ldquo;Infomercials\u0026rdquo;. One of the most memorable of these was for their \u0026ldquo;Showtime Rotisserie\u0026rdquo;. Take a look, and make note of the catch phrase:\n\"Put the chicken in the oven, follow the instructions, and... SET IT AND FORGET IT!\" But what does this have to do with programming in JavaFX?\nMore than you\u0026rsquo;d think, because this is exactly the process you should follow when you\u0026rsquo;re creating a screen and the individual elements that make up a screen:\nCreate the widget Configure the widget Bind the widget SET IT in the layout AND FORGET IT! How Does This Work? # The most important idea here is that \u0026ldquo;State\u0026rdquo; and \u0026ldquo;View\u0026rdquo; are not the same thing. In fact, they are quite different:\nState State is the data related to a UI. Generally it is going to be reflected in the information shown on the screen, or the presentation of that data on the screen. In JavaFX, state is usually held in Observable objects of some sort. View The View is the visual presentation on the screen, and the component of your system that the user will directly interact with. In JavaFX, the components that make up the View are generally bound to components of State. In this way, changes to the State will be immediately reflected in the View, and changes made in the View are immediately reflected in State. Doing this allows the rest of your application to access, modify and react to changes in State without any dependency on the nature of the View.\nIn practice, it looks like this:\nWhy is This Important? # Coupling.\nIt\u0026rsquo;s all about coupling or, rather, not coupling.\nA View can be a complicated thing. There are all sorts of visual and user experience factors that need to be dealt with and potentially you might need to account for accessibility issues or different client devices. Putting code in there that calls out to business logic, or providing hooks to allow external classes to update data in the View directly quickly builds up the complexity of your View and clutters it with stuff that\u0026rsquo;s not, strictly speaking, \u0026ldquo;View\u0026rdquo;. Not to mention that you\u0026rsquo;re going to need to write some code to get the data out of your View and expose it to external classes.\nBut, as you can see from the diagram above, the View and the Business Logic are ignorant of each other, so neither one can become bogged down with the complexities of the other. And changes to one will not require changes to the other.\nJavaFX provides an incredibly complete toolkit to get rid of all of that complexity. It\u0026rsquo;s called \u0026ldquo;Binding\u0026rdquo;.\nWith Binding you can create an external class to hold all of the elements of State as Observable object fields. Then you pass that class to your View and it can bind the individual elements of State to the various components and their properties in the View. Once you\u0026rsquo;ve done that, you can just about forget about the inner workings of your View when you\u0026rsquo;re writing your business logic.\nThis Idea Scales! # You can think of this concept like a fractal diagram. At a big picture level, you have a UI with one or more screens involved with it. Together those screen comprise a single View, and would have a single State. But each of those screens is a View itself, and has an associated State. Maybe some of those screens have sections which are written as custom components, each of which can be considered to be a View with their own State. And when you look into the layouts of any of these screens you see sub-layouts and containers, each of which can be considered a View with an associated State.\nFinally, you get down to the individual Nodes in the layouts. Each of those can be considered a View, and each of them would have their own associated State.\nNo matter what level you\u0026rsquo;re looking at, the process is always the same. Create the View, configure the View and bind it to State, then set it in a layout and FORGET IT. {: .notice\u0026ndash;primary}\nState Data is Not View Data # This is a subtle but important distinction.\nAll of the data in State is in State because it is needed for the View, but it\u0026rsquo;s not the data of the View.\nLet\u0026rsquo;s look at some examples to understand what this means.\nData in State is in its native format. For instance, if you have a data element which is a whole number, it should be represented in State as an Integer type. The View might need to transform it into some other type in order to display it on the screen - probably a String, but maybe an Enum. Maybe the View needs to change it into a decimal between 0 and 1.0 so that it can be shown in a ProgressBar. Leave it as an Integer in State, and let the View deal with the presentation.\nData in State is organized to be independent of the View. Think about a name. How should a name be displayed? Last name, comma, first name? First name, space, last name? Just last name? Just first name? Name should probably be stored in State as two fields; first name and last name. That way the View alone can be responsible for how the presentation is done.\nData in State is identified by business meaning. As an example, let\u0026rsquo;s imagine that some value needs to be shown in a different colour if it\u0026rsquo;s in an error state, so you create a separate field in State to indicate this. What kind of data should it be, and what do you call it? Well, you don\u0026rsquo;t make it type Colour and call it XyzFieldColour. Something like a Boolean type called AbcValueExceedsLimit, would be much better. Nor would you make it a Boolean called XyzInRed. That wouldn\u0026rsquo;t make any sense to the Business Logic module - and what would happen if the View was changed so that it in purple? Let the View interpret the meaning and decide about the presentation.\nNotice a trend? State is designed to convey meaning to the data, the View is designed to handle presentation of the data. Keep these two roles strictly separated. {: .notice\u0026ndash;primary }\nHow Do You Do This? # At the Top Level # First, use some framework that separates View, State and Business Logic. I\u0026rsquo;ve found that my MVCI structure works well.\nWith MVCI there is a Controller which instantiates the State (the \u0026ldquo;Model\u0026rdquo;) and passes a reference to both the View and the Interactor (the Business Logic) in their constructors. The Model is just a POJO with the fields made up of JavaFX Observable objects.\nAs for the \u0026ldquo;Set It and Forget It\u0026rdquo; part, the Controller exposes a single method called getView() which returns a Region. Whatever class instantiates the Controller (very often something like Application which creates the Stage and Scene), then calls this method to get a very generic layout class which it can place into a Scene or another layout.\nThe result is that the entire MVCI construct presents itself as a generic Region to the rest of your application\u0026rsquo;s GUI. Whatever information it needs to bootstrap itself is supplied through constructor parameters or pulled through an external API in the Interactor. Your application\u0026rsquo;s GUI instantiates it, sets it in a Scene or layout, and FORGETS IT.\nInside the Layout # The best practice here is to stick to the \u0026ldquo;Single Responsibility\u0026rdquo; Principle, and \u0026ldquo;Don\u0026rsquo;t Repeat Yourself\u0026rdquo; (or, \u0026ldquo;DRY\u0026rdquo;), and things should just naturally fall in place.\nLet\u0026rsquo;s say that your main layout is a BorderPane. You\u0026rsquo;re going to have some quantity of zones in the BorderPane that you\u0026rsquo;ll want to populate and configure. Don\u0026rsquo;t stick it all in one, giant method. Have a top level method that just has one line of code for each zone, and that line calls some other method to construct the layout that goes into that zone. Something like this:\nprivate Region buildLayout() { BorderPane results = new BorderPane(); results.setTop(createTop()); results.setCenter(createCentre()); results.setBottom(createBottom()); return results; } Now buildLayout() has just one responsibility - to create the main layout.\nAlso note that buildLayout() doesn\u0026rsquo;t return BorderPane, it returns Region. There\u0026rsquo;s not much you can do with Region other than put it in a Scene or layout and set some constraints on its size. You certainly can\u0026rsquo;t muck about with its contents or call setRight() on it. This pretty much forces whatever calls buildLayout() to, \u0026ldquo;Set It and Forget It\u0026rdquo;.\nThe same thing goes for createTop(), createCentre() and createBottom(), all of which are going to return either Node or Region. You can see that buildLayout() simply calls those methods, takes the results and sets it in the BorderPane and then forgets them.\nLike I said earlier. This concept is fractal in nature. If createCentre() is going to create a BorderPane itself, then I\u0026rsquo;d expect it to look very much like buildLayout() in its structure. And so on\u0026hellip;\nBTW: Don\u0026rsquo;t give those methods names like, createCentre(). Call them something meaningful, like createDataEntryBox() or whatever. Anything but, createCentre(). {: .notice\u0026ndash;warning }\nAnother important point here is that the BorderPane has been named, \u0026ldquo;results\u0026rdquo;. This has a huge psychological impact as it really says, \u0026ldquo;I have no meaningful existence as an object outside of the context of this method\u0026rdquo;. That makes it very clear that you are going to \u0026ldquo;Set It and Forget It\u0026rdquo;.\nAt the Node Level # Individual Node declaration tends to be more about configuration than layout, but the ideas are very much the same. Let\u0026rsquo;s look at a simple example:\nprivate Node createNameBox() { Label prompt = new Label(\u0026#34;Name:\u0026#34;); prompt.getStyleClass().add(\u0026#34;prompt-label\u0026#34;); Label name = new Label(); name.textProperty().bind(state.nameProperty()); name.getStyleClass().add(\u0026#34;data-label\u0026#34;); return new HBox(5, prompt, name); } There\u0026rsquo;s a few things to make note of here:\nThe returned HBox is never instantiated as a variable. This is the end goal for \u0026ldquo;Set It and Forget It\u0026rdquo;. This method returns Node, not HBox. No calling method can use it as an HBox. These two Labels are only instantiated as variables because they need to be configured. This method is violating the \u0026ldquo;Single Responsibility Principle\u0026rdquo;, as it both performs layout and configures the Nodes. This last point is important, because it suggests some significant improvements which are going to get us even closer to \u0026ldquo;Set It and Forget It\u0026rdquo; - remember how I said this would follow naturally?\nLet\u0026rsquo;s do this:\nprivate Node createNameBox() { return new HBox(5, createStyledLabel(\u0026#34;Name:\u0026#34;, \u0026#34;prompt-label\u0026#34;), createBoundStyledLabel(state.nameProperty(), \u0026#34;data-label\u0026#34;)); } private Node createStyledLabel(String contents, String style) { Label label = new Label(contents); label.getStyleClass().add(style); return label; } private Node createBoundStyledLabel(ObservableStringValue contents, String style) { Label label = new Label(); label.TextProperty().bind(contents); label.getStyleClass().add(style); return label; } Now the configuration code is completely divorced from the layout code, so there\u0026rsquo;s no need to have any local variables in createNameBox(), and it\u0026rsquo;s become just one line.\nIf you think about it a little bit, you\u0026rsquo;ll see that createStyledLabel and createBoundStyledLabel have absolutely zero code in them which is specific to this, or any, particular layout. You could split them off into a static utility class somewhere and never have to think about the those Labels as Labels ever again.\nKotlin has \u0026ldquo;scope\u0026rdquo; functions that really bludgeon you over the head with this idea of \u0026ldquo;Set It and Forget It\u0026rdquo; because you can completely dispense with explicitly instantiating variables for these Labels:\nfun createBoundStyledLabel(contents : ObservableStringValue, style: String) : Node = Label().apply{ styleClass += style textProperty().bind(contents) } In truth, the apply function has an implicit receiver of this, which can then be omitted when referencing fields or methods of the Label in the scope of apply. But the impact is still huge - it\u0026rsquo;s never given a name and it\u0026rsquo;s a Label only in the scope of the apply.\nGeneral Guidelines # Single Responsibility and DRY Will Get You There # JavaFX is chock full of repeated boilerplate that you need to get out of your layout code. Try to keep configure the amount of configuration code (things like binding and setting styles) in your layout code down to a minimum. Write helper/builder methods with parameters to handle repeated configuration operations.\nAs you do this, you get closer and closer to \u0026ldquo;Set It and Forget It\u0026rdquo;.\nAvoid Nodes as Fields # Declaring a Node object as field in your View is essentially announcing that you\u0026rsquo;re planning on referencing it as a global variable throughout the class. That\u0026rsquo;s pretty much the opposite of, \u0026ldquo;Set It and Forget It\u0026rdquo;.\nSo don\u0026rsquo;t do it.\nThink about why you want to make it a field. What aspect of that Node do you want to access from all over your View? Maybe you could move that information into your State?\nDon\u0026rsquo;t Extend, Use Builders # The straight-ahead approach to build a View is to take a class like BorderPane and extend it.\nDon\u0026rsquo;t do that.\nCreate a ViewBuilder class which implements Builder\u0026lt;Region\u0026gt;. Call its build() method to create your view, and return it as a Region.\nThe important point here is that once your application has received a Region there\u0026rsquo;s not much it can do with it except put it in a layout and forget it.\nViews and ViewBuilders Have no Public Methods # \u0026hellip;other than build(), and their constructors.\nEvery new non-private method that you add to a View or ViewBuilder increases the coupling with the other components of your application. This is the opposite of what you want to achieve.\nWho\u0026rsquo;s going to call these methods?\nProbably your business logic. And that\u0026rsquo;s bad because it means that your business logic is now becoming entangled with the implementation of the View.\nBuilders and Builder Methods Return Node or Region # The higher you can go up the tree structure of JavaFX objects with your return types, the less functionality you\u0026rsquo;ll expose to the rest of your application, and the more freedom you\u0026rsquo;ll have to design the inner workings of that View without worrying about compatibility with any other part of your application.\nDon\u0026rsquo;t Put Business Rules in Your View # This is one of the primary guidelines to decide \u0026ldquo;What goes where?\u0026rdquo;\nImagine that you have a screen with some kind of warning icon on it somewhere. That icon is invisible until one of a subset of choices in a ComboBox is selected. It\u0026rsquo;s tempting to say, \u0026ldquo;This is View, I can bind the Visible property of the icon to the Value property of the ComboBox\u0026rdquo;.\nBut is it View?\nNot really, the conditions that determine if the warning icon should be shown are really business rules. Somewhere, you need some code that says, \u0026ldquo;These particular choices are important\u0026rdquo;. And that code is business logic. So it can\u0026rsquo;t go in the View.\nFar better is to create a Boolean property in State that indicates that whatever condition that would merit a warning has been met. Usually, that kind of a property is going to be a Binding, and the internal logic of the Binding would be set by your business logic class.\nIt\u0026rsquo;s important that programmers looking for business logic know where to go to find it. So keep it in one place.\nYou\u0026rsquo;re more likely to break this rule when it\u0026rsquo;s all contained within a single Node. For instance, if you want to change the colour of a Label when it\u0026rsquo;s value meets certain criteria. It\u0026rsquo;s so easy to just create a binding that looks at the value and restyles the Label based on that value. But those conditions are business logic.\nIn truth, the State for that Label actually contains two properties; one for the value and another to convey that some business condition has been met. Once again, that second property is likely to be a Binding that should be defined in your business logic class.\nBinding Properties # Bind Node Properties to State First # All of your Nodes should be as independent of each other as possible. So create as much of those dependencies with State as you can, even if this means that you have many Nodes and their properties bound to the same State property.\nCreate a Property Field in the View, and Bind to It # Imagine that you\u0026rsquo;ve identified some common aspect of your View that needs to be bound together. It\u0026rsquo;s not State, because it is completely internal to the presentation of the information in the GUI. As an example, let\u0026rsquo;s say that you want to have a \u0026ldquo;Page Down\u0026rdquo; Button activated whenever a Pane displays a scroll-bar. Rather than binding the Enabled property of the Button to the Visible property of the scroll-bar, create a Boolean property field or variable, call it \u0026ldquo;PageDownAvailable\u0026rdquo;, and bind it to the scroll-bar Visible property. Then bind the Disable property of the Button to it.\nThis is really the best route when the Nodes involved exist in different scopes.\nBind Nodes Properties to Node Properties as a Last Resort # And only do this when the relationship is strictly View. For instance, bind the MaxWidth of an HBox, to the Width of another HBox. Generally, you\u0026rsquo;re only going to do this when the two Nodes involved have been instantiated in the same scope.\nConclusion # Nothing in this article is particularly complex, or difficult to master.\nBut none if it is obvious when you\u0026rsquo;re starting out with JavaFX, and I\u0026rsquo;ve never seen any article or tutorial that lays it out clearly.\nAnd this approach works. It works really well because it strips out a huge amount of complexity in your design. It\u0026rsquo;s really amazing how even complicated systems can become almost trivial to execute in JavaFX when you take the right approach - and that all starts by drawing an uncrossable line between presentation and business logic.\nI\u0026rsquo;m not going to try and pretend that there\u0026rsquo;s anything innovative or unique about the concept of creating a \u0026ldquo;Presentation Model\u0026rdquo; and binding it to the View. It\u0026rsquo;s an idea that\u0026rsquo;s been around for decades.\nBut the \u0026ldquo;Set It and Forget It\u0026rdquo; philosophy takes this idea and turns it into a practical design pattern that you can implement at every level of your GUI application. I think it\u0026rsquo;s worth adopting.\n","date":"14 March 2022","externalUrl":null,"permalink":"/javafx/techniques/setitforgetit/","section":"JavaFX","summary":"The best way to structure an application is to separate the presentation of your data from the logic of your application.  That can be hard to do.  But not if you take an approach where you configure your screen elements, put them in the layout and then discard any reference to them.","title":"Building Views - The \"Set It and Forget It\" Approach","type":"javafx"},{"content":" Introduction # People seem to get stuck when it comes to turning their JavaFX application into something useful; something that connects to the outside world, or saves and retrieves its data using a database, or interacts with some other application. I\u0026rsquo;ve seen this question asked from both directions:\nHow do I connect my JavaFX application to an SQL Database? I have a screen but I need to be able to save and read information.\nor:\nI have an application that does this interesting thing, but I\u0026rsquo;d like to add a JavaFX front end onto it. How do I do this?\nThese are reasonable questions. How do you do this:\nAnswer: Use a Standard Framework # The simple answer is to use a standard framework that will allow you to split your application into two parts. One part is the GUI, and the other is whatever your application actually \u0026ldquo;does\u0026rdquo;. Personally, I prefer Model-View-Controller, since it works really well with JavaFX as a Reactive environment. But that just describes the GUI part of your application.\nAt some point, you have to connect your GUI with the part of your application that does the work - which we\u0026rsquo;ll call the \u0026ldquo;domain\u0026rdquo; code. You need an interface that allows the GUI and the domain code to interact in a particular way. I call that the \u0026ldquo;Interactor\u0026rdquo;.\nNow your application has three parts:\nThe GUI (Model, View and Controller) The Interactor The Domain Code Why Do This? # This first time I was shown an MVC structure I struggled to understand what the benefit was. How does this make it any easier to build an application? I think that there are two key advantages to using this framework.\nFirst, it makes your programming easier to manage because it reduces coupling. Coupling occurs when the parts of application have too many dependencies on each other. It can be insidious, building up a little at a time, until it becomes impossible to change one part of your application because then you have to change something else, and then something else, and so on. Using a framework like MVC-I forces you to keep those pieces apart from each other, right from the start, and gives you clear rules about how much each part is allowed to know about the other parts. In the end, your application structure is much easier to understand.\nSecondly, it takes away the blank page syndrome. You\u0026rsquo;re not starting from zero, and even if it feels a little bit like, \u0026ldquo;paint by numbers\u0026rdquo;, at the start it makes very easy to get a bare skeleton up and running - sometimes in just minutes.\nThe Interactor # The heart of the question of, \u0026ldquo;How do I make my JavaFX application do something?\u0026rdquo;, really comes down to the role of the Interactor.\nThe Interactor is the only part of your system which has any knowledge about the other two parts. It\u0026rsquo;s the place where domain objects and the Model can exist together, and you can have code which is aware of both of them.\nThe Interactor is also the place where your specific application logic goes. While it has knowledge of the domain, its job is to support whatever function within the application that the MVC part of the framework supplies. It\u0026rsquo;s a part of a process that has the GUI as one end-point, and some \u0026ldquo;external\u0026rdquo; domain at the other end, and it contains all of the rules and logic that make the process work.\nExample Application - A Simple Weather App # Here we\u0026rsquo;re going to look at a simple application that has all of the components that you\u0026rsquo;ll need:\nA Reactive MVC GUI Application logic in an Interactor A weather fetching service A domain object An external API Before we go any further, let\u0026rsquo;s see what it looks like when it\u0026rsquo;s running:\nIt\u0026rsquo;s pretty simple. You pick a city from a small list in the ComboBox then click \u0026ldquo;Get Weather\u0026rdquo; and it retrieves the information. There\u0026rsquo;s only 3 pieces of data: the temperature, a description of the conditions, and an image associated with the conditions.\nAll of this information comes from a web API from openweathermap.org.\nAPI Token # If you want to run this application, you\u0026rsquo;ll need an API Token from openweathermap.org. You can get one for free from their site, and the link is on the \u0026ldquo;Pricing\u0026rdquo; page, under the \u0026ldquo;Free\u0026rdquo; column. You\u0026rsquo;ll probably be redirected to a login/sign-up page before you get there.\nGet An API Token\nWhen you get a token (it\u0026rsquo;s just a string of characters), you\u0026rsquo;ll need to copy it into a resource file in the project. Under \u0026ldquo;resources\u0026rdquo;, create a directory called \u0026ldquo;data\u0026rdquo;, and then create a file in it called \u0026ldquo;api.data\u0026rdquo;. Then just copy your token into it on the first line. Check the constructor of WeatherFetcher in the source code if any of this is unclear.\nThe application doesn\u0026rsquo;t have particularly robust error handling, but if it cannot find the token it will give you a message that it had a problem. If the token is invalid, you just won\u0026rsquo;t get any weather reports in the GUI.\nApplication Components # You should take a look at the project on GitHub. It\u0026rsquo;s not very big, and I\u0026rsquo;m only going to include a small part of the code in this article.\nGitHub Project\nThe ViewBuilder # The ViewBuilder is an implementation of the JavaFX Builder utility interface, returning a Region. Using a Builder here is better than the only other real alternative, which would be to extend one of the layout classes, like BorderPane, and configure it through its constructor.\nIn this case, the Region returned is a BorderPane. The centre of the BorderPane is an HBox holding the weather image, and a VBox that has the city name, and a GridPane with the weather information. The bottom of the BorderPane is an HBox with the city selection ComboBox and the Button which triggers the data load.\nAll of the styling in the View is done through CSS, which is included in the project.\nThere are a couple of static convenience classes that instantiate and configure some of the Nodes in the View. This helps to keep the ViewBuilder concerned with layout only, keeps it from getting clogged up with JavaFX boilerplate.\nTo allow the Button in the View to actually do something, the ViewBuilder is supplied a Consumer\u0026lt;Runnable\u0026gt; by the Controller through a constructor parameter. This is really just a callback containing a callback. We\u0026rsquo;ll look at this a little later.\nFinally, the Model is passed to the ViewBuilder through its constructor, and the Model properties are connected to the View elements as they are instantiated. From this point on, any changes in the Model will be automatically reflected in the View.\nThe Controller # The Controller is responsible for instantiating the other MVC components and the Interactor. In this respect it\u0026rsquo;s the \u0026ldquo;centre\u0026rdquo; of the application. The Main class for the application instantiates the Controller in order to bootstrap the GUI, and then calls its getView() method to get the root for the Scene.\nThe Controller is also responsible for handling FXAT threading. This is important in this kind of application, because virtually anything that needs to talk to the domain code will need to run off the FXAT, and then the effects of that domain code need to be applied back to the model by code running on the FXAT. Usually, this needs to be coordinated with both the View and the Interactor, so the Controller is the right place for that to happen.\nThe WeatherFetcher # This is the \u0026ldquo;domain code\u0026rdquo; of this application. It\u0026rsquo;s small, just a single class that is able to perform a couple of HTTP \u0026ldquo;GET\u0026rdquo; transactions with openweathermap.org, use some JSON parsing to extract the information and package up the results in a domain object - WeatherData.\nIt essentially becomes a service, wrapping the calls to openweathermap.org in it\u0026rsquo;s own logic and the bundling the response into a locally defined domain object.\nThe Interactor # This is where the \u0026ldquo;application logic\u0026rdquo; resides. The Interactor is instantiated by the Controller, and it is passed the Model as a parameter in its constructor. It has two methods, checkWeather() and updateWeatherModel(). The checkWeather() method invokes a method in the WeatherFetcher, and then stores the WeatherData object that is returned in a private field. The updateWeatherModel() method takes the data stored in that WeatherData object and puts it into the Model.\nAlthough the Interactor has no knowledge, and no logic, that is aware of the FXAT or any other threading issues, the design is such that checkWeather() will be called on a background thread, while updateWeatherModel() will be called on the FXAT.\nInvoking the Weather Fetch # Let\u0026rsquo;s look at the View first, and the set-up for the Button:\nprivate final Consumer\u0026lt;Runnable\u0026gt; weatherFetcher; . . . Button button = new Button(\u0026#34;Get Weather\u0026#34;); button.setOnAction(evt -\u0026gt; { button.setDisable(true); fetchWeather.accept(() -\u0026gt; { button.setDisable(false); }); }); You can see that weatherFetcher is a Consumer\u0026lt;Runnable\u0026gt;. So it\u0026rsquo;s a piece of code that is going to accept another piece of code as a parameter. Essentially, it\u0026rsquo;s a callback.\nWhy do this?\nWhen the Button is clicked, the first thing that we need to do is to disable it, so that it cannot be clicked again while the fetch is happening. But then we need to re-enable it when it\u0026rsquo;s all done. The problem that we have is that there is no mechanism that allows us to \u0026ldquo;wait\u0026rdquo; for the fetch to complete, because that would hang our GUI. So what we have to do is invoke the fetch, and then give the fetcher (whatever it may be) some code to execute when the fetch is completed.\nIn this case, that code just re-enables the Button.\nNow, let\u0026rsquo;s look at the what weatherFetcher does. It\u0026rsquo;s defined in the Controller:\nprivate void fetchWeather(Runnable postFetchGuiStuff) { Task\u0026lt;Void\u0026gt; fetchTask = new Task\u0026lt;\u0026gt;() { @Override protected Void call() { interactor.checkWeather(); return null; } }; fetchTask.setOnSucceeded(evt -\u0026gt; { interactor.updateWeatherModel(); postFetchGuiStuff.run(); }); Thread fetchThread = new Thread(fetchTask); fetchThread.start(); } Here we are defining a JavaFX element called a Task. A Task is JavaFX class which is designed to facilitate running code on a background thread and then get back on the FXAT. It\u0026rsquo;s possible to have a Task return a result, but in this case we don\u0026rsquo;t need it to, as the Interactor is going to store the results in a field.\nThe next thing to note is the call to setOnSucceeded(). Whenever you see a method called setOn...() in JavaFX, that means that you\u0026rsquo;re going to be passing it an EventHandler. The most important thing about this is that EventHandlers always run on the FXAT. The EventHandler passed to setOnSucceeded() is executed when the Task ends in any way other than throwing an exception. So this call to setOnSucceeded() tells us how the Controller is going to get the processing back on the FXAT after it\u0026rsquo;s done running on the background thread.\nIn this case, we want to do two things on the FXAT once the background thread has completed. We want the Interactor to update the Model with the data it just fetched, and then we want to run that code that came from the Button - the one that re-enables the Button.\nFinally, the Controller initializes a new Thread for the Task, and starts it.\nYou should be clear that fetchWeather() itself runs on the FXAT. This means that the Task is configured on the FXAT, the Thread is initiated and started from the FXAT, but everything in fetchTask.call() is executed on that new Thread.\nJust to be more explicit, all of this means that interactor.checkWeather() is run in a background thread, while interactor.updateWeatherModel() is run on the FXAT.\nJust to be even more explicit: This is the only place in our application code that has any knowledge about the FXAT and the only place where threads are addressed at all. This is one of the main responsibilities of the Controller.\nConclusion # This is about as simple as you can make an application that has a domain layer but yet it has all of the basic components that you need to deal with. You could clone this project and start changing the names of the classes and the methods and it probably wouldn\u0026rsquo;t be a bad start.\nYeah, But What If I Really Want To Use FXML? # I wouldn\u0026rsquo;t. It doesn\u0026rsquo;t do anything for you. However, if you must\u0026hellip;\nRemember that the FXML Controller is NOT an MVC Controller. You need to think of the FXML, FXML Loader and FXML Controller together as the \u0026ldquo;View\u0026rdquo; in MVC. And for that matter, the FXML file isn\u0026rsquo;t a Model either, even though it feels like data.\nWhat you\u0026rsquo;ll need to do is to create an MVC Controller that instantiates the View by calling the FXML Loader and then passing your Model into the FXML Controller to create the Bindings that connect it to the Nodes in the GUI. If you can do that, then the rest of your application structure should be just as described in this article.\nBut once again, I wouldn\u0026rsquo;t bother with FXML.\n","date":"6 February 2022","externalUrl":null,"permalink":"/javafx/projects/weatherfx/","section":"JavaFX","summary":"Maybe you’ve figured out how to build a working GUI that looks good.  Now, how do you connect it to the rest of the world and make it do something?","title":"How to Build a JavaFX Application That Does Something","type":"javafx"},{"content":" Wordle # This simple game is everywhere right now (the beginning of 2022) and it seems like everyone is playing it. As I was writing this article, it was announced that Wordle was sold to the New York Times, who will host it in the future.\nIf you don\u0026rsquo;t know about Wordle you should go and take a look at it here{:target=\u0026quot;_blank\u0026quot;}. The website has all of the information that you need to understand how the game works, and you can play it (once a day) to see how it looks when it\u0026rsquo;s running.\nThe game mechanics are very similar to \u0026ldquo;Mastermind\u0026rdquo;, where you get clues about which letters are correct, and which are letters in the word but not in the correct place. It\u0026rsquo;s different in these respects:\nThe indicators are on the guessed letters themselves You know exactly which letters are correct You know exactly which letters are in the wrong place The guess has to be an actual word in the English language You only get six guesses All of these differences make the game a little easier than \u0026ldquo;Mastermind\u0026rdquo;, but the requirement to use real words makes it feel tougher even though this factor limits the field of possible answers.\nFinally, Wordle generates just one target word each day, for the entire world, and you only get to play it once per day.\nWordleFX # WordleFX is written to be an absolute clone of Wordle in terms of look and game play. You get one word per day, and it\u0026rsquo;s the same word as on the website. It doesn\u0026rsquo;t lock you out if you\u0026rsquo;ve lost and restart it, or just restart it part-way through so you can start over. So it has no feature to share your results - that wouldn\u0026rsquo;t be fair.\nWordleFX wasn\u0026rsquo;t really created to be replacement for playing Wordle on the Web. It\u0026rsquo;s intended to be a demonstration of how Reactive JavaFX can be used to create a highly interactive and interesting interface in an application. It\u0026rsquo;s also intended to be a demonstration of how CSS styling can be used as an integral part of a user interface.\nThe Code # This article doesn\u0026rsquo;t contain a lot of code samples. The entire application is about 350 lines of code in 10 classes. Seven of those classes have less than 40 lines of code. No class is more than 80 lines long, so it should be fairly easy to wrap your mind around how the application works.\nWordleFX is written in Kotlin because \u0026hellip; Kotlin. There\u0026rsquo;s nothing too crazy in here, and if you understand Java should should be able to scratch your head a little bit and figure out what most of the code is doing. Maybe you\u0026rsquo;ll decide that Kotlin looks really cool and you\u0026rsquo;ll want to learn it - that would be awesome.\nProbably the strangest thing to Java programmers will be \u0026ldquo;Scope Functions\u0026rdquo;, especially apply which is used copiously in WordleFX to configure Nodes. You can find out more about apply here.\nAnd no, you don\u0026rsquo;t need TornadoFX to write JavaFX applications in Kotlin.\nSo go take a look at the source code in GitHub if you\u0026rsquo;re interested:\nGitHub Project\nAppearance of the Application # As part of this exercise, both light mode and dark mode have been implemented entirely in the CSS. CSS styling is crucial to this application, and most of the user feedback is achieved by manipulating pseudo classes in the style sheets.\nThe application looks like this:\nAnimations # One of the biggest challenges in writing this application is to handle the animations, which really give the game its feel.\nThere are four animations:\nWhen a letter is selected, the tile \u0026ldquo;flashes\u0026rdquo; briefly. When the guess results are revealed, the tiles flip over to show their colour. When an invalid word is entered and checked, the entire row of tiles wiggle briefly. When an invalid word is entered and checked, a toast with an error message fades in, displays briefly and then fades out. Here\u0026rsquo;s what the tiles look like in motion:\nDesign Notes # Reactive Design # The design of WordleFX is 100% Reactive. There is a Model, which is the application \u0026ldquo;State\u0026rdquo;, and all of the components interact with each other indirectly by manipulating or responding to changes in the elements of \u0026ldquo;State\u0026rdquo;.\nIncluding Animations and Pseudo-Class Changes # Animations are necessarily imperative in nature. They are actions that are triggered and happen at a particular moment in time. This can be particularly challenging when animations are involved with changes in the GUI presentation of some element, as those changes cannot simply be bound to the Model since they need to co-ordinate with an animation.\nAt the same time, most of the GUI presentation changes are achieved through Pseudo-Class changes, which are also not Reactive in nature and require Listeners in order to work. It\u0026rsquo;s possible to include the Pseudo-Class change on the Node in the animation, or to co-ordinate it with the invocation of the animation.\nModel-View-Controller-Interactor # WordleFX is built with an MVCI structure, which is MVC with an added component, the \u0026ldquo;Interactor\u0026rdquo;. All of the game play logic is contained within the Interactor, which responds to events from the GUI and updates the Model. The View is composed of two major components; the game field containing the letter tiles, and the virtual keyboard.\nThis is all tied together by the Controller which instantiates everything and supplies Runnables and Consumers to the virtual keyboard to invoke methods in the Interactor. It also installs a Keyboard event handler on the entire View so that the user can use either the virtual keyboard or a real keyboard.\nLow Coupling # One of the key goals of any good application design is to reduce coupling as much as possible. In WordleFX, you\u0026rsquo;ll only find the following coupling:\nLayout Coupling Any layout is comprised of container classes (parents) which hold other Nodes (children) inside them. This kind of coupling is unavoidable, but its impact is reduced by ensuring that parents have as little knowledge of the nature of their children as possible. Virtually all child objects are returned from their builder functions as instances of Node or Region. Coupling To the Model The goal of MVC is have - as much as possible - the coupling happen exclusively with the Model and only the Model. Public Interactor Methods Every public method in a class adds to coupling but for the Interactor this is unavoidable, as the Controller needs to be able to call specific functions. Additionally, in pure MVC the Interactor functions would be inside the Controller. The MVCI structure physically isolates the business logic into the Interactor, which is an improvement in clarity, but then it needs to be exposed to the Controller in order to work. Other than the parent-\u0026gt;child relationships, every element of the GUI in WordleFX is completely independent and has no relationship, or knowledge of, any other element of the GUI.\nAn Example of How this Works # In WordleFX, the controller instantiates the Virtual Keyboard, supplying it Runnables and Consumers which will invoke methods in the Interactor in its constructor. The Virtual Keyboard needs these functional elements to handle the individual letter clicks, to handle the backspace key, and to process the guess when the \u0026lt;Enter\u0026gt; button is clicked. The only other dependency that the Virtual Keyboard needs is a map of LetterStatus keyed on each letter of the alphabet.\nWhat each of these functional elements does is not known to the Virtual Keyboard. It\u0026rsquo;s job is simply to invoke the appropriate Consumer or Runnable when each button is clicked. At the same time, the Controller has no knowledge of how the Virtual Keyboard will implement these functional elements.\nThen the Controller passes the Virtual Keyboard to the ViewBuilder in its constructor. The ViewBuilder accepts it as an instance of Region, and has no knowledge of its function or its true nature. As a Region it simply inserts it into it\u0026rsquo;s layout as a child.\nFinally, the ViewBuilder passes the View back as a Region. The Controller then installs a KeyEvent Listener on it, to handle the keystrokes from the physical keyboard. The View has no role in this other than to be a target for the Events which, in turn, have no need to know anything about the View.\nFrom this you can see that the main part of the View has absolutely nothing to do directly with any user input. All of the \u0026ldquo;Action\u0026rdquo; type of interactions are set up by the Controller.\nAs a matter of fact, you could remove the KeyEvent listener, and the application would run just fine - albeit without any physical keyboard input. Conversely, you could replace the Virtual Keyboard with an empty Pane, and the application would still work with physical keyboard input - however in this case you\u0026rsquo;d lose the clues in the colours of the Virtual Keyboard buttons.\nImplementation Notes # Tile Status # There are 5 possible states that a tile can be in:\nEmpty The tile has nothing in it. Unchecked A letter has been placed in the tile, but it has not been checked against the solution word Wrong A letter has been placed in the tile, it has been checked and that letter does not appear in the solution word Present A letter has been placed in the tile, it has been checked and found to be present in the word, but not in that position Correct A letter has been placed in the tile, it has been checked and found to match the letter in that place in the solution word These statuses have been created in an Enum called LetterStatus. Since the determination of Pseudo-Class colours is virtually 100% controlled by the value of a Node\u0026rsquo;s related LetterStatus, the code for updating PseudoClass based on LetterStatus has been included in the LetterStatus class.\nDoes this break some design rules, having GUI elements tied to an element in the Model? I\u0026rsquo;m not sure, but it avoids \u0026ldquo;feature envy\u0026rdquo; by having some other class performing logic based solely on the contents of LetterStatus.\nDark Mode # WordleFX supports the Wordle Dark mode, as well as the Light mode. Mostly this is just to demonstrate how sweeping changes to the look of the application can be made by organizing the CSS properly and using Pseudo Classes.\nDark mode looks like this:\nAll of the colours in the CSS are defined and named in the .root section of the CSS. The pseudo-class for \u0026ldquo;dark-mode\u0026rdquo; modifies those definitions:\n.root{ -white-colour: white; -wrong-colour: #787c7e; -text-fill-colour: black; -empty-border-colour: #d3d6da; -unlocked-border-colour: #787c7e; -background-colour: white; -key-background-colour: #d3d6da; -toast-background-colour: black; -toast-letter-colour: -white-colour; -present-colour: #c9b458; -correct-colour: #6aaa64; } .main-screen: dark-mode { -white-colour: #d7dadc; -wrong-colour: #3a3a3c; -text-fill-colour: -white-colour; -empty-border-colour: #3a3a3c; -unlocked-border-colour: #565758; -background-colour: #121213; -key-background-colour: #818384; -toast-background-colour: -white-colour; -toast-letter-colour: black; } The \u0026ldquo;main-screen\u0026rdquo; selector is applied to the VBox which contains the entirety of the window, and when the pseudo-class is applied to it the definitions of these colours will change for all of its child Nodes, thereby enabling Dark mode.\nThe trigger for the mode change is the \u0026ldquo;Gear\u0026rdquo; icon in the top right corner of the screen.\nIkonli FontAwesome # WordleFX uses the Ikonli library for integrating FontAwesome into JavaFX to handle icons. This library provides a class FontIcon which you can use just like Text in an application. In WordleFX, the \u0026ldquo;gear\u0026rdquo; icon in the top right corner of the screen, and the \u0026ldquo;backspace\u0026rdquo; icon on the Virtual Keyboard are FontIcon.\nAnimations # All of the animation code is contained in a separate file of package level functions called \u0026ldquo;WordleAnimations.kt\u0026rdquo;. All of these functions simply accept a Node as a parameter, and would work equally on any kind of Node passed to them.\nAll of these animations require multiple steps to complete, and most of them use SequentialTransition to chain several Transitions one after the other.\nFlipping Tiles # In Wordle, the tiles flip one after the other, possibly with each tile starting to flip before the previous tile has finished (this is how it\u0026rsquo;s implemented in WordleFX).\nHow do we keep each tile independent but still have the tiles flip sequentially?\nThe answer is, they aren\u0026rsquo;t really flipping sequentially! Each one has a delay, courtesy of PauseTransition which is run at the beginning of the sequence. The length of the delay is based on the Column property of the LetterModel which is installed on the tile. So all of the flip sequences are running virtually in parallel, but the first step in each one is a pause which varies from 0 to 2000 milliseconds.\nHere\u0026rsquo;s the code:\nfun flipTile(node: Node, column: Int, letterStatus: LetterStatus) { SequentialTransition(PauseTransition(Duration(column * delay)), RotateTransition(Duration.millis(speed), node).apply { byAngle = 90.0 axis = Rotate.X_AXIS setOnFinished { LetterStatus.updatePseudoClass(node, letterStatus) } }, RotateTransition(Duration.millis(speed), node).apply { byAngle = -90.0 axis = Rotate.X_AXIS }).play() } The other thing to notice about this is that the call to update the PseudoClass happens in the EventHandler at the end of the first RotateTransition. So, in Light mode, the tile is white until it rotates 90 degrees down when it\u0026rsquo;s essentially edge on and invisible, the colour changes, and it rotates back up again. The colour change makes it look like it\u0026rsquo;s doing a single 180 degree rotation.\nWiggling Invalid Words # The animation itself is fairly straight-forward. Move the Node 5 pixels to the left, run repeated TranslateTransition to move it 10 pixels to the right and back, and then put it back where it started at the end of the TranslateTransition:\nfun wiggleRow(node: Node) { node.translateX = -5.0 TranslateTransition(Duration.millis(50.0), node).apply { byX = 10.0 cycleCount = 6 isAutoReverse = true setOnFinished { node.translateX = 0.0 } play() } } The big question about this was how to trigger it in a Reactive manner. All of the tiles are independent, but the wiggle needs to happen based on the entire row, and triggered when the Interactor decides the word is invalid.\nIn truth, the animation doesn\u0026rsquo;t care if the tiles are wiggled together or in a group. The effect would be the same if 5 transitions (one per tile) were triggered in a millisecond of each other, or if the whole row is wiggled as one Node.\nThe real challenge was to implement it in State, so that it would work.\nOriginally, the tiles were implemented on the screen in a GridPane. That would have meant adding a new component to the LetterModel to make each tile aware of what row it was on (column was added to LetterModel to facilitate the flip delay). However, since the tiles were all identical in shape, there really wasn\u0026rsquo;t any need to have them in a GridPane, and it was just as effective to put them in HBoxes, all contained in a VBox. This meant that there was now a screen Node that corresponded to a single guessed word.\nNow, how to trigger the wiggle?\nFirst Implementation # Since you can only have one active guess at a time, and you can move on to the next while the current word is invalid, I decided to implement an IntegerProperty in the Model called InvalidRow. Originally, it would be set to 99, which wouldn\u0026rsquo;t correspond to any row on the screen. Then it would be changed to the current row if the word was found to be invalid.\nThe HBox for the word row looked like this:\nprivate fun createRow(row: Int) = HBox(5.0).apply { alignment = Pos.CENTER children += IntRange(0, 4).map { letterBox(model.letters[row][it]) } model.invalidRowProperty().addListener { _ -\u0026gt; if (model.invalidRow == row) { wiggleRow(this) } } } This felt really kludgey. But on the other hand, it seemed very direct and simple to understand.\nThere was one problem, however\u0026hellip;\nWhat if the word was invalid the second time that it was checked?\nIn this case, the InvalidRow property would already be set to the current row, and the InvalidationListener wouldn\u0026rsquo;t be triggered. It was possible to get around it though.\nHere\u0026rsquo;s the code from the Interactor that handled word validity:\nif (data.isWordValid(guess.map(LetterModel::letter))) { . . . } else { model.invalidRow = 99 model.invalidRow = model.currentRow } This worked, but really set off alarm bells. Essentially what this code does is treat a State element like a trigger, instead of a representation of \u0026ldquo;state\u0026rdquo;. The invalid row didn\u0026rsquo;t actually change. Also, there\u0026rsquo;s no guarantee that JavaFX isn\u0026rsquo;t going to detect that value wasn\u0026rsquo;t really changing, and not trigger the invalidation.\nA Small Improvement # It made a little more sense like this:\nmodel.invalidRow = 99 if (data.isWordValid(guess.map(LetterModel::letter))) { . . . } else { model.invalidRow = model.currentRow } And you could argue that before the word is actually checked it\u0026rsquo;s not invalid and the change to State reflects this.\nBut it still seemed round-about and kludgey.\nThe Final Version # A more direct way makes sense, even though it complicates the Model a little bit. The Model was changed to remove invalidRow and replace it with this:\nval wordValidity = List(6) { _ -\u0026gt; SimpleBooleanProperty(true) } val wordsValid: BooleanBinding = run { var results = SimpleBooleanProperty(true).and(wordValidity[0]) wordValidity.forEach { results = results.and(it) } results } The BooleanBinding that does an and on each of the elements of wordValidity is needed because the toast display of the error message when an invalid word is checked needs to know if any of the words is invalid. It will now listen to wordsValid.\nThe word checking part of the Interactor isn\u0026rsquo;t really that different, it now just updates the wordValidity element that corresponds to the row being checked:\nmodel.wordValidity[model.currentRow].set(true) val guess = model.letters[model.currentRow] if (data.isWordValid(guess.map(LetterModel::letter))) { performCheck(guess) model.currentRow++ model.currentColumn = 0 setAlphabet() } else { model.wordValidity[model.currentRow].set(false) } In the Model, this feels like more work, but in terms of the Interactor and the View, it\u0026rsquo;s now much more direct and easy to understand.\nChecking Words # Wordle has two lists, one is a list of candidate words from which today\u0026rsquo;s word will be selected. The second is a list of every five letter word in the English language that isn\u0026rsquo;t a candidate word. Presumably the candidate words were cherry picked to be more common and therefore fair to the average player. It\u0026rsquo;s hard to imagine anyone actually guessing a word like \u0026ldquo;zanze\u0026rdquo;. However, any word from either list is considered a \u0026ldquo;valid\u0026rdquo; word, and can be entered as a guess.\nThe JavaScript for Wordle simply has both lists hard-coded into the source code. WordleFX has them split out into text files in the project resources. They\u0026rsquo;re read when the application is started, and stored as lists.\nThe code for determining the result for each letter in each guess is the most complicated part of the application. This is because if a guess contains repetitions of a letter which appears in the solution fewer times than in the guess, it only gets one \u0026ldquo;Correct\u0026rdquo; or \u0026ldquo;Present\u0026rdquo; result for each time the letter appears in the solution.\nFor instance, if the solution is \u0026ldquo;SCRAM\u0026rdquo; and the user enters \u0026ldquo;MUMMS\u0026rdquo; as a guess. Then only the first \u0026ldquo;M\u0026rdquo; should get a \u0026ldquo;Present\u0026rdquo; result, and the second and third should get \u0026ldquo;Wrong\u0026rdquo;. If the solution is \u0026ldquo;GUMMY\u0026rdquo; for the same guess, then the second and third \u0026ldquo;M\u0026rdquo; should get \u0026ldquo;Correct\u0026rdquo;, and the first should get \u0026ldquo;Wrong\u0026rdquo;.\nLet\u0026rsquo;s look at the code for checking a word:\nprivate fun performCheck(guess: List\u0026lt;LetterModel\u0026gt;) { val presentLetters = model.word.filterIndexed { index, c -\u0026gt; guess[index].letter != c } as MutableList\u0026lt;Char\u0026gt; model.wordGuessed = (presentLetters.size == 0) guess.forEachIndexed { column, letterModel -\u0026gt; val letter = letterModel.letter letterModel.status = LetterStatus.WRONG if (model.word[column] == letter) { letterModel.status = LetterStatus.CORRECT } else if (presentLetters.contains(letter)) { letterModel.status = LetterStatus.PRESENT presentLetters.remove(letter) } } } The MutableList, presentLetters is created by filtering out every letter in the solution that has been guessed correctly. This leaves only letters which are candidates for the \u0026ldquo;Present\u0026rdquo; result. If presentLetters is empty, then it means that every letter in the solution has been guessed correctly and the game is over.\nNow, for each letter in the guess, we check to see if it\u0026rsquo;s a match against the corresponding letter in the solution and give it a \u0026ldquo;Correct\u0026rdquo; status if it is. If it\u0026rsquo;s not, then we only need to check to see if it\u0026rsquo;s in the presentLetters list. If it is, then we mark it as \u0026ldquo;Present\u0026rdquo; and the delete that letter from presentLetters. This way, we can only get one \u0026ldquo;Present\u0026rdquo; status for each occurance of a letter in the solution.\n","date":"29 January 2022","externalUrl":null,"permalink":"/javafx/projects/wordlefx/","section":"JavaFX","summary":"The original Wordle is JavaScript in a browser, here’s an implementation of it in Reactive JavaFX written in Kotlin.","title":"Wordle in Kotlin JavaFX","type":"javafx"},{"content":"Content under construction\n","date":"24 January 2022","externalUrl":null,"permalink":"/javafx/elements/stylesheets/","section":"JavaFX","summary":"Content under construction\n","title":"Stylesheets and PseudoClasses","type":"javafx"},{"content":"Kotlin has been described as, \u0026ldquo;The language that Java would have been if it had been designed 25 years later\u0026rdquo;. It\u0026rsquo;s starting to pick up popularity, and has had a boost from being endorsed by Google for Android development.\nBut you\u0026rsquo;re not an Android developer, so should you learn, and use, Kotlin?\nIf you are a Java programmer and \u0026hellip; # You\u0026rsquo;re fed up with Java boilerplate and verbose syntax You think that Lambdas are the greatest thing since Generics You think that Streams are the greatest thing since Lambdas You think that Optional is cool, but at the same time awkward and clumsy You want to move towards more functional programming Then Kotlin is probably for you. And if you\u0026rsquo;re a Java programmer who isn\u0026rsquo;t all of those things above, then maybe Kotlin would be a good way to expand your horizons and learn about new ways to program.\nIt\u0026rsquo;s Not a Steep Learning Curve # Kotlin is very closely related to Java, and there\u0026rsquo;s almost nothing that you can do in Kotlin that you couldn\u0026rsquo;t do in Java as well. So this means that the concepts which are central to Kotlin aren\u0026rsquo;t going to be all that foreign to you if you come from Java. This is especially true if you\u0026rsquo;ve really become comfortable with lambdas, streams and optional; as all of these concepts are central to Kotlin. Unlike Java, where these features were added on decades after the language was invented, Kotlin was designed with them as an integral, integrated part from the very beginning.\nIt probably takes just a couple of hours to learn enough about Kotlin to be able to read and understand most of the Kotlin code you\u0026rsquo;re likely to encounter in real life. And it seems to take just a few days of working in Kotlin to became comfortable writing new code in Kotlin.\nIf you are using Intellij Idea as your IDE, there\u0026rsquo;s even a menu item to convert your Java classes to Kotlin. That\u0026rsquo;s a great start. It doesn\u0026rsquo;t produce great Kotlin code, and it definitely looks like Java code written Kotlin, but it doesn\u0026rsquo;t take too much effort to polish up the converted code so that it looks a lot more like it was written in Kotlin from the start.\nI\u0026rsquo;ve found that you can just take all of the concepts and approaches you\u0026rsquo;d use in Java and just write them in Kotlin and you\u0026rsquo;ll get a working program. Then if you go over your code, looking for the bits that seem awkward and clumsy, you can refactor them by researching the concepts with web searches until they look natural.\nWhat\u0026rsquo;s so Great about Kotlin, Anyways? # Less Code is Better Code # Over decades of programming I\u0026rsquo;ve come to understand that, all other things being equal, the less code you have to write, the better. Why?\nThere\u0026rsquo;s less code to read, so less code to understand. There\u0026rsquo;s less places for a bug to hide in. Only the bits that make your code unique need to be written. Kotlin helps you to write less code. It strips away a LOT of the extra coding that you need to do with Java that, to be honest, no one reads anyways.\nLet\u0026rsquo;s Look at Fields as an Example # In Java we have Fields (or \u0026ldquo;Instance Variables\u0026rdquo;, if you like) in our classes. But we are taught (correctly) that fields are part of the private inner workings of a class and should not be exposed to the world. Exposing them would tie our inner class design down to those client classes that are peeking into our class and making decisions based on its internal design.\nSo we make all of our fields private, and we create two public methods for each field, a getter and a setter. These methods are named following a convention which puts the word \u0026ldquo;get\u0026rdquo; or \u0026ldquo;set\u0026rdquo; in front of the field name, with the first letter capitalized. Typically, it would look like this:\n{% gist a55821e44ddb909e290a05491bfaad4f %}\nShould you decide to change the way that intField is stored, maybe store it as a String, you can keep the external interface constant by providing the conversion operations as part of the getter and setter for that field.\nNow, Kotlin was developed knowing that this is the best way to share your class\u0026rsquo;s data without exposing its inner structure to the outside world. So Kotlin doesn\u0026rsquo;t have \u0026ldquo;Fields\u0026rdquo;, but it has \u0026ldquo;Properties\u0026rdquo;. Think of a Property as an object wrapper around a field which has its own getter and setter. Kotlin automatically creates the class level getter and setter for the field, delegating them to the getter and setter of the encapsulating property. The default getter and setter for a property just do what you would expect them to do.\n{% gist aee9580aad1e0d3a7347eb742f82ddda %}\nYou can see that it takes just 1 line of code to do what takes about 8 in Java.\nBut it gets better! No one would actually write Kotlin code like that.\nIn your Kotlin code, you can replace direct calls to the getter and setter with direct references to the property. It will then invoke the appropriate getter or setter for you. Additionally, the setter is defined as an operator function, so you can just put the property reference on the left side of an \u0026ldquo;=\u0026rdquo; operator and it will turn it into a call to the setter for you. Like this:\n{% gist f0df4dbfb6a5835e093c2983223fb394 %}\nAs a Java programmer this looks scary! It looks like it\u0026rsquo;s doing exactly what we were all taught to never, never, ever do - expose the field and directly access it from an external class. But it\u0026rsquo;s not. It\u0026rsquo;s following all of the rules, to the letter, but just hiding all of the boilerplate so that it doesn\u0026rsquo;t get in the way.\nThe ironic thing is that the resulting code looks exactly the way you would have written your Java before somebody told you not to!\nMutable, Immutable and Nullable # Variables in Kotlin are intialized with the \u0026ldquo;val\u0026rdquo; or \u0026ldquo;var\u0026rdquo; keyword and only allowed to be null when explicitly declared as nullable (with a \u0026ldquo;?\u0026rdquo; at the end of the type). Variables declared with the \u0026ldquo;val\u0026rdquo; keyword are essentially the same as \u0026ldquo;final\u0026rdquo; variables in Java.\nKotlin does everything it can to encourage you to use \u0026ldquo;val\u0026rdquo; over \u0026ldquo;var\u0026rdquo; whenever possible. So much so that \u0026ldquo;var\u0026rdquo; could be considered to be a \u0026ldquo;code smell\u0026rdquo; in Kotlin. What\u0026rsquo;s the big deal?\nUsing vals in your code makes you think about alternative, immutable, functional code. […] Removing vars leads to refactoring. The refactoring leads to new coding patterns. New coding patterns leads to a shift in your approach to programming. This shift in approach leads to transformative code that has fewer defects and is easier to maintain.\n— Beginning Scala, David Pollack\nI have found this to be true. When I look at a block of code and think, \u0026ldquo;This seems clumsy\u0026hellip;wrong\u0026rdquo;, it\u0026rsquo;s usually got a \u0026ldquo;var\u0026rdquo; in it somewhere. Flipping it around and finding an approach that solves the problem without using \u0026ldquo;var\u0026rdquo; often results in something that is elegant and clean compared to my original code.\nNullable variables are everything that Optional in Java should be \u0026hellip; without the clumsiness of Optional. It\u0026rsquo;s an integral part of the language that you need to master, not an add-on capability that few people understand properly.\nGenerally speaking a variable gets a null value in one of two ways, it\u0026rsquo;s a var initialized to null which never gets updated to a real value, or it\u0026rsquo;s a val instantiated via a function that returns a nullable value. It\u0026rsquo;s declared with a \u0026ldquo;?\u0026rdquo; at the end of the class name, like \u0026ldquo;String?\u0026rdquo;, or \u0026ldquo;Int?\u0026rdquo; or \u0026ldquo;CustomClass?\u0026rdquo;. Just like Optional, to get that value out of the variable, you need to provide a way for your program to deal with possibility that there\u0026rsquo;s nothing there.\n{% gist 5059ba75f5b94a77e2ea406f0814f24d %}\nThe assumption is that the external database function returns a nullable Customer value. Both functions do the same thing. The second shows how the val variables can actually collapse into just function calls.\nThe \u0026ldquo;Elvis Operator\u0026rdquo; (\u0026quot;?:\u0026quot;) is used like the orElse() method of Optional. If you want to do something like Optional\u0026rsquo;s map() function, you use the \u0026ldquo;safe operator\u0026rdquo; (\u0026quot;?.\u0026quot;) and then invoke some method of the variable. In the first function, let is an external \u0026ldquo;scope\u0026rdquo; function which takes a lambda argument, passing the value to it as it.\nKotlin also has an !! operator, which works in the same way as Optional.get(). Using !! is generally considered an anti-pattern, and is the only way you can get an NPE with Kotlin.\nScope Functions # How many times have you instantiated a variable, just to perform some configuration on it by calling it\u0026rsquo;s methods, and then passing it on to some other class as a parameter? Something like this:\n{% gist fdbc1af6c4b89a7bf1ac17342961d67e %}\nBut Kotlin has an apply scope function which takes a lambda which receives the value as this and returns this. Inside the scope function, you can treat the this as implied when it makes sense. So that code above becomes:\n{% gist b05ceb242a9f98d350404bf6d07dc752 %}\nSo now we don\u0026rsquo;t even need a variable reference to the object at all, we can just pass the results of the constructor through the apply and directly off to another object\u0026rsquo;s method. In a way, you can use the apply to turn a class\u0026rsquo;s configuration methods into decorators.\nThere are 5 different kinds of scope functions in Kotlin. You can read about them here .\nBy the way, if the multi-line lambda breaks up the flow of your code a bit much, you can very easily turn it into a named function in Kotlin:\n{% gist 1029617ae1412e59a021ab82584942ba %}\nThere\u0026rsquo;s a LOT More # Any attempt to list all of the features of Kotlin, and how they improve upon kludgy Java implementations would just result in a TLDR blog. What you really need to know, though, is the Kotlin takes virtually all of the modern concepts added to Java and integrates them right into the language so that they feel natural and spread everywhere.\nLambdas are a really good example of this. You can get away without lambdas in Java, but you couldn\u0026rsquo;t write much of anything in Kotlin without them. They\u0026rsquo;re a crucial concept to the language, and using them in Kotlin feels completely natural.\nWhat Are the Downsides to Using Kotlin? # From a technical perspective, there are very few. You can freely co-mingle Java and Kotlin in you projects, and you can call any Java library from Kotlin. With a very small amount of care, any of your Kotlin code can be called from Java, and you can use any Kotlin libraries with Java.\nKotlin compiles to run in the JVM, and you can tell the Kotlin compiler to generate code compatible with whatever version of the JVM you want. So it\u0026rsquo;s easy to maintain compatibility between your Java and your Kotlin code in your project.\nAlthough Kotlin has been around for almost a decade now, it\u0026rsquo;s nowhere near as old as Java, and doesn\u0026rsquo;t have the wealth of resources that you are going to find with Java. The programmer community is smaller, so there just aren\u0026rsquo;t as many people to answer your questions on sites like StackOverflow.com, and you\u0026rsquo;re less likely to find that somebody has already asked your question so the answer is just there waiting for you.\nAnother thing you\u0026rsquo;ll need to deal with is that, even though Kotlin can use just about any Java library you might find, virtually all of the examples and the documentation are going to be in Java. You\u0026rsquo;ll probably also find that any questions and answers you find on sites like StackOverflow.com are going to be in Java, too.\nIt\u0026rsquo;s also possible that some of the tooling that you\u0026rsquo;re using right now with Java can\u0026rsquo;t be used with Kotlin, and you may not be able to find a Kotlin compatible replacement. This will undoubtedly improve over time.\nIf you are working on a preexisting code-base with a lot of old Java code, there are going to be practical issues around converting everything to Kotlin. Especially in the absence of a comprehensive test suite. But if you\u0026rsquo;re opening up a class for refactoring or modification, you might have the opportunity to convert it to Kotlin in the process. The resulting code will almost certainly be smaller, cleaner and easier to extend and maintain.\nA Final Thought # One thing about learning Kotlin, though; it will change you. A phrase that you\u0026rsquo;ll hear a lot is \u0026ldquo;idiomatic Kotlin\u0026rdquo;, which means the programming equivalent of \u0026ldquo;speaking like a native\u0026rdquo;. You don\u0026rsquo;t hear about \u0026ldquo;idomatic Python\u0026rdquo; or \u0026ldquo;idomatic BASIC\u0026rdquo; too much, but it seems like \u0026ldquo;idiomatic Kotlin\u0026rdquo; is a key concept of learning the language.\nThere\u0026rsquo;s something about Kotlin, maybe its focus on fixing the bad parts of Java, that makes your bad Kotlin code stick out like a sore thumb. You see it, and you can\u0026rsquo;t help but think, \u0026ldquo;That just doesn\u0026rsquo;t look right!\u0026rdquo;. And, generally speaking, fixing that bad code means writing it in a more idiomatic Kotlin way.\nDo that often enough, and that idiomatic Kotlin way becomes your way to write code, and it changes how you approach writing code.\nAnd then you have to go back to Java to work on some legacy code\u0026hellip;\n","date":"11 December 2021","externalUrl":null,"permalink":"/kotlin/2021-12-11-kotlin_should_you/","section":"Kotlin","summary":"Kotlin has been described as, “The language that Java would have been if it had been designed 25 years later”. It’s starting to pick up popularity, and has had a boost from being endorsed by Google for Android development.\n","title":"Kotlin - Should You?","type":"kotlin"},{"content":"Way, way back in 2014 when I started learning JavaFX, I tried using Scene Builder and FXML and found it to be an amazing waste of time and effort. One of the best decisions I ever made was to abandon it early on, and just code screens by hand. I advise everyone else to do the same.\nThere seems to be some confusion as to whether you can even hand code a screen in JavaFX, not to mention how you would go about doing it. This is probably because virtually all of the beginners\u0026rsquo; tutorials available make the assumption that \u0026ldquo;drag and drop\u0026rdquo; is only method that\u0026rsquo;s going to be palatable to new learners.\nIn this article I\u0026rsquo;m going to build a complete screen, bound to a data model, that accepts input and works. It will be fully configured and styled. You\u0026rsquo;ll see how just applying some of the basic concepts of Clean Coding will guide the design to produce an amazing amount of functionality in a small amount of code that\u0026rsquo;s takes very little time to write.\nYou\u0026rsquo;ll see that the end result is simple and easy to read and understand, and should be equally easy to maintain and expand.\nThe Beginning # We are going to build an application from the start. So we\u0026rsquo;ll begin with the usual main class that extends Application, and it will look something like this:\n{% highlight java %} public class NoFxmlExample extends Application {\npublic static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Region sceneRoot = new ScreenBuilder().build(); Scene scene = new Scene(sceneRoot); primaryStage.setScene(scene); scene.getStylesheets().add(\u0026quot;/css/default.css\u0026quot;); primaryStage.show(); } }\n{% endhighlight %}\nNow, most of that is boilerplate, and the only thing you really need to work out to actually build the screen is to create sceneRoot. I\u0026rsquo;ve defined it as a Region, and that\u0026rsquo;s a good starting place, because anything that extends Region is going to work as the root of a Scene. Examples of Regions are BorderPane, ScrollPane, AnchorPane, and StackPane as well as VBox and HBox. Your region can be any one of those, plus a few others I haven\u0026rsquo;t listed.\nOne thing that we\u0026rsquo;ll get back to later is the stylesheet. It\u0026rsquo;s good practice to put your styling into a stylesheet and then use selectors to apply it to various nodes in your application. I have a \u0026ldquo;default\u0026rdquo; stylesheet I use for a lot of projects, so I just went and added it to the Scene here.\nA general rule of thumb is that you should only extend classes that you are going to add new functionality to. If you\u0026rsquo;re just configuring them, then you should use a builder class - which is what we are doing here. Personally, I like to use the Builder interface, because it\u0026rsquo;s built-in, obvious and works well. That\u0026rsquo;s what the ScreenBuilder class is, it\u0026rsquo;s an implementation of Builder\u0026lt;Region\u0026gt;.\nSo let\u0026rsquo;s get to that.\nThe ScreenBuilder Class # We\u0026rsquo;ll start with just the bare essentials, to get to something that will compile and run as quickly as possible. We want to be able to see our screen as we build it out, so it needs to run right from the get-go.\nBecause it\u0026rsquo;s an excellent all-round start for a screen that has a basic structure, I\u0026rsquo;ve chosen to use a BorderPane:\n{% highlight java %} public class ScreenBuilder implements Builder {\n@Override public Region build() { BorderPane results = new BorderPane(); results.setTop(createTop()); return results; } private Node createTop() { return new Text(\u0026quot;This is the Screen Title\u0026quot;); } } {% endhighlight %} It looks like this:\nNot pretty, but it\u0026rsquo;s a start. The key point is that now you can see what you\u0026rsquo;ve built and exactly how it will look on the screen. In about 6 lines of code!\nSome Notes About Code Structure and Organization # I\u0026rsquo;ve started this out the way that I would structure most screen builders. The build() method instantiates the container - in this case a BorderPane - and then each of the sections is built in its own method. These methods all return something generic, like a Node or a Region, and not the actual class of the object being returned. This is for two reasons:\nThe calling method should never reach inside the returned value to fiddle with it. All of the configuration should go inside that creation method. It\u0026rsquo;s easier to change it later, or to wrap another container around the original returned value. We\u0026rsquo;ll see this in action later on. By the same token, the creation method should never attempt to reach up to the container to tinker with the layout.\nAdding Some Styling to the Heading # To illustrate how the design grows, and where the code belongs, I wrapped the Text in a HBox with a border and made the text bigger. When that was done, it was clear that everything was cramped together too much. The Text was too close to the border, and the border was too close to the edge of the screen. So some space was added. The code now looks like this:\n{% highlight java %}hangman: /assets/images/Hangman.png public class ScreenBuilder implements Builder {\n@Override public Region build() { BorderPane results = new BorderPane(); Node headingBox = createHeadingBox(); BorderPane.setMargin(headingBox, new Insets(4)); results.setTop(headingBox); return results; } private Node createHeadingBox() { HBox results = new HBox(); Text headingText = new Text(\u0026quot;This is the Screen Title\u0026quot;); headingText.getStyleClass().add(\u0026quot;heading-text\u0026quot;); results.getStyleClass().add(\u0026quot;standard-border\u0026quot;); results.getChildren().add(headingText); results.setPadding(new Insets(6)); return results; } } {% endhighlight %}\nThere are some things you should notice:\nThe creation method for the heading has now been renamed to something more descriptive. The creation method for the heading now returns an HBox, instead of a Text, yet the signature of the method did not need to be changed. The styling for the Text and the HBox are done in the creation method. The internal spacing for the HBox (the padding) is done in the creation method. The external spacing for the heading Node is done in the build() method, as this is an element of the layout of the BorderPane. Just to be really, really clear about this. No way, no how, should the line:\n{% highlight java %} BorderPane.setMargin(headingBox, new Insets(4)); {% endhighlight %} go inside the createHeadingBox() method.\nThe createHeadingBox() method has no knowledge that it\u0026rsquo;s return value is going to be placed into a BorderPane. So it cannot do this.\nAlso, since the createHeadingBox() method returns a Node, there\u0026rsquo;s no way that build() can call setPadding() on it.\nLayout vs Configuration # An important concept when writing clean JavaFX code is to understand the difference between \u0026ldquo;Layout\u0026rdquo; and \u0026ldquo;Configuration\u0026rdquo;.\nConfiguration # Configuration is the setting of properties and behaviours of a Node. This can include things like styling, scaling, setting minimum or maximum height and width, visibility, and binding to properties in a Model.\nLayout # Layout is the population of a Region with child Nodes and determining their position and bounds, as well as sizing parameters in relation to other elements of the layout.\nIt\u0026rsquo;s possible, even probable, that a Region will be defined in your code with both Configuration and Layout. We have that so far in our example. The createHeadingBox() method configures the styling for an HBox and then defines it\u0026rsquo;s layout.\nYou will find that Configuration tends to be less specific to a particular screen than layout. For instance, you\u0026rsquo;ll create a styling for Text that works in a particular context, and then you\u0026rsquo;ll want to re-use that styling throughout a screen, or throughout an application, or even across multiple applications. Once again, this is an important point, as we\u0026rsquo;ll see in a little bit.\nAdding A Data Entry Section # We\u0026rsquo;re going bring these ideas together now by adding a GridPane with some prompts and data entry fields. It\u0026rsquo;s going to go in the centre of the BorderPane:\n{% highlight java %} public class ScreenBuilder implements Builder {\n@Override public Region build() { BorderPane results = new BorderPane(); Node headingBox = createHeadingBox(); BorderPane.setMargin(headingBox, new Insets(4)); results.setTop(headingBox); results.setCenter(createDataEntrySection()); return results; } private Node createHeadingBox() { HBox results = new HBox(); Text headingText = new Text(\u0026quot;This is the Screen Title\u0026quot;); headingText.getStyleClass().add(\u0026quot;heading-text\u0026quot;); results.getStyleClass().add(\u0026quot;standard-border\u0026quot;); results.getChildren().add(headingText); results.setPadding(new Insets(6)); return results; } private Node createDataEntrySection() { GridPane results = createTwoColumnGridPane(); results.add(createPromptText(\u0026quot;First Name:\u0026quot;), 0, 0); results.add(createInputField(), 1, 0); results.add(createPromptText(\u0026quot;Last Name:\u0026quot;), 0, 1); results.add(createInputField(), 1, 1); results.add(createPromptText(\u0026quot;Email:\u0026quot;), 0, 2); results.add(createInputField(), 1, 2); results.add(createPromptText(\u0026quot;Phone Number:\u0026quot;), 0, 3); results.add(createInputField(), 1, 3); return results; } private GridPane createTwoColumnGridPane() { GridPane results = new GridPane(); results.getColumnConstraints().addAll(createJustifiedConstraint(HPos.RIGHT), createJustifiedConstraint(HPos.LEFT)); results.setHgap(6); results.setVgap(4); results.setPadding(new Insets(4)); results.getStyleClass().add(\u0026quot;test-border\u0026quot;); return results; } private ColumnConstraints createJustifiedConstraint(HPos alignment) { ColumnConstraints results = new ColumnConstraints(); results.setHalignment(alignment); return results; } private Node createInputField() { TextField results = new TextField(); results.setMaxWidth(100); return results; } private Node createPromptText(String prompt) { Text results = new Text(prompt); results.getStyleClass().add(\u0026quot;label-text\u0026quot;); return results; } } {% endhighlight %} Now it looks like this:\nLet\u0026rsquo;s look at what\u0026rsquo;s been done here. First, a new call was added to the main layout section to populate the Centre region of the BorderPane through the createDataEntrySection() method. That new method creates a GridPane with two columns, and then populates the left column of the GridPane with prompt Texts, and the right column with TextFields.\nNotice that the Configuration has been completely split away from the Layout. It might be argued that most of the \u0026ldquo;configuration\u0026rdquo; of a GridPane has to do with defining how the layout works, but the important thing is that contents of the GridPane and their positioning in the GridPane are isolated from everything else. That makes it easy to see how the layout works. To that end, all of the configuration of the Texts and TextFields is handled by separate methods.\nI\u0026rsquo;ve also added a \u0026ldquo;test-border\u0026rdquo; styling to the GridPane that will be removed later. One of the things that can be challenging with JavaFX is understanding which region owns the whitespace on the screen. Putting a temporary border around regions is a great way to understand what\u0026rsquo;s really going on.\nUsing the DRY Principle # There is a lot of boilerplate code in JavaFX.\nThe key to writing good JavaFX code is to aggressively apply the DRY (Don\u0026rsquo;t Repeat Yourself) Principle whenever you can. The layout of that GridPane would be agonizing to read if the boilerplate for the Texts was repeated inside code, like this:\n{% highlight java %} Text firstNamePrompt = new Text(\u0026ldquo;First Name:\u0026rdquo;); firstNamePrompt.getStyleClass().add(\u0026ldquo;label-text\u0026rdquo;); results.add(firstNamePrompt, 0,0); {% endhighlight %}\nThat\u0026rsquo;s going to add up to 8 extra lines of code in a method that only has 10 lines to start with! Not to mention another 8 lines for the TextFields if they\u0026rsquo;re all defined in-line as well.\nThis concept is carried over to the creation of the ColumnConstraints for the GridPane as well. It\u0026rsquo;s just two lines of code each time, but the single line of code that\u0026rsquo;s used instead has the advantage of having the method name describing what the ColumnConstraints are for, so it\u0026rsquo;s even easier to read.\nAdding Some More Data Entry # We\u0026rsquo;re going to add a TextArea to the screen, to show how the screen design grows and the code is morphed to accommodate it. Rather than repeat the whole class, I\u0026rsquo;ll just show the changed code:\n{% highlight java %} private Node createDataEntrySection() { HBox results = new HBox(10); results.setPadding(new Insets(2, 10, 4, 2)); results.getChildren().addAll(createGridPane(), createBioBox()); return results;Ugly But It\u0026rsquo;s Something }\nprivate Node createBioBox() { TextArea bioTextArea = new TextArea(); bioTextArea.setWrapText(true); bioTextArea.setMaxWidth(200); bioTextArea.setMaxHeight(200); return new VBox(4, createPromptText(\u0026quot;Short Bio:\u0026quot;), bioTextArea); } {% endhighlight %} Now it looks like this:\nThe old createDataEntrySection() was renamed to createGridPane() and a new createDataEntrySection() was written. Essentially, this \u0026ldquo;pushes\u0026rdquo; the GridPane down one level in the container structure, as it\u0026rsquo;s now contained in a new HBox which comprises the BorderPane's centre region.\nThe \u0026ldquo;BioBox\u0026rdquo; area is just a VBox with a Text and a TextArea. There\u0026rsquo;s no particular configuration to the VBox, so there no need to even instantiate it into a variable. Just create it and return it. In my opinion, the layout is so simple that there\u0026rsquo;s no need to split it apart from the configuration of the TextArea\u0026hellip;.yet.\nAdding a \u0026ldquo;Save\u0026rdquo; Button and Linking to the Outside World # We have this \u0026ldquo;data entry\u0026rdquo; section, but the data isn\u0026rsquo;t going anywhere. So we need to add a Model to get the information out of the screen. This article isn\u0026rsquo;t about MVC, so I won\u0026rsquo;t go into much detail about this, but you should be able to see how it works.\nHere\u0026rsquo;s a Model:\n{% highlight java %} public class ExampleModel {\nprivate final StringProperty firstName = new SimpleStringProperty(\u0026quot;Alloysius\u0026quot;); private final StringProperty LastName = new SimpleStringProperty(\u0026quot;Smith\u0026quot;); private final StringProperty email = new SimpleStringProperty(\u0026quot;a_smith@reallyexpensiveisp.com\u0026quot;); private final StringProperty phone = new SimpleStringProperty(\u0026quot;(999)888-7777\u0026quot;); private StringProperty bio = new SimpleStringProperty(\u0026quot;This is the life story of Alloysius Smith\\n\\nHe was born a while ago\\n\u0026quot;); public StringProperty firstNameProperty() { return firstName; } public StringProperty lastNameProperty() { return LastName; } public StringProperty emailProperty() { return email; } public StringProperty phoneProperty() { return phone; } public StringProperty bioProperty() { return bio; } } {% endhighlight %}\nI\u0026rsquo;ve put some default values into the properties of the Model so that we can see how the populated data looks on the screen without having to type it in each time.\nI\u0026rsquo;m not going to do anything with it other than bindings, so I haven\u0026rsquo;t included any getters or setters for the fields, just the \u0026ldquo;\u0026hellip;Property()\u0026rdquo; methods. It\u0026rsquo;s going to be passed to the ScreenBuilder class in its constructor along with a Runnable to handle the \u0026ldquo;Save\u0026rdquo; action (which isn\u0026rsquo;t going to actually do anything in this example):\n{% highlight java %} public class NoFxmlExample extends Application {\npublic static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Region sceneRoot = new ScreenBuilder(new ExampleModel(), () -\u0026gt; { }).build(); Scene scene = new Scene(sceneRoot); primaryStage.setScene(scene); scene.getStylesheets().add(\u0026quot;/css/default.css\u0026quot;); primaryStage.show(); } } {% endhighlight %}\nNow, lets link up the Model and add the Button:\n{% highlight java %} public class ScreenBuilder implements Builder {\nprivate ExampleModel model; private Runnable saveAction; public ScreenBuilder(ExampleModel model, Runnable saveAction) { this.model = model; this.saveAction = saveAction; } public Region build() { BorderPane results = new BorderPane(); Node headingBox = createHeadingBox(); BorderPane.setMargin(headingBox, new Insets(4)); results.setTop(headingBox); results.setCenter(createDataEntrySection()); results.setBottom(createButtonBox()); return results; } private Node createHeadingBox() { HBox results = new HBox(); Text headingText = new Text(\u0026quot;This is the Screen Title\u0026quot;); headingText.getStyleClass().add(\u0026quot;heading-text\u0026quot;); results.getStyleClass().add(\u0026quot;standard-border\u0026quot;); results.getChildren().add(headingText); results.setPadding(new Insets(6)); return results; } private Node createDataEntrySection() { HBox results = new HBox(10); results.setPadding(new Insets(2, 10, 4, 2)); results.getChildren().addAll(createGridPane(), createBioBox()); return results; } private Node createBioBox() { TextArea bioTextArea = new TextArea(); bioTextArea.setWrapText(true); bioTextArea.setMaxWidth(200); bioTextArea.setMaxHeight(200); bioTextArea.textProperty().bindBidirectional(model.bioProperty()); return new VBox(4, createPromptText(\u0026quot;Short Bio:\u0026quot;), bioTextArea); } private Node createGridPane() { GridPane results = createTwoColumnGridPane(); results.add(createPromptText(\u0026quot;First Name:\u0026quot;), 0, 0); results.add(createInputField(model.firstNameProperty()), 1, 0); results.add(createPromptText(\u0026quot;Last Name:\u0026quot;), 0, 1); results.add(createInputField(model.lastNameProperty()), 1, 1); results.add(createPromptText(\u0026quot;Email:\u0026quot;), 0, 2); results.add(createInputField(model.emailProperty()), 1, 2); results.add(createPromptText(\u0026quot;Phone Number:\u0026quot;), 0, 3); results.add(createInputField(model.phoneProperty()), 1, 3); return results; } private Node createButtonBox() { HBox results = new HBox(); results.setAlignment(Pos.CENTER_RIGHT); results.setPadding(new Insets(8)); Button button = new Button(\u0026quot;Save\u0026quot;); button.setOnAction(evt -\u0026gt; saveAction.run()); results.getChildren().add(button); return results; } private GridPane createTwoColumnGridPane() { GridPane results = new GridPane(); results.getColumnConstraints().addAll(createJustifiedConstraint(HPos.RIGHT), createJustifiedConstraint(HPos.LEFT)); results.setHgap(6); results.setVgap(4); results.setPadding(new Insets(4)); results.getStyleClass().add(\u0026quot;test-border\u0026quot;); return results; } private ColumnConstraints createJustifiedConstraint(HPos alignment) { ColumnConstraints results = new ColumnConstraints(); results.setHalignment(alignment); return results; } private Node createInputField(StringProperty boundProperty) { TextField results = new TextField(); results.setMinWidth(100); results.textProperty().bindBidirectional(boundProperty); return results; } private Node createPromptText(String prompt) { Text results = new Text(prompt); results.getStyleClass().add(\u0026quot;label-text\u0026quot;); return results; } }\n{% endhighlight %} Which looks like this:\nAs you can see, we\u0026rsquo;ve now updated createInputField() to take a StringProperty as a parameter, and it\u0026rsquo;s now bound to the Text property of the TextField. Back up in createGridPane(), it\u0026rsquo;s easy to see how each TextField is bound to a Model property.\nThe \u0026ldquo;Save\u0026rdquo; Button is wrapped in an HBox so that it can be set up at the right-hand side of the screen. You could use an AnchorPane if you want to, and then attach it to the right side. The setOnAction() method of the Button just runs the Runnable.\nOne Last Refinement # The last 4 methods in ScreenBuilder don\u0026rsquo;t really have anything to do with our entry screen at all. They\u0026rsquo;re just generic methods to configure nodes that could appear on any screen in our application. So, let\u0026rsquo;s make them static and move them to a new class, Widgets:\n{% highlight java %} public class Widgets {\nstatic GridPane createTwoColumnGridPane() { GridPane results = new GridPane(); results.getColumnConstraints().addAll(createJustifiedConstraint(HPos.RIGHT), createJustifiedConstraint(HPos.LEFT)); results.setHgap(6); results.setVgap(4); results.setPadding(new Insets(4)); return results; } private static ColumnConstraints createJustifiedConstraint(HPos alignment) { ColumnConstraints results = new ColumnConstraints(); results.setHalignment(alignment); return results; } static Node createInputField(StringProperty boundProperty) { TextField results = new TextField(); results.setMinWidth(100); results.textProperty().bindBidirectional(boundProperty); return results; } static Node createPromptText(String prompt) { Text results = new Text(prompt); results.getStyleClass().add(\u0026quot;label-text\u0026quot;); return results; } } {% endhighlight %}\nNow our ScreenBuilder class only has about 70 lines of executable code, it\u0026rsquo;s easy to read, and it\u0026rsquo;s easy to understand:\n{% highlight java %} public class ScreenBuilder implements Builder {\nprivate ExampleModel model; private Runnable saveAction; public ScreenBuilder(ExampleModel model, Runnable saveAction) { this.model = model; this.saveAction = saveAction; } public Region build() { BorderPane results = new BorderPane(); Node headingBox = createHeadingBox(); BorderPane.setMargin(headingBox, new Insets(4)); results.setTop(headingBox); results.setCenter(createDataEntrySection()); results.setBottom(createButtonBox()); return results; } private Node createHeadingBox() { HBox results = new HBox(); Text headingText = new Text(\u0026quot;This is the Screen Title\u0026quot;); headingText.getStyleClass().add(\u0026quot;heading-text\u0026quot;); results.getStyleClass().add(\u0026quot;standard-border\u0026quot;); results.getChildren().add(headingText); results.setPadding(new Insets(6)); return results; } private Node createDataEntrySection() { HBox results = new HBox(10); results.setPadding(new Insets(2, 10, 4, 2)); results.getChildren().addAll(createGridPane(), createBioBox()); return results; } private Node createBioBox() { TextArea bioTextArea = new TextArea(); bioTextArea.setWrapText(true); bioTextArea.setMaxWidth(200); bioTextArea.setMaxHeight(200); bioTextArea.textProperty().bindBidirectional(model.bioProperty()); return new VBox(4, Widgets.createPromptText(\u0026quot;Short Bio:\u0026quot;), bioTextArea); } private Node createGridPane() { GridPane results = Widgets.createTwoColumnGridPane(); results.add(Widgets.createPromptText(\u0026quot;First Name:\u0026quot;), 0, 0); results.add(Widgets.createInputField(model.firstNameProperty()), 1, 0); results.add(Widgets.createPromptText(\u0026quot;Last Name:\u0026quot;), 0, 1); results.add(Widgets.createInputField(model.lastNameProperty()), 1, 1); results.add(Widgets.createPromptText(\u0026quot;Email:\u0026quot;), 0, 2); results.add(Widgets.createInputField(model.emailProperty()), 1, 2); results.add(Widgets.createPromptText(\u0026quot;Phone Number:\u0026quot;), 0, 3); results.add(Widgets.createInputField(model.phoneProperty()), 1, 3); return results; } private Node createButtonBox() { HBox results = new HBox(); results.setAlignment(Pos.CENTER_RIGHT); results.setPadding(new Insets(8)); Button button = new Button(\u0026quot;Save\u0026quot;); button.setOnAction(evt -\u0026gt; saveAction.run()); results.getChildren().add(button); return results; } } {% endhighlight %}\nThe Stylesheet # {% highlight css %} .root{ -theme-colour : #017987; -theme-deep-colour : derive(-theme-colour, -15%); -theme-font-colour : derive(-theme-colour, -50%); -theme-deep-font-colour : derive(-theme-font-colour, -50%); -theme-light-colour : derive(-theme-colour, +90%); -contrast-colour : #f89642; -contrast-deep-colour : derive(-contrast-colour, -20%); -near-black : #252535; -theme-font-family : \u0026lsquo;Calibri\u0026rsquo;; }\n.label-text { -fx-font-family : -theme-font-family; -fx-font-size: 17; -fx-fill: -theme-colour; -fx-font-weight: bold; }\n.heading-text { -fx-font-family : -theme-font-family; -fx-font-size: 24; -fx-fill: -contrast-colour; -fx-stroke: -theme-font-colour; -fx-font-weight: bold; }\n.standard-border { -fx-border-color : -theme-colour, -theme-deep-colour; -fx-border-insets: 0, 0 -1.4 -1.4 0; -fx-border-radius : 4px, 6px; -fx-border-width : 1.5px, 0 2px 2px 0; }\n.test-border { -fx-border-color : red; -fx-border-width : 0.5; } {% endhighlight %}\nWrapping Up # This article ended up being more about code style than JavaFX, but that doesn\u0026rsquo;t really surprise me. There\u0026rsquo;s nothing magical about JavaFX, it\u0026rsquo;s just Java.\nIt took me much longer to write the text of this article than it did to write the code.\nMuch longer.\nI wrote it and modified the code as I wrote the article, so I can\u0026rsquo;t say for sure, but my guess is that the whole finished product would have taken about one cup of coffee for me to complete from scratch. Even less if I had used my own library of configuration methods and classes (a bit like the Widgets class we just created).\nBut yet it\u0026rsquo;s not really a trivial screen. It does something. Add a proper Controller and an adapter to an external API or persistence layer and it\u0026rsquo;s a fully functional application.\nThe Methodology # As you can see, building a screen by hand in JavaFX is fairly simple.\nCreate a container, put some stuff in it and run it.\nPut some more stuff inside.\nIf the new stuff needs a layout that your current container can\u0026rsquo;t supply, then put your new stuff into its own container, and then add that to your original container.\nKeep on adding stuff to the containers, creating new containers as necessary until you\u0026rsquo;re done. Keep running it over and over to see what the changes look like.\nFinally # I hope this was helpful. I\u0026rsquo;ve seen quite a few people express confusion about how to start writing JavaFX code by hand, and there doesn\u0026rsquo;t seem to be anything out there (until now) to show them how.\nLet me know if this helps you, or if you have more questions.\nCreating a screen is just one part of building an application, and you\u0026rsquo;ll need to apply a framework to get to the finish line. If you want to use Model-View-Controller with JavaFX, you can carry on with this article here.\n","date":"24 August 2021","externalUrl":null,"permalink":"/javafx/techniques/nofxml/2021-08-24-javfx_without_fxml/","section":"JavaFX","summary":"Way, way back in 2014 when I started learning JavaFX, I tried using Scene Builder and FXML and found it to be an amazing waste of time and effort. One of the best decisions I ever made was to abandon it early on, and just code screens by hand. I advise everyone else to do the same.\n","title":"How to Write a JavaFX Screen without FXML \u0026 Scene Builder","type":"javafx"},{"content":"","date":"24 August 2021","externalUrl":null,"permalink":"/categories/javafx-kotlin/","section":"Categories","summary":"","title":"Javafx Kotlin","type":"categories"},{"content":" JavaFX Properties # In JavaFX, properties and other observable classes are used to implement reactive programming techniques - which is absolutely the best way to design a JavaFX application. Ideally, you should implement your data elements as properties and then use the Binding library to connect the data to your GUI.\nJavaFX Properties in Java # In Java, JavaFX properties should be placed into a Model with a \u0026ldquo;bean\u0026rdquo; structure. This means that you should have the following 3 methods in your Model for each property that you define:\nA delegate getter for the property value A delegate setter for the property value A getter for the property itself There is a naming convention that you should follow, and you can see it in the code snippet below:\n{% gist 3a2e8ae215eaee024386a9867a5e67f1 %}\nPerhaps the most important part is that the getter for the property should have the name \u0026ldquo;{Property Name}Property\u0026rdquo;. There are some built-in parts of JavaFX which will use reflection to access the property and will need need this structure.\nif you are using Intellij Idea, it will create the three methods for you once it\u0026rsquo;s identified the lonely field declaration as an issue.\nThe Property is Final # Notice that the property in the example is has the \u0026ldquo;final\u0026rdquo; qualifier. This is because the property itself, viewed as a container, should never be changed. The contents can change - that\u0026rsquo;s the point - but you should never change the reference of the property itself or you\u0026rsquo;ll lose any bindings or listeners on the property.\nKotlin Getters and Setters # Somewhat confusingly for JavaFX programmers, Kotlin calls class fields (or \u0026ldquo;instance variables\u0026rdquo;), \u0026ldquo;properties\u0026rdquo;. This isn\u0026rsquo;t technically precise, but if you want to know more you can read the Official Kotin Page about it. Properties in Kotlin are wrappers for fields which are hidden from direct access with default getters and setters each property. Kotlin syntax allows the calls to the getters and setters to look like direct references to the properties. Like this:\n{% gist e7e3c38f85227842558e3ebc79f6c9ff %}\nIt\u0026rsquo;s possible to override the default getter and setter for a property, as in this code:\n{% gist 34b03c5530a07313dca2f4c27b4c5785 %}\nIf you run this code, you\u0026rsquo;ll see the following output:\nin the setter in the getter Nickname: Shorty So you can see that, although it looks like you\u0026rsquo;re referencing the field directly, you are actually calling the getter and the setter defined for the property.\nPerhaps even more importantly, though, is that you can access the getter and setter from Java through calls to testClass.setNickName() and testClass.getNickName(). Kotlin automatically handles this for you. By the same token, you can access getters and setters in Java classes from Kotlin using direct references to the fields.\nThe Goal for JavaFX Properties in Kotlin # When creating JavaFX properties in Kotlin, we want them to look externally exactly like they would in Java. Meaning that they have to have the same JavaFX \u0026ldquo;bean\u0026rdquo; structure with the same method names. Of course, in Kotlin the getter and setter need to still work same way that they would for any other Kotlin property - looking like direct access.\nIn this case, however, the Kotlin approach makes things appear to be a little more complex.\nDealing With the \u0026ldquo;Final\u0026rdquo; Qualifier # In Kotlin, variables can either be immutable or mutable, which is set by the use of the \u0026ldquo;val\u0026rdquo; or \u0026ldquo;var\u0026rdquo; keywords when the variable is defined. Immutable variables, defined with the \u0026ldquo;val\u0026rdquo; keyword cannot have setters. This is an issue because, while you can always override the default getter to delegate to the property getValue() method, you can\u0026rsquo;t even define a setter for a \u0026ldquo;val\u0026rdquo; variable.\nThis makes perfect sense, but it means that we need to use a slightly roundabout route to implement a JavaFX \u0026ldquo;bean\u0026rdquo;.\nAn Approach That Works # A good answer seems to be a fairly common design pattern in Kotlin, and that\u0026rsquo;s to use a hidden backing property. In this case, the hidden backing property is the actual JavaFX property type that we are going to use. There\u0026rsquo;s also a public property, which has the same type as the data contained in the JavaFX property, and which delegates it\u0026rsquo;s getters and setters to the hidden properties, get() and set() methods.\nFinally, a method is added to the Model to return the JavaFX property itself, using the same naming convention that you would use in Java.\nThe effect is that the publicly visible property will never actually have any data assigned to it, since it\u0026rsquo;s setter is overridden to put the data into the hidden backing JavaFX property. It only exists to present a public interface to the methods in the JavaFX property.\nThe result looks like this:\n{% gist f6d3bd9dfc261b6d411d7945172d5833 %}\nAnd the output looks like this:\nNickname: Shorty Property: ObjectProperty [value: Shorty] It seems to be the convention in the examples I\u0026rsquo;ve seen to use \u0026ldquo;_\u0026rdquo; prefix for hidden properties, as seen in that same official Kotlin page - in the section on Backing Properties . So I\u0026rsquo;ve followed that here.\nYou should also note that, even though it seems quite verbose, this is actually a little less code than you\u0026rsquo;d need to implement a JavaFX bean in Java. Which is pretty normal for Kotlin.\nAn Added Bonus # If you\u0026rsquo;re using Intellij Idea, like I do, then you might find the following like template useful:\n{% gist d301cb308d2b4741220c6146cf3ba69c %}\nJust give it a short name (I use \u0026ldquo;fxprop\u0026rdquo;) and it\u0026rsquo;ll make your life a lot easier.\nI\u0026rsquo;ve only implemented it as ObjectProperty\u0026lt;{Type}\u0026gt; because that works best. StringProperty might be useful, but the Number based properties like IntegerProperty and DoubleProperty have type issues when you get into complicated stuff. So they seem easier to use, but ObjectProperty\u0026lt;Int\u0026gt; is almost always a better choice than `IntegerProperty``.\n","date":"24 August 2021","externalUrl":null,"permalink":"/kotlin/2021-11-16-kotlin_javafx_properties/","section":"Kotlin","summary":"JavaFX Properties # In JavaFX, properties and other observable classes are used to implement reactive programming techniques - which is absolutely the best way to design a JavaFX application. Ideally, you should implement your data elements as properties and then use the Binding library to connect the data to your GUI.\n","title":"Kotlin: JavaFX Properties","type":"kotlin"},{"content":"I decided to try writing a Hangman game that would look up words from the internet, just too see how much code it would actually take. The simplicity of the result surprised even me and it points out just how powerful reactive programming can be.\nThe Inspiration # I came across a question on the Internet from someone trying to create a virtual keyboard (for a Hangman game) that looked a bit like a physical keyboard, with each row of keys a little offset from the one above it. The person asking the question was trying to use a GridPane, which seems like it might be logical, but that became complicated because to create the offsets you needed to have each key span two columns so that you could skip the first column on the second row, and so on.\nIt turns out it\u0026rsquo;s extremely simple to write with just a few lines of code by using a VBox with the keys in three HBoxes. Then you can provide the offsets, by adding some padding to the left of the 2nd and 3rd HBoxes.\nThe code that was posted by the person asking the question was really, really complicated - apart from the GridPane - and I was struck by how really simple the code I had written was in comparison. So I asked myself, \u0026ldquo;Just how little code would it take to write the whole game?\u0026rdquo;\nHangman - The Rules # Hangman is a pretty simple game where you try find a hidden word by guessing at letters that might be in it. There\u0026rsquo;s a picture of a noose, and every time you guess a letter that isn\u0026rsquo;t in the word, a new body part is added to the picture. First, a head, then a body then the legs and so on. When the entire figure has been drawn, if you haven\u0026rsquo;t guessed the word, you lose. Usually, when you\u0026rsquo;re playing the game with paper and pens, the hangman is just a stick figure. Generally speaking, there are 7 body parts, so you get 7 wrong letter guesses before you lose.\nThe Program Structure # I decided to use an MVCI structure (MVC plus an Interactor to hold the game logic), because that\u0026rsquo;s generally best when doing anything non-trivial.\nThe Model # This is the place to start, because the Model really describes how the game logic is going to be translated over to the GUI.\nIf you think about it for a little bit, there\u0026rsquo;s really only two pieces of data required for the game; a word, and a list of letters that have been guessed. Every other aspect of the game play can be derived from just those two things. Furthermore, the word isn\u0026rsquo;t really going to be treated like a word, it\u0026rsquo;s just an ordered list of letters.\nSo the first two elements of the Model need to be ObservableLists; one with the letters of the word, and the other with the letters that have been guessed.\nThe next thing we need for the GUI is the number of wrong letters that have been guessed. This is just a count of all of the letters in guessed list that aren\u0026rsquo;t contained in the word list. Since this is game logic, it can\u0026rsquo;t be done in the View, but needs to be included in the Model so that it can be observed by the View. In the Model, we\u0026rsquo;ll create an IntegerProperty to hold this value.\nFinally, we need a BooleanProperty to hold whether the game has been won, and another to hold whether it has been lost.\nThis is what the fields in the Model look like:\nprivate final ObservableList\u0026lt;String\u0026gt; pickedLetters = FXCollections.observableArrayList(); private final ObservableList\u0026lt;String\u0026gt; word = FXCollections.observableArrayList(); private final IntegerProperty wrongLetterCount = new SimpleIntegerProperty(0); private final BooleanProperty gameWon = new SimpleBooleanProperty(false); private final BooleanProperty gameLost = new SimpleBooleanProperty(false); Of course, the Model will also contain all of JavaFX Bean methods for these fields.\nThe Interactor # The game play for Hangman is pretty simple. There are just two actions; one to reset the hidden word, and another to add a letter to the list of guessed letters. So the Interactor has to support those two actions, and set up the Model with the relationships between those two lists and the three other fields.\nBindings in the Model # Let\u0026rsquo;s look at the Model setup first. It\u0026rsquo;s done in the constructor of the Interactor, which is provided a reference to the Model by the Controller when it\u0026rsquo;s instantiated:\npublic HangmanInteractor(HangmanModel model) { this.model = model; this.model.gameLostProperty().bind(model.wrongLetterCountProperty().greaterThan(5)); this.model.gameWonProperty() .bind(Bindings.createBooleanBinding(() -\u0026gt; hasGameBeenWon() \u0026amp;\u0026amp; !model.isGameLost(), model.getPickedLetters(), model.gameLostProperty(), model.getWord())); this.model.wrongLetterCountProperty().bind(Bindings.createIntegerBinding(this::countWrongLetters, this.model.getPickedLetters())); } private int countWrongLetters() { return Math.toIntExact(model.getPickedLetters().stream().filter(letter -\u0026gt; !model.getWord().contains(letter)).count()); } private boolean hasGameBeenWon() { return (model.getWord().stream().allMatch(model.getPickedLetters()::contains)); } The first element to look at is the wrongLetterCount property. It\u0026rsquo;s created via Bindings.createIntegerBinding(). There\u0026rsquo;s a method called countWrongLetters() which streams the pickedLetters List, filters out the ones that are in the word, and then counts them. The Binding is triggered every time the pickedLetters List changes.\nThe gameLost property is bound to the wrongLetterCount property, checking to see if it is greater than 5.\nThe gameWon property is calculated by streaming through the word List, and checking that each letter in the word is also in the pickedLetters List. Recalculation is triggered when either of the two letter lists is changed.\nGame Play Methods # There\u0026rsquo;s only two actions in the game play; when a letter is selected, and restarting a new word. Here\u0026rsquo;s the code for both:\nvoid processLetter(String letter) { if (!model.isGameWon() \u0026amp;\u0026amp; !model.isGameLost()) { model.getPickedLetters().add(letter); } } void newWord() { model.setWord(wordAsList(words.remove(0).toUpperCase())); model.getPickedLetters().clear(); } private List\u0026lt;String\u0026gt; wordAsList(String word) { return Arrays.asList(word.split(\u0026#34;\u0026#34;)).stream().filter(letter -\u0026gt; !letter.isEmpty()).collect(Collectors.toList()); } That\u0026rsquo;s all pretty simple. The action for selecting a letter is just to add it to the pickedLetters List and then the Bindings will do the rest, updating the other three properties. The if statement around the action just prevents the addition of new selected letters after the game is over.\nThe newWord() method just clears the pickedLetters list and converts a new word string into an array of letters that are used to replace the contents of the word List.\nGetting Words # But where does the array of words come from? A little Googling found a REST API on herokuapp.com that supplies random words. Rather than hit it each time to get a new word, it seemed more efficient to grab 100 words, pull only those with less that 10 letters and put them in a list. The API is hit once when the application is started, and the initial list just has the word \u0026ldquo;HANGMAN\u0026rdquo; in it until the API responds - that way it\u0026rsquo;s not empty if the user starts to play before the API can return a list.\nIf anyone feels the need to play Hangman more than about 100 times in a row, they\u0026rsquo;ll run out of words. It didn\u0026rsquo;t seem worth programming around this, and probably easier just to increase the number of words retrieved from the API to 200 or 1000 if it\u0026rsquo;s a problem.\nHere\u0026rsquo;s the code to retrieve the words from the REST API, note that this code is expected to run on a background thread managed by the Controller:\nvoid fetchWords() { try { HttpRequest request = Unirest.get(\u0026#34;https://random-word-api.herokuapp.com/word\u0026#34;).queryString(\u0026#34;number\u0026#34;, \u0026#34;100\u0026#34;); Type collectionType = new TypeToken\u0026lt;Collection\u0026lt;String\u0026gt;\u0026gt;() {}.getType(); ArrayList\u0026lt;String\u0026gt; wordArray = new Gson().fromJson(request.asString().getBody().toString(), collectionType); words = wordArray.stream().filter(word -\u0026gt; word.length() \u0026lt; 10).collect(Collectors.toList()); } catch (Exception e) { e.printStackTrace(); } } The code uses konghq.com\u0026rsquo;s unirest-java and unirest-object-wrappers-gson libraries to handle the REST and JSON work.\nThe Controller # Here\u0026rsquo;s the entire Controller, since it\u0026rsquo;s pretty simple:\npublic class HangmanController { HangmanInteractor interactor; HangmanModel model; Builder\u0026lt;Region\u0026gt; viewBuilder; public HangmanController() { model = new HangmanModel(); interactor = new HangmanInteractor(model); viewBuilder = new HangmanViewBuilder(model, interactor::processLetter, interactor::newWord); fetchWords(); } private void fetchWords() { Task\u0026lt;Void\u0026gt; fetchTask = new Task\u0026lt;\u0026gt;() { @Override protected Void call() { interactor.fetchWords(); return null; } }; fetchTask.setOnSucceeded(evt -\u0026gt; interactor.newWord()); Thread fetchThread = new Thread(fetchTask); fetchThread.start(); } public Region getView() { return viewBuilder.build(); } } The constructor instantiates the Model, View Builder, and Interactor, then launches a background task to go fetch the words list from the REST API at herokuapp.com. There\u0026rsquo;s a getter for the View which triggers the build() method in the View Builder.\nA Task is used to do the background processing to fetch the words from the internet. When the background task is completed, it calls the Interactor\u0026rsquo;s newWord() method which launches a new game.\nThe View # Now all of the game mechanics have been completed, the Model has been set up, the game play actions are all handled and we can even retrieve a list of words from the Internet. All that remains is the GUI.\nThe GUI needs 4 components:*\nA virtual keyboard A hanged man Win/Lose indicators Puzzle word I chose to use a BorderPane to hold the GUI because it seemed like a natural fit for a screen that has been divided up into logical sections. It should be possible to achieve something similar with just VBoxes and HBoxes, though.\nIt\u0026rsquo;s important to remember that the GUI is just strictly a GUI. It doesn\u0026rsquo;t contain any game logic at all, but simply handles the visual presentation of the game, and the interaction with the user. One other thing that you\u0026rsquo;ll notice is that none of the parts of the GUI have any direct interaction with any of the other parts. I could have written a separate class for each part, just to make the point.\nThe Virtual Keyboard # The virtual keyboard is a separate class, mostly because it has enough code in it that it makes sense to split it out and thereby keep the main GUI code cleaner. Also, this was the part that I started with to see how it could be done, and I like the idea of having it as a separate, reusable part.\nThe layout is super simple. It\u0026rsquo;s a VBox, and the VBox holds a set of HBoxes. Each HBox is a row of keys. The last row has the \u0026ldquo;Restart\u0026rdquo; button, which is a little special. Each row has a little more left padding than the one above it. In the spirit of DRY, the creation of the buttons and the HBox rows are delegated to methods which are called repeatedly. So there\u0026rsquo;s no duplicated code.\nThe letter Buttons and the \u0026ldquo;Restart\u0026rdquo; button are pretty much identical, except for the action an the \u0026ldquo;Restart\u0026rdquo; button. So the instantiation and formatting of the buttons is all done in the createButton() method.\nThe virtual keyboard takes two parameters in its constructor, one is a Consumer\u0026lt;String\u0026gt; which will handle the letter button clicks, and the other is a Runnable which will handle the restart. These are invoked via onAction event of each Button. The virtual keyboard has no knowledge of what these elements do, and it does not need to know. Besides invoking the Consumer and the Runnable, the onAction event handlers on the letter Buttons disables the button, while the \u0026ldquo;Restart\u0026rdquo; button re-enables all the buttons.\nHere\u0026rsquo;s the code for the Virtual Keyboard:\npublic class VirtualKeyboard extends VBox { private static final List\u0026lt;String\u0026gt; row1Keys = List.of(\u0026#34;Q\u0026#34;, \u0026#34;W\u0026#34;, \u0026#34;E\u0026#34;, \u0026#34;R\u0026#34;, \u0026#34;T\u0026#34;, \u0026#34;Y\u0026#34;, \u0026#34;U\u0026#34;, \u0026#34;I\u0026#34;, \u0026#34;O\u0026#34;, \u0026#34;P\u0026#34;); private static final List\u0026lt;String\u0026gt; row2Keys = List.of(\u0026#34;A\u0026#34;, \u0026#34;S\u0026#34;, \u0026#34;D\u0026#34;, \u0026#34;F\u0026#34;, \u0026#34;G\u0026#34;, \u0026#34;H\u0026#34;, \u0026#34;J\u0026#34;, \u0026#34;K\u0026#34;, \u0026#34;L\u0026#34;); private static final String restart = \u0026#34;Restart\u0026#34;; private static final List\u0026lt;String\u0026gt; row3Keys = List.of(\u0026#34;Z\u0026#34;, \u0026#34;X\u0026#34;, \u0026#34;C\u0026#34;, \u0026#34;V\u0026#34;, \u0026#34;B\u0026#34;, \u0026#34;N\u0026#34;, \u0026#34;M\u0026#34;); private final Consumer\u0026lt;String\u0026gt; keystrokeConsumer; private Runnable restartHandler; private List\u0026lt;Button\u0026gt; allButtons = new ArrayList\u0026lt;\u0026gt;(); public VirtualKeyboard(Consumer\u0026lt;String\u0026gt; keystrokeConsumer, Runnable restartHandler) { this.keystrokeConsumer = keystrokeConsumer; this.restartHandler = restartHandler; getChildren().addAll(createRow(row1Keys, 0d, false), createRow(row2Keys, 20d, false), createRow(row3Keys, 40d, true)); } private HBox createRow(List\u0026lt;String\u0026gt; letters, Double leftPadding, boolean includeRestart) { HBox hBox = new HBox(); hBox.getChildren().addAll(letters.stream().map(this::buttonSetup).collect(Collectors.toList())); if (includeRestart) { hBox.getChildren().add(createRestartKey()); } hBox.setPadding(new Insets(0, 0, 0, leftPadding)); return hBox; } private Node buttonSetup(String letter) { Button button = createButton(letter); button.setOnMouseClicked(mouseEvent -\u0026gt; { keystrokeConsumer.accept(letter); button.setDisable(true); }); allButtons.add(button); return button; } private Button createButton(String letter) { Button button = new Button(letter); button.setMinSize(60.0, 60.0); button.setStyle(\u0026#34;-fx-font-size: 22; -fx-font-weight: bold;\u0026#34;); return button; } private Node createRestartKey() { Button button = createButton(\u0026#34;Restart\u0026#34;); button.setOnAction(evt -\u0026gt; { allButtons.forEach(eachButton -\u0026gt; eachButton.setDisable(false)); restartHandler.run(); }); return button; } } The Hanged Man # This was probably the trickiest part of the whole screen. How to cycle through a series of 7 images as the number of wrong letters grows? A little bit of googling came up with the following image:\nI liked the look of this, but I wasn\u0026rsquo;t thrilled with the idea of splitting it up manually to make 7 images out of it. It turns out that you don\u0026rsquo;t need to! The JavaFX ImageView class has a property called viewport, and you can manually manipulate this to show only part of an image. You can set the viewport by creating a Rectangle2D object with the correct size and positioning and use that. In order to handle the whole thing, I created a List of Rectangle2D, each with the correct offset, then bound the Viewport property of the ImageView to it.\nI\u0026rsquo;ve since found out that this image is called a \u0026ldquo;Sprite\u0026rdquo;, and is commonly used in gaming to provide animation to characters that move around the screen.\nThe binding itself is interesting. It\u0026rsquo;s from the Bindings library and it\u0026rsquo;s called valueAt(). You give it an Observable List, and an Observable Integer and it will select the item in the List that corresponds to the Observable Integer. It\u0026rsquo;s simple, and it\u0026rsquo;s clean. I use the wrongLetterCount property from the Model to control the Viewport.\nHere\u0026rsquo;s the code:\npublic Node createHangedMan() { Image image = new Image(\u0026#34;/images/hangman.png\u0026#34;); ImageView imageView = new ImageView(image); double cellWidth = image.getWidth() / 7; List\u0026lt;Rectangle2D\u0026gt; clippingShapes = IntStream.range(0, 7) .mapToObj(i -\u0026gt; new Rectangle2D(i * cellWidth, 0, cellWidth, image.getHeight())) .collect(Collectors.toList()); imageView.viewportProperty().bind(Bindings.valueAt(FXCollections.observableList(clippingShapes), model.wrongLetterCountProperty())); StackPane results = new StackPane(imageView); results.setPadding(new Insets(30)); return results; } It\u0026rsquo;s actually fairly simple. I put the ImageView in a StackPane because the contents of StackPanes are centred by default, with a little padding so that he\u0026rsquo;s not tucked up over to the left or crowded by the Centre panel.\nWin/Lose Indicators # This part is pretty simple. I created a StackPane and put the two ImageViews into it. One with the the \u0026ldquo;Win\u0026rdquo; image, and one with the \u0026ldquo;You Lose\u0026rdquo; image. Both images were scaled the same, at a size that would fit in the area available. The Visible property of each image was bound to the appropriate property in the Model, either gameWon or gameLost.\nPuzzle Word # The Puzzle Word, and the Win/Lose images are in a VBox in the Centre of the BorderPane. The Puzzle Word is actually a set of VBox\u0026rsquo;s held in an HBox. Each VBox has a Text with a letter above a Line. Once again, in order to stick to DRY, there\u0026rsquo;s a separate method to create each box.\npublic void buildWord() { wordBox.getChildren().setAll(model.getWord().stream().map(this::createLetterBox).collect(Collectors.toList())); } private Node createLetterBox(S```tring letter```) { VBox vBox = new VBox(); Node letterText = bigText(letter); letterText.visibleProperty() .bind(Bindings.createBooleanBinding(() -\u0026gt; model.getPickedLetters().contains(letter) || model.isGameLost(), model.getPickedLetters(), model.gameLostProperty())); vBox.getChildren().addAll(letterText, new Line(0, 10, 40, 10)); vBox.setAlignment(Pos.BOTTOM_CENTER); return vBox; } private Node bigText(String letter) { Text results = new Text(letter); results.setStyle(\u0026#34;-fx-font-size: 45; -fx-font-weight: bold\u0026#34;); return results; } That\u0026rsquo;s All it Takes # Really, that\u0026rsquo;s it. On the surface, this seems like a much more difficult thing to build, there\u0026rsquo;s all of the images, the virtual keyboard and keeping track of what letters have been played and which to show. But when you look at it closely, it all becomes very simple. None of the screen elements directly interact, and the virtually every element of the GUI is controlled by two arrays of letters and some bindings which combine them together.\nThe complete code can be found here:\nHangMan on GitHub\n","date":"15 August 2021","externalUrl":null,"permalink":"/javafx/hangman/","section":"JavaFX","summary":"How much code does it take to build a complete Hangman game in JavaFX?\nIt turns out … Not much!","title":"Hangman in JavaFX","type":"javafx"},{"content":"PropertyValueFactory is an obsolete convenience method designed to eliminate boilerplate. We don\u0026rsquo;t don\u0026rsquo;t need it any more now that we have Lambda expressions.\nIn JavaFX, the TableView class is largely customized by adding TableColumns to an instance. TableColumn is where you define both the TableCells that will be used, and manner in which data will be extracted from each row model to populate each the TableCell.\nThe Full Boilerplate Approach # The JavaDoc\u0026rsquo;s for TableColumn document the full boilerplate necessary to define how the data will be populated into the cells:\nObservableList\u0026lt;Person\u0026gt; data = ... TableView\u0026lt;Person\u0026gt; tableView = new TableView\u0026lt;Person\u0026gt;(data); TableColumn\u0026lt;Person,String\u0026gt; firstNameCol = new TableColumn\u0026lt;Person,String\u0026gt;(\u0026#34;First Name\u0026#34;); firstNameCol.setCellValueFactory(new Callback\u0026lt;CellDataFeatures\u0026lt;Person, String\u0026gt;, ObservableValue\u0026lt;String\u0026gt;\u0026gt;() { public ObservableValue\u0026lt;String\u0026gt; call(CellDataFeatures\u0026lt;Person, String\u0026gt; p) { return p.getValue().firstNameProperty(); } }); Yikes! That\u0026rsquo;s a lot of \u0026ldquo;\u0026lt;\u0026gt;\u0026rdquo; to wrap your brain around.\nWhat does this do?\nWell first, a Callback is very similiar to a Function. It takes a value of some specific type and returns a value of some other, specific type. While a Function uses the apply() method, a Callback uses a call() method.\nIn this case, we\u0026rsquo;re going to pass a TableColumn.CellDataFeatures object to the call() method, and it will return an ObservableValue back. Both of these classes are generics, so we need specify the class of the data model for the table, and the class of the TableColumn data. In the example above, the table model is Person and the Observable returned will contain a String.\nIt\u0026rsquo;s not really important to know, but TableColumn.CellDataFeatures is a wrapper class which holds a reference to the TableView, the TableColumn, and has a function to return an instance of the TableView data model (in other words, the data for the row). Remember that this Callback is invoked from deep inside the TableView code, and the purpose of this Callback is to provide a \u0026ldquo;hook\u0026rdquo; to allow that code to extract the data that it needs from the TableView data model to populate the cell.\nTechnically, it might be possible that you\u0026rsquo;d need some properties of the TableView or the TableColumn to determine how to extract the data from the data model. Generally speaking, though, you just need the data model to extract the data.\nThe Original Convenience Method # Even when you do understand what that boilerplate code is doing, it\u0026rsquo;s still a lot of code to write for something that almost always boils down to one line very similar to this:\nreturn p.getValue().firstNameProperty(); Because that\u0026rsquo;s what most CellValueFactories do, they extract a single ObservableValue from an object composed of a number of ObservableValue\u0026rsquo;s. Furthermore, the best practice is to implement the fields in the model as JavaFX Property Beans, like this:\npublic class Model { private StringProperty firstName = new SimpleStringProperty(\u0026#34;\u0026#34;); public String getFirstName() { return firstName.get(); } public void setFirstName(String firstName) { this.firstName.set(firstName); } public StringProperty firstNameProperty() { return firstName; } } For each field in the Model, there\u0026rsquo;s a getter and a setter which delegate to the field\u0026rsquo;s get() and set() methods. Then there\u0026rsquo;s a method called {Field Name}Property(), which returns the property itself.\nThis last piece is the most important for the TableView because, if this pattern is followed, you can use reflection to access the property through this method.\nReturning to the JavaDoc\u0026rsquo;s for TableColumn, we see this:\nIt is hoped that over time there will be convenience cell value factories developed and made available to developers. As of the JavaFX 2.0 release, there is one such convenience class: PropertyValueFactory. This class removes the need to write the code above, instead relying on reflection to look up a given property from a String.\nCuriously, this hasn\u0026rsquo;t been updated, even as of JavaFX 15 - which is a shame.\nThis meant that you could replace the boilerplate described above with this:\nfirstNameCol.setCellValueFactory(new PropertyValueFactory(\u0026#34;firstName\u0026#34;); Which is clearly a lot easier to both write and read than the full boilerplate. In fact, most programmers learn this method without ever understanding the Callback code that lies behind it.\nThe Problem with \u0026ldquo;PropertyValueFactory\u0026rdquo; # The problem is that \u0026ldquo;firstName\u0026rdquo; is just a String. There\u0026rsquo;s no way that your IDE or a compiler is going to detect that \u0026ldquo;firstName\u0026rdquo; means that there needs to be a method called firstNameProperty() in your model. So if you misspell \u0026ldquo;firstName\u0026rdquo;, perhaps as \u0026ldquo;FirstName\u0026rdquo;, your code will fail at runtime and you\u0026rsquo;ll have to chase it down.\nEven worse, should you decide to refactor your model and change the name of the field and the methods associated with it, your IDE won\u0026rsquo;t be able to track down the reference in the String and update it as well. Which, of course, won\u0026rsquo;t generate any compiler errors but will mysteriously cause your TableView to stop working.\nLambda to the Rescue # Since Java 8, there is a better way to reduce the boilerplate which avoids the use of reflection and makes PropertyValueFactory obsolete.\nRemember that the Callback interface is a Functional Interface. Which means that it can be the target of a lambda expression. Also, lambda expressions can infer the class of the input parameters based on the context in which it is declared. So the boilerplate listed above can be replaced with this:\nObservableList\u0026lt;Person\u0026gt; data = ... TableView\u0026lt;Person\u0026gt; tableView = new TableView\u0026lt;Person\u0026gt;(data); TableColumn\u0026lt;Person,String\u0026gt; firstNameCol = new TableColumn\u0026lt;Person,String\u0026gt;(\u0026#34;First Name\u0026#34;); firstNameCol.setCellValueFactory(p -\u0026gt; p.getValue().firstNameProperty()); In this example p is the CellDataFeatures\u0026lt;Person, String\u0026gt; object generated from deep inside the TableView code, and the return value is an ObservableValue\u0026lt;String\u0026gt;. But there\u0026rsquo;s no need to declare these types in the lambda since they are all inferred from the context.\nClearly, this is about the same amount of code as using PropertyValueFactory, but it avoids the use of reflection. If you misspell something, you will get a compiler error and a red squiggly line in your IDE. If you refactor firstNameProperty() to a new name, your IDE will adjust it automatically and the compiler will complain if something goes wrong.\nSo that\u0026rsquo;s it.\nStop using PropertyValueFactory right now. It does nothing for you, and can cause you trouble in the future. Use lambda expressions instead.\n","date":"24 June 2021","externalUrl":null,"permalink":"/javafx/techniques/propertyvaluefactory/","section":"JavaFX","summary":"PropertyValueFactory is an obsolete convenience method designed to eliminate boilerplate.  We don’t don’t need it any more now that we have Lambda expressions.","title":"Stop Using PropertyValueFactory","type":"javafx"},{"content":"Model-View-Controller is generally accepted as a good way to structure an application with a user interface. While JavaFX contains all of the classes needed to implement MVC in a natural and seamless way, it doesn\u0026rsquo;t seem to be widely documented and there\u0026rsquo;s little evidence on the Web that many people have figured out how to do it properly. The fact that JavaFX supports Reactive programming makes a big difference to how you should implement MVC.\nModel-View-Controller-Interactor # One of the biggest complications with MVC in JavaFX is the need to handle background threads to perform time intensive work which cannot take place on the Application Thread (FXAT). Coupled with that is a firm rule that GUI elements, including any Observable type variables and fields must only be updated by code running on the FXAT.\nTo make this easier to handle, I prefer a slight modification to MVC which splits the controller into two parts, adding an \u0026ldquo;Interactor\u0026rdquo; which handles the business/application logic for the screen. You don\u0026rsquo;t need to do this split if you don\u0026rsquo;t want to, but it does make the code more organized.\nLet\u0026rsquo;s look at what each part does:\nThe Model # The model is the storage space for all of the data shared throughout the the MVCI structure. It is instantiated by the Controller, which retains a reference to it, and passes that reference on to the View and the Interactor. It is the only source of direct communication between the View and the Interactor.\nIn the world of Reactive Programming, the Model is the home for the elements generally referred to as \u0026ldquo;State\u0026rdquo;.\nIn JavaFX, the building blocks of data storage are \u0026ldquo;observable\u0026rdquo; entities such as Properties and ObservableLists. The Model should be a POJO with the fields all instances of the various observable classes in JavaFX. Each property type field should have a getter and a setter which delegate to the get() and set() methods of the property, and a {Field_Name}Property() method which returns the property itself. Essentially, this is the JavaFX version of a Bean. Normally, an ObservableList type field would have a getter, and then a setter that delegates to the setAll() method of the list. All of the fields should be final.\nThe Controller # The Controller is the central element of the structure, and the connection to the rest of the application\u0026rsquo;s GUI. It\u0026rsquo;s instantiated first, and is responsible for instantiating the other components and handling the communication between them. It\u0026rsquo;s also responsible for handling any threading, which is important in JavaFX as all non-GUI activity needs to happen in a background thread.\nThe View # The View is the actual graphical content of the screen. Typically, the View would be a class which extends one of the Region type Nodes in JavaFX; like a BorderPane, VBox or StackPane. Something which can be inserted into a Scene. The View is restricted to having only code which is directly related to displaying information on the screen and accepting user input.\nThe View normally interacts with the Model in one way only. It binds the properties in the model to the properties in the JavaFX nodes on the screen. User editable fields are bound bidirectionally, and display only fields are bound from the Model to the value properties of display nodes. In this way, the Model always reflects the current state of the information displayed on the screen, and there is no reason to write code that scrapes the data out of the screen nodes to submit it to the Controller when any action button is clicked.\nThe Interactor # The Interactor is the application - or business - logic of the MVCI system. It\u0026rsquo;s the piece which \u0026ldquo;interacts\u0026rdquo; with the non-GUI elements of the application. It can invoke the persistence layer or communication classes to talk to other systems, and it can deal with both domain objects and the Model. It\u0026rsquo;s responsible for establishing interrelationships between elements of the model based on business rules.\nGenerally speaking, the Interactor is the only class which should be allowed to use the getters and setters in the Model. The Controller and the View should only access the fields by the ...Property() methods - and then usually just to bind them or add listeners.\nHow Does the Interactor Help? # While it\u0026rsquo;s really just an organizational thing, it\u0026rsquo;s critical to providing structure to the application by isolating the application logic from screen control logic. Technically, everything that the Interactor does could be handled by the Controller, but at the cost of making the Controller much more difficult to understand.\nThe Interactor is where the domain objects, process data and screen data all exist in the same place. Typically, an Interactor will make calls to a persistence layer or external API and then it will need to store the results from those calls. It might have data structures to handle processing and analysis of the domain objects specific to the functionality of the MVCI component, all of which need to be updated on a background thread. In addition, it will probably need to read the contents of the Model in order to make those API calls and process the resulting data on that background thread.\nKeeping Things in the Right Place # One of the keys to writing clean applications, second only to naming things clearly, is putting code in the right place. The toughest part of MVCI is keeping business logic out of the View, as it is extremely easy for it to sneak in. One of the best ways to avoid this is to follow a strict rule of not creating relationships between screen nodes unless the relationship is completely about display.\nFor instance, if there is a checkbox that makes a region of the screen visible or invisible, then it\u0026rsquo;s OK to bind the visible property of the region to the value property of the checkbox. But if the visibility of that region is based on something in the data, perhaps a threshold value for a particular field, then that linkage can\u0026rsquo;t be established by the View itself, and needs to come from the Model.\nHow do you put that in the Model?\nCreate a boolean property in the Model called something like, \u0026ldquo;yadaYadaThresholdMet\u0026rdquo;, and bind the visibility of the region to that field. The logic to actually decide if the threshold has been met belongs in the Interactor, and there should be some code, generally invoked from the Interactor\u0026rsquo;s constructor, that binds that boolean property to other fields in the Model properly.\nPhilosophically, it\u0026rsquo;s also important not to call that boolean property something like, \u0026ldquo;showXyzRegion\u0026rdquo;, as this has no meaning when someone is looking at the Interactor. Since it\u0026rsquo;s actually expressing some sort of application rule, it should be named in a manner that clearly reflects that rule.\nWhile the View has no code which reflects business logic, no other part of the system can ever assume anything about the contents of the View. Any particular field in the Model may or may not be present in the GUI, and manner in which it\u0026rsquo;s displayed cannot be known. There may or may not be a \u0026ldquo;Save\u0026rdquo; button - maybe the save action is triggered by changing the value in a ComboBox?\nA Simple Example # Here\u0026rsquo;s a simple example to illustrate how this all goes together. This is an MVCI set-up with screen that has a TextField and a button, and the button triggers an action that will modify the value in the TextField in some way - in this case it assumes it is a number and attempts to add 5 to it.\npublic class MvciSample extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Scene scene = new Scene(new MvciController().getView(), 500, 200); primaryStage.setScene(scene); primaryStage.show(); } } You can see here that the application instantiates the Controller, then fetches the View from it and installs it into the Scene. Let\u0026rsquo;s look at how the Controller works:\npublic class MvciController { private final Region view; public MvciController() { MvciModel viewModel = new MvciModel(); MvciInteractor interactor = new MvciInteractor(viewModel); view = new MvciView(viewModel, interactor::addFive); } public Region getView() { return view; } } The Controller instantiates the Model, then passes it to the constructor of the Interactor and then the constructor of the View. Additionally, the Controller passes a Runnable to the View to handle the action when View triggers it.\nNow let\u0026rsquo;s look at the Model:\nclass MvciModel { private final StringProperty number = new SimpleStringProperty(\u0026#34;0\u0026#34;); private final BooleanProperty moreAllowed = new SimpleBooleanProperty(true); String getNumber() { return number.get(); } StringProperty numberProperty() { return number; } void setNumber(String number) { this.number.set(number); } boolean isMoreAllowed() { return moreAllowed.get(); } BooleanProperty moreAllowedProperty() { return moreAllowed; } void setMoreAllowed(boolean moreAllowed) { this.moreAllowed.set(moreAllowed); } } There are just two fields, one is a StringProperty that holds a number, and the other is a BooleanProperty that will control whether the \u0026ldquo;Add 5\u0026rdquo; action is allowed at any time. Both fields are explicitly declared final, but they\u0026rsquo;re also effectively final since the \u0026ldquo;setter\u0026rdquo; for each actually delegates to the set method of the properties.\nNow to look at the View:\nclass MvciView extends VBox { MvciView(MvciModel model, Runnable actionHandler) { TextField numberTF = new TextField(); numberTF.textProperty().bindBidirectional(model.numberProperty()); HBox dataBox = new HBox(4, new Label(\u0026#34;Enter a number: \u0026#34;), numberTF); Button button = new Button(\u0026#34;Add 5\u0026#34;); button.setOnAction(evt -\u0026gt; actionHandler.run()); button.disableProperty().bind(model.moreAllowedProperty().not()); getChildren().addAll(dataBox, button); } } The View is one of the simplest JavaFX container nodes, a VBox. There are two elements in the VBox; an HBox containing a label and a TextField for data entry, and a button that performs an action. The \u0026ldquo;text\u0026rdquo; property of the TextField is bound to the number field in the Model, and the \u0026ldquo;disable\u0026rdquo; property of the button is bound to the moreAllowed field in the Model. The button has been set up so that it\u0026rsquo;s onAction() event handler will invoke the actionHandler Runnable.\nThe only constraints on the structure of the View are defined by the dependencies in the constructor. It must be able to function with the Model it\u0026rsquo;s given, and potentially invoke the action supplied through the Runnable. The TextField doesn\u0026rsquo;t have to be a TextField, potentially it could be a Spinner (although it would have to handle conversions to String in the binding) or some other input method. Presumably, if it was a ComboBox, the Model would have to be able to supply the possible value choices somehow.\nNow, the Interactor:\nclass MvciInteractor { MvciModel viewModel; MvciInteractor(MvciModel viewModel) { this.viewModel = viewModel; viewModel.moreAllowedProperty() .bind(Bindings.createBooleanBinding(() -\u0026gt; checkIfMoreAllowed(), viewModel.numberProperty())); } void addFive() { try { viewModel.setNumber(Integer.toString(Integer.parseInt(viewModel.getNumber()) + 5)); } catch (NumberFormatException e) { viewModel.setNumber(\u0026#34;5\u0026#34;); } } private boolean checkIfMoreAllowed() { try { int numberValue = Integer.parseInt(viewModel.getNumber()); return (numberValue \u0026lt; 21); } catch (Exception e) { return true; } } } This interactor does two important things. First, it provides the logic to perform the \u0026ldquo;Add 5\u0026rdquo; action by converting the string into an integer and adding 5. If the integer conversion fails, then it resets the string to \u0026ldquo;5\u0026rdquo;.\nSecondly, it binds the Model field \u0026ldquo;moreAllowed\u0026rdquo; to the \u0026ldquo;number\u0026rdquo; field through a supplier that invokes the checkIfMoreAllowed() method. This method always returns true if the string in the \u0026ldquo;number\u0026rdquo; field is not a number, so that the addFive() method can reset it when invoked.\nNote that this Interactor has no knowledge of the structure of the View baked into it. It deals with the Model alone, and supplies a single callable method with no constraints or dependencies about when or how it will be invoked. Just like the View, it could be replaced with a different Interactor so long as that new Interactor handles the same model and supplies an addFive() method. For instance, a new Interactor could treat the \u0026ldquo;number\u0026rdquo; string like a string and simply concatenate a \u0026ldquo;5\u0026rdquo; onto it, setting \u0026ldquo;moreAllowed\u0026rdquo; to false if the string had 10 or more characters in it.\nPackaging and Visibility # The best packaging is probably to place all of the MVCI components into the same package so that visibility of the inner parts of the components can be controlled properly. Any other custom view elements, such as extensions of TableView, can be added to the same package.\nNote that the only methods with \u0026ldquo;public\u0026rdquo; visibility are the constructor and the getView() method of the controller. All of the other classes are \u0026ldquo;package-private\u0026rdquo;, and have no exposure from classes outside the package. Although this example is too simple to see it, it should also be noted that the View has no methods which are not \u0026ldquo;private\u0026rdquo; except its constructor. In other words, nothing of the inner structure of the View is exposed to any other classes.\nIn order to ensure that the \u0026ldquo;set\u0026rdquo; and \u0026ldquo;get\u0026rdquo; methods of the Model are only callable from the Interactor, it\u0026rsquo;s tempting to put the Model and the Interactor in a separate sub-package and leave those methods as \u0026ldquo;package-private\u0026rdquo;. But this would mean that the constructors and all of the other externally callable methods in those two classes would need to be public, which would expose them outside of the MVCI structure. So it\u0026rsquo;s probably best to leave everything in one package and use programmer discipline to ensure that methods are only called in the appropriate manner. This is made a little bit easier by the fact that the only the Controller has references to all three of the other components, while the View and the Interactor only have a reference to the Model. The Model, of course, has no references to any other components of the MVCI.\nEnabling \u0026ldquo;Save\u0026rdquo; Only When Data Has Been Changed # One situation which is fairly common and appears to be difficult with this approach is when you have a \u0026ldquo;Save\u0026rdquo; button that you only want enabled when the user has changed something. When using the value properties of the screen nodes to store the user data separate from the data model, in seems easier to be able to compare the original values (in the model) to the values on screen. However, Thomas Nields has written an excellent library called DirtyFX which handles this wonderfully.\nNormally, most models have ObjectProperty's which are instantiated using the SimpleObjectProperty implementation. Thomas has created a set of implementations which also allow the initial state of the property - a baseline - to be set and then it maintains an internal boolean property which indicates whether or not the current value of the property is different from the baseline - if it\u0026rsquo;s \u0026ldquo;dirty\u0026rdquo;.\nImplementing this in the example model is straight-forward:\npublic class MvciModel { private final DirtyStringProperty number = new DirtyStringProperty(\u0026#34;0\u0026#34;); private final BooleanProperty moreAllowed = new SimpleBooleanProperty(true); public String getNumber() { return number.get(); } public StringProperty numberProperty() { return number; } public void setNumber(String number) { this.number.set(number); this.number.rebaseline(); } public BooleanProperty moreAllowedProperty() { return moreAllowed; } public ObservableValue\u0026lt;Boolean\u0026gt; dataChanged() { return number.isDirtyProperty(); } } I\u0026rsquo;ve removed the setter for the moreAllowed field, since the property is bound in the constructor of the Interactor, and a bound property cannot be set. I\u0026rsquo;ve also removed the getter, since the Interactor doesn\u0026rsquo;t use it. The dataChanged() method is only there to expose the isDirty property of \u0026ldquo;number\u0026rdquo; to the interactor, while keeping the return value of numberProperty() to be StringProperty - in other words, not exposing the DirtyStringProperty implementation of the field to the rest of the system.\nNote that the setter for the \u0026ldquo;number\u0026rdquo; field now calls rebaseline(), which resets the baseline value to whatever the current value of the property is, after calling the set() method of the property. This works nicely with the idea that the setter is only called from the Interactor, and the View interacts with the property only through a binding, and therefore won\u0026rsquo;t invoke the rebaseline() method when the value in the TextField is changed.\nThe constructor of the Interactor now looks like this:\npublic MvciInteractor(MvciModel viewModel) { this.viewModel = viewModel; viewModel.moreAllowedProperty() .bind(Bindings.createBooleanBinding(() -\u0026gt; checkIfMoreAllowed() \u0026amp;\u0026amp; viewModel.dataChanged().getValue(), viewModel.numberProperty(), viewModel.dataChanged())); } Now the \u0026ldquo;moreAllowed\u0026rdquo; property will only be true if the number in the field is less than 21 or not actually a number, and has been changed since the the last time the addFive() method was invoked. DirtyFX also has a composite dirty property, which allows you to register other dirty properties with it, and will be true if any of the component properties are dirty.\nIn Summary # This example is simple because it lacks any deeper application behind it, and therefore no need to perform background processing to handle communication with a database or external API. If it did, then the simple Runnable to invoke the addFive() method would need to be changed to something more complex to allow the GUI to be modified prior to invoking the background job, perhaps showing some kind of progress indicator and disabling the button, and then to return the GUI back to it\u0026rsquo;s original state when the job was completed. The Controller would need to be enhanced to create a Task to run the background job, and then to invoke a second Interactor method to update the Model on the FXAT after the background job had been completed.\n","date":"11 May 2021","externalUrl":null,"permalink":"/javafx/techniques/mvci/2021-05-11-mvc_in_javafx/","section":"JavaFX","summary":"Model-View-Controller is generally accepted as a good way to structure an application with a user interface. While JavaFX contains all of the classes needed to implement MVC in a natural and seamless way, it doesn’t seem to be widely documented and there’s little evidence on the Web that many people have figured out how to do it properly. The fact that JavaFX supports Reactive programming makes a big difference to how you should implement MVC.\n","title":"Implementing MVC in JavaFX","type":"javafx"},{"content":"","date":"11 May 2021","externalUrl":null,"permalink":"/series/textformatter/","section":"Series","summary":"","title":"TextFormatter","type":"series"},{"content":" Introduction # Model - View - Controller is a design pattern that has become the defacto standard for building web sites and business applications worldwide. It can be used effectively with JavaFX, but how to do so properly is not very well understood or very well documented. MineSweeper is a simple game that can be used to illustrate how to implement MVC in JavaFX, and also to show how a lot of functionality can be packed into very simple code with JavaFX.\nThe structure of this application uses the Reactive nature of JavaFX in virtually all of its design aspects.\nRecently, I saw a question on StackOverflow from someone attempting to build a MineSweeper game in JavaFX. The question itself doesn\u0026rsquo;t matter - and it was later deleted by the person asking the question - but the sample code that was posted was extremely complicated and did not work very well.\nI wondered just how simple a MineSweeper program could be if it was written with a strict MVC approach. The nice thing about using MineSweeper as an example is that it has no outside API to interact with and no persistence layer, so that everything can be done on the FXAT. This way, we can concentrate on the MVC structure and how to implement it without have to complicate things with background processing and threading issues.\nMineSweeper - The Rules # Everybody probably already knows MineSweeper - it\u0026rsquo;s a game with a grid of grey squares, and when you click on a square it turns white to reveal:\nNothing - No mines are in any adjacent squares A Number - How many adjacent squares have mines in them A Mine - Boom! You lose The goal is to click on all of the squares that don\u0026rsquo;t contain a mine, leaving only the squares with mines in them unclicked.\nAnother important idea about the game-play is that it shouldn\u0026rsquo;t ever be left to chance alone at the beginning. So the first square you click cannot be a mine. Nor can it have any adjacent mines, as you wouldn\u0026rsquo;t yet have enough information to make a non-random choice about which square to click second. The main impact this has on designing the game is that the contents of the squares cannot be determined until the user clicks on the first square.\nA Quick Review of Model-View-Controller # Model-View-Controller is a design pattern that divides a system - or a component of a system - into three parts:\nThe Model This holds the data which is used by the View. In JavaFX, the model is composed of Observable objects and lists. The View The View is strictly the component which handles the display and user interaction elements of system. It has no business logic, and binds screen components to the Observable objects in the model. The Controller The Controller is responsible for instantiating the Model and the View, and communicating with the rest of the system. It is the only MVC component which has methods visible outside of the MVC structure. The Controller also contains all of the application logic. The idea is that the screen components and UI are a black box and could, theoretically at least, be swapped out for any number of designs without modifying the Controller or the Model. At the same time, the View does not contain any application logic, it\u0026rsquo;s strictly a user interface that delegates any application decisions back to the Controller.\nThe Components # This implementation of the game is built with two MVC constructs, the game board and the cells. A great deal of care is taken to ensure that all of the functionality is placed in the element where it belongs. This means that the Cell MVC is responsible for everything that goes on inside a cell, and the game board MVC is responsible for the game board activities and anything that involves multiple cells.\nThis design is especially interesting because it illustrates how a more complicated application with many layered and interdependent components could be constructed. Think of each MVC as a component, with the entirety of its API exposed through the Controller.\nThe Cell # The Cell is an autonomous, self-contained unit. Other than having a public method which will tell whether or not an (x,y) cell co-ordinate is one of its neighbours it isn\u0026rsquo;t particularly aware of existing in the context of a grid. It doesn\u0026rsquo;t have any functionality which is dependant on any other Cells, or the Game Board itself.\nThe CellModel # The CellModel is fairly simple, mostly a collection of boolean properties:\nclass CellModel { private final BooleanProperty clicked = new SimpleBooleanProperty(false); private final BooleanProperty isMine = new SimpleBooleanProperty(false); private final BooleanProperty isFlag = new SimpleBooleanProperty(false); private final StringProperty mineCount = new SimpleStringProperty(\u0026#34;\u0026#34;); public BooleanProperty clickedProperty() { return clicked; } public BooleanProperty isMineProperty() { return isMine; } public BooleanProperty isFlagProperty() { return isFlag; } public StringProperty mineCountProperty() { return mineCount; } } We need to know if the Cell contains a mine, if it\u0026rsquo;s been clicked, what number to display when it\u0026rsquo;s clicked (assuming it\u0026rsquo;s not a mine), and whether we should show the flag to indicate that it\u0026rsquo;s been right clicked. That\u0026rsquo;s all.\nI chose not to use delegate getters or setters for these properties because it wouldn\u0026rsquo;t add any extra control or make anything easier to understand given how simple the Controller is.\nThe CellView # The CellView needs to display some of 4 possible things:\nA gray or white rectangle as a background. And one of the following: A Flag A Mine A Number The best layout for the Cell is a StackPane, with the everything loaded up in it at the beginning, and the visibility of the components bound to the properties in the CellModel. The Rectangle needs to go at the bottom, but the other three components can go in any order as only one of them will be visible at a time:\nclass CellView extends StackPane { private static final Image mineImage = new Image(\u0026#34;/images/MineSmall.png\u0026#34;); private static final Image flagImage = new Image(\u0026#34;/images/Flag.png\u0026#34;); CellView(CellModel model, Consumer\u0026lt;MouseButton\u0026gt; clickConsumer) { Text mineCountText = new Text(\u0026#34;\u0026#34;); ImageView mineImageView = new ImageView(mineImage); ImageView flagImageView = new ImageView(flagImage); flagImageView.setFitHeight(24); flagImageView.setFitWidth(24); getChildren().addAll(createRectangle(model.clickedProperty()), mineCountText, mineImageView, flagImageView); mineCountText.textProperty().bind(model.mineCountProperty()); mineCountText.visibleProperty().bind(model.clickedProperty().and(model.isMineProperty().not())); mineImageView.visibleProperty().bind(model.clickedProperty().and(model.isMineProperty())); flagImageView.visibleProperty().bind(model.isFlagProperty().and(model.clickedProperty().not())); setOnMouseClicked(evt -\u0026gt; clickConsumer.accept(evt.getButton())); } private Rectangle createRectangle(ObservableBooleanValue clickedProperty) { Rectangle rectangle = new Rectangle(30, 30); rectangle.fillProperty() .bind(Bindings.createObjectBinding(() -\u0026gt; clickedProperty.get() ? Color.WHITE : Color.GRAY, clickedProperty)); rectangle.setStroke(Color.BLACK); return rectangle; } } The mine and the flag are just images. I wasn\u0026rsquo;t able to quickly find a small enough flag image, so I scaled it in the code when it was loaded into its ImageView. StackPane's centre everything in them by default, so there\u0026rsquo;s no need to fuss about with alignment either.\nThe Rectangle starts out gray, and then flips to white when the cell has been clicked, so the binding for that converts the boolean property into a Color via the Bindings library createObjectBinding(). For the other bindings, I\u0026rsquo;ve just used the Fluent Binding API since they are direct bindings of the model properties joined with and() and possibly modified with not().\nThe Cell Controller # The Controller for each Cell is just the Cell object itself. It instantiates the CellModel as a field, and then passes it to the constructor of the CellView:\npublic class Cell { private static final Random RANDOM = new Random(); private final int x; private final int y; private final CellModel model = new CellModel(); private final Region cellView; public Cell(int x, int y, BiConsumer\u0026lt;Integer, Integer\u0026gt; clickConsumer) { this.x = x; this.y = y; Consumer\u0026lt;MouseButton\u0026gt; viewClickConsumer = mouseButton -\u0026gt; { if (mouseButton.equals(MouseButton.SECONDARY)) { model.isFlagProperty().set(!model.isFlagProperty().get()); } else { model.clickedProperty().set(true); clickConsumer.accept(x, y); } }; cellView = new CellView(model, viewClickConsumer); } public Region getView() { return cellView; } public void setNum(long num) { model.mineCountProperty().set(num \u0026gt; 0 ? Long.toString(num) : \u0026#34;\u0026#34;); } public int getX() { return x; } public int getY() { return y; } public boolean isMine() { return model.isMineProperty().get(); } public boolean notMine() { return !isMine(); } public boolean isNeighbour(int testX, int testY) { if ((Math.abs(getX() - testX) \u0026lt;= 1) \u0026amp;\u0026amp; (Math.abs(getY() - testY) \u0026lt;= 1)) { return !((getX() == testX) \u0026amp;\u0026amp; (getY() == testY)); } return false; } public BooleanProperty clickedProperty() { return model.clickedProperty(); } public void setMineRandomly() { model.isMineProperty().set(RANDOM.nextInt(10) \u0026lt;= 1); } } One thing you\u0026rsquo;ll notice right away is the the \u0026ldquo;x\u0026rdquo; and \u0026ldquo;y\u0026rdquo; location of the cell aren\u0026rsquo;t part of the model! That\u0026rsquo;s because they have nothing to do with the CellView at all and they act more like domain objects in a business application. So they don\u0026rsquo;t belong in the View Model for the Cell.\nThe other thing to notice is the viewClickConsumer which is passed to the CellView. It would seem to be easier to just directly attach an EventHandler to the CellView with view.setOnMouseClicked(), so why not do that? This would violate the idea that the View is a black box by assuming that the design of the CellView makes it clickable itself. While the MineSweeper game generally assumes that mouse clicks are going to be the user interaction, it\u0026rsquo;s not cast in stone, and the implementation doesn\u0026rsquo;t have to make the whole CellView clickable. It\u0026rsquo;s possible it could be implemented with a pair of ToggleButtons, one for \u0026ldquo;flag\u0026rdquo; and one for \u0026ldquo;reveal\u0026rdquo;. We break that dependency by creating a Consumer, and passing it to the CellView via its constructor.\nThe viewClickConsumer still assumes that MouseButton is going to be sent back, but any implementation of CellView that doesn\u0026rsquo;t use mouse clicks can still convert its functionality to pass either MouseButton.PRIMARY or MouseButton.SECONDARY. This is maybe a bit of a kludge, and if I was making a large business application I\u0026rsquo;d probably create an Enum called CellAction and pass that back instead.\nThere\u0026rsquo;s surprisingly little logic inside the Cell. It really has only the following functions that aren\u0026rsquo;t getters or setters:\nIt defines a click consumer that handles both left and right clicks. It can determine if any given x,y combination is one of its neighbours. It can use a random function to set it\u0026rsquo;s isMine Property The notMine() method was added so that it could be access as a Method Reference in a Stream in the game Controller. It just makes that code look a little cleaner.\nThe MineSweeper Game Board # This is essentially the entire window for the game. There are three buttons to start (or restart) a game of various board sizes, the board itself, and some graphics to indicate win or lose.\nThe Model # The View Model for the game board is very simple, just 2 boolean properties:\nprivate final BooleanProperty loseProperty = new SimpleBooleanProperty(false); private final BooleanProperty winProperty = new SimpleBooleanProperty(false); Rather than create a separate Model class for this, these have been left as fields in the Controller. This was really just to make a point; they still comprise the Model but they don\u0026rsquo;t *have * to be put into a separate class if it doesn\u0026rsquo;t make sense. In this case, they can be passed to the View in its constructor without resulting in a ridiculously long parameter list.\nThe View # The main screen View is a VBox with two compound elements. The first is a StackPane which contains the main GridPane at the bottom and two other StackPanes, each containing an image for win or lose on top of it. These two StackPane\u0026rsquo;s are only partially opaque, and have a different background colour.\nThe second part of the VBox is an HBox with a set of Buttons which start new games at different board sizes. The entirety of the action of these buttons is handled by the View Controller via the Consumer passed to the View in its constructor.\nclass MineSweeperView extends VBox { private GridPane grid = new GridPane(); private Consumer\u0026lt;Integer\u0026gt; boardInitializer; MineSweeperView(Consumer\u0026lt;Integer\u0026gt; boardInitializer, ObservableBooleanValue gameLostProperty, ObservableBooleanValue gameWonProperty) { this.boardInitializer = boardInitializer; initialize(gameLostProperty, gameWonProperty); } private void initialize(ObservableBooleanValue mineClickedProperty, ObservableBooleanValue unclickEmptyProperty) { setSpacing(10); getChildren().addAll(setUpMainGrid(mineClickedProperty, unclickEmptyProperty), setUpButtonBox()); setPadding(new Insets(10)); } private Region setUpButtonBox() { return new HBox(10, createTypeButton(\u0026#34;Easy\u0026#34;, 10), createTypeButton(\u0026#34;Medium\u0026#34;, 16), createTypeButton(\u0026#34;Hard\u0026#34;, 25)); } private Region setUpMainGrid(ObservableBooleanValue gameLostProperty, ObservableBooleanValue gameWonProperty) { return new StackPane(grid, setUpWinLosePane(new ImageView(new Image(\u0026#34;/images/MineBig.png\u0026#34;)), Color.PINK, gameLostProperty), setUpWinLosePane(new ImageView(new Image(\u0026#34;/images/Win.png\u0026#34;)), Color.BLUEVIOLET, gameWonProperty)); } void addCell(int x, int y, Region cell) { grid.add(cell, x, y); } void clearView() { grid.getChildren().clear(); } private Pane setUpWinLosePane(ImageView imageView, Color colour, ObservableBooleanValue revealProperty) { Pane pane = new StackPane(); pane.setBackground(new Background(new BackgroundFill(colour, CornerRadii.EMPTY, Insets.EMPTY))); pane.setOpacity(0.6); pane.getChildren().add(imageView); pane.visibleProperty().bind(revealProperty); return pane; } private Node createTypeButton(String label, int newBoardSize) { Button button = new Button(label); button.setOnAction(evt -\u0026gt; boardInitializer.accept(newBoardSize)); return button; } } Virtually all of the code in this View is involved with setting up the layout. There are only two methods not related to this; one which clears the GridPane of all of its children, and one which adds a Node to the GridPane at specified row and column. These are also the only two non-private methods in the View, aside from the constructor. It is possible to avoid having these two public methods by expanding the Model to have enough information to populate or clear the GridPane, but it wouldn\u0026rsquo;t result in a cleaner design.\nThe Game Controller # The Game Controller has the code which launches everything, and the code which needs to look at more than one cell at a time, which as it turns out, isn\u0026rsquo;t much:\nclass MineSweeperController { private final BooleanProperty loseProperty = new SimpleBooleanProperty(false); private final BooleanProperty winProperty = new SimpleBooleanProperty(false); private final MineSweeperView view; private boolean initialized = false; private ArrayList\u0026lt;Cell\u0026gt; cells = new ArrayList\u0026lt;\u0026gt;(); private final BiConsumer\u0026lt;Integer, Integer\u0026gt; screenSizeHandler; MineSweeperController(BiConsumer\u0026lt;Integer, Integer\u0026gt; screenSizeHandler) { this.screenSizeHandler = screenSizeHandler; view = new MineSweeperView(this::setBoardSize, loseProperty, winProperty); } private void setBoardSize(int newBoardSize) { screenSizeHandler.accept((newBoardSize * 31) + 100, (newBoardSize * 31) + 32); reset(); loadBoard(newBoardSize); } public Region getView() { return view; } public void reset() { winProperty.unbind(); winProperty.set(false); loseProperty.unbind(); loseProperty.set(false); cells.clear(); initialized = false; } public void loadBoard(int boardSize) { view.clearView(); for (int i = 0; i \u0026lt; boardSize; i++) { for (int j = 0; j \u0026lt; boardSize; j++) { Cell cell = new Cell(i, j, this::consumeBoardClick); view.addCell(i, j, cell.getView()); cells.add(cell); } } } private void consumeBoardClick(int x, int y) { if (!initialized) { initializeBoard(x, y); initialized = true; } System.out.println(\u0026#34;***** (\u0026#34; + x + \u0026#34;, \u0026#34; + y + \u0026#34;)\u0026#34;); cells.stream() .filter(Cell::notMine) .filter(cell -\u0026gt; !cell.clickedProperty().get()) .forEach(cell -\u0026gt; System.out.println(\u0026#34;\\t\u0026#34; + \u0026#34;(\u0026#34; + cell.getX() + \u0026#34;, \u0026#34; + cell.getY() + \u0026#34;)\u0026#34;)); } public void initializeBoard(int pressedI, int pressedJ) { setMines(pressedI, pressedJ); cells.forEach(cell -\u0026gt; cell.setNum(getNeighbors(cell.getX(), cell.getY()).stream().filter(Cell::isMine).count())); winProperty.bind(createWinLoseBinding(Cell::notMine, Boolean::logicalAnd)); loseProperty.bind(createWinLoseBinding(Cell::isMine, Boolean::logicalOr)); } private BooleanBinding createWinLoseBinding(Predicate\u0026lt;Cell\u0026gt; predicate, BinaryOperator\u0026lt;Boolean\u0026gt; reductionOperator) { List\u0026lt;BooleanProperty\u0026gt; clickProperties = cells.stream().filter(predicate).map(Cell::clickedProperty).collect(Collectors.toList()); return Bindings.createBooleanBinding(() -\u0026gt; clickProperties.stream() .map(BooleanProperty::get) .reduce(clickProperties.get(0).get(), reductionOperator), clickProperties.toArray(new BooleanProperty[0])); } private void setMines(int pressedI, int pressedJ) { cells.stream() .filter(cell -\u0026gt; !cell.isNeighbour(pressedI, pressedJ)) .filter(cell -\u0026gt; !((cell.getX() == pressedI) \u0026amp;\u0026amp; (cell.getY() == pressedJ))) .forEach(Cell::setMineRandomly); } public List\u0026lt;Cell\u0026gt; getNeighbors(int x, int y) { return cells.stream().filter(cell -\u0026gt; cell.isNeighbour(x, y)).collect(Collectors.toList()); } } Setting Up the Board # This is really simple, just use two nested loops for the (x,y) coordinates of the board, instantiate a Cell for each iteration and pass it to the View to do something with it. What\u0026rsquo;s really interesting is that you don\u0026rsquo;t need a logical grid structure to store the Cell elements for doing any later processing. A simple List\u0026lt;Cell\u0026gt; will do. Since each Cell knows its own (x,y) coordinates, it can supply a method which will tell if any given (x,y) coordinate is one of its neighbours.\nThe original sample code in the StackOverflow question had a matrix: Cell[][] board that held all of cells in a grid. Then the board was traversed with two nested loops any time all of the cells needed to be checked. Replacing this with a List means that the nested loops can be simplified to simple Stream operations.\nThe First Click # The biggest complication in the game is that the first cell clicked has to be empty white. This means that none of the cell contents can be determined until that first cell has been clicked. For this reason, the game board controller passes a BiConsumer to the Cell constructors to handle the (x,y) location of the click. That BiConsumer calls consumeBoardClick() which will do nothing if it isn\u0026rsquo;t the first click.\nInitializing the board is by necessity a two-pass operation. Pass 1 visits each cell, and if it isn\u0026rsquo;t the first cell clicked or one of its neighbours, then tell it to randomly set its isMine property. Pass 2 visits each cell, gets all of its neighbours and Streams them, filtering for the ones with isMine set to true. Then it counts them up and sets the cell\u0026rsquo;s number based on that.\nThe Win and Lose Properties # The last step in initializing the board is to bind the two win/lose properties to the cells. Let\u0026rsquo;s have a look a the conditions for \u0026ldquo;win\u0026rdquo; and \u0026ldquo;lose\u0026rdquo;\nWin The player wins when **all ** of the cells with the isMine property set to false have been clicked. Lose The player loses when any of the cells with the isMine property set to true have been clicked. Determining win or lose at any given time is simple; Stream a list of cells and check to see if any or all of them have been clicked. So we need two lists of cells, one with isMine set to true, and the other with false. However, we\u0026rsquo;re not really interested in the cells, just the isMine property contained in each one. We can use a map() function to transform the Stream of Cell into a Stream of BooleanProperty, and then collect them into a list.\nWe\u0026rsquo;re going to use the Bindings library to create this binding:\npublic static BooleanBinding createBooleanBinding(Callable\u0026lt;Boolean\u0026gt; func, Observable... dependencies) That\u0026rsquo;s straight from the JavaDocs for Bindings. The first parameter is just a Supplier, which is a functional interface that doesn\u0026rsquo;t take any parameters but returns a value of the type specified - in this case a Boolean. We just need to write a supplier that determine if the player has won or lost at the time that it is called. It\u0026rsquo;s important to note that this Supplier isn\u0026rsquo;t necessarily involved with properties and observables, it\u0026rsquo;s just ordinary Java that calculates a boolean value somehow.\nThe second parameter is an array of Observable objects that will trigger a recalculation of the Boolean whenever any one of them changes. For these cases, we\u0026rsquo;ll have a List of BooleanProperty that we can convert into an array.\nLet\u0026rsquo;s see how this works:\nprivate BooleanBinding createWinLoseBinding(Predicate\u0026lt;Cell\u0026gt; predicate, BinaryOperator\u0026lt;Boolean\u0026gt; reductionOperator) { List\u0026lt;BooleanProperty\u0026gt; clickProperties = cells.stream().filter(predicate).map(Cell::clickedProperty).collect(Collectors.toList()); return Bindings.createBooleanBinding(() -\u0026gt; clickProperties.stream() .map(BooleanProperty::get) .reduce(clickProperties.get(0).get(), reductionOperator), clickProperties.toArray(new BooleanProperty[0])); } The first step is to create a List\u0026lt;BooleanProperty\u0026gt; by streaming the entire list of cells, filtering them based on isMine() or notMine() and then extracting the clickedProperty() in a map function.\nTo determine the win/lose condition, the list of BooleanProperty will be Streamed, then the current value will be determined by calling the get() method of the property, and the Stream will be reduced to a single Boolean value by applying a Boolean operator between all of the elements. For this, \u0026ldquo;Any\u0026rdquo; means that we need to use OR as the operator, and \u0026ldquo;All\u0026rdquo; means that we need to use AND.\nEven if you have trouble following exactly how it works, the calls to this method are pretty clear in their intent:\nwinProperty.bind(createWinLoseBinding(Cell::notMine, Boolean::logicalAnd)); loseProperty.bind(createWinLoseBinding(Cell::isMine, Boolean::logicalOr)); One Ugly Part # The only problem with the win/lose logic is that it isn\u0026rsquo;t relevant in the time between the reset of the grid for a new game and the first cell click of that game. It needs to be disabled for that time span so we have the following code:\npublic void reset() { winProperty.unbind(); winProperty.set(false); loseProperty.unbind(); loseProperty.set(false); cells.clear(); initialized = false; } This seems a bit clumsy, but the only alternative is to turn the initialized field into a BooleanProperty and then tie it into the BooleanBinding built for the winProperty and loseProperty. But doing this wouldn\u0026rsquo;t make anything clearer, and the bindings still need to be rebuilt and bound to those properties after the first click.\nThe Main Class # The Main class is only of interest here because has some code which resizes the window according to the size of the board the game is being played on:\npublic class MineSweeper extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) throws Exception { primaryStage.setTitle(\u0026#34;Minesweeper\u0026#34;); BiConsumer\u0026lt;Integer, Integer\u0026gt; screenSizeHandler = (height, width) -\u0026gt; { primaryStage.setHeight(height); primaryStage.setWidth(width); }; Scene value = new Scene(new MineSweeperController(screenSizeHandler).getView()); primaryStage.setScene(value); primaryStage.show(); } } You can see that the Main class passes a BiConsumer to the constructor of the Controller to allow it to change the size of the window. In this way, the main screen Controller needs to have no knowledge of anything outside it\u0026rsquo;s scope. It has a dependency declared in its constructor that requires that any parent class instantiating the Controller has to provide some sort of screen size handling\nThe images # If you want to copy this code and run it yourself with changing things, you\u0026rsquo;ll need the images:\nSummary # Once the game is underway, there\u0026rsquo;s only really two lines of code that are directly executed when a cell is clicked:\nif (evt.getButton().equals(MouseButton.SECONDARY)) { model.isFlagProperty().set(!model.isFlagProperty().get()); } else { model.clickedProperty().set(true); } Everything else is either layout or setup, which largely consists of setting model values and creating bindings between the view and the model. This the essence of programming with JavaFX: virtually all of the JavaFX code is setup - creating the layout, the bindings and the listeners - and it only amounts to a small proportion of the overall code.\nLook at where the bulk of the code in this application resides. It\u0026rsquo;s in the Controllers, after the getView() method. Sure, a lot of it deals with observables and properties, but it deals with them as data, and the way that they are tied back to the GUI is not considered in that code at all.\nAnother thing to note is just how small the entire application is. The Cell MVC has about 60 lines of code, and the Game MVC has about 100 lines of code. To me, that doesn\u0026rsquo;t seem like much code at all considering what it does.\nUsing the MVC design pattern here really allows the code to become easier to understand and to develop. Having a clear picture of exactly what information is needed to make the GUI work is a key step to creating a clean design for the GUI, and the need to define a Model helps to understand that. Having a complete separation between layout and application logic is critical to implementing MVC cleanly, and the result is that a of complexity that you\u0026rsquo;d have without it just drops out of the application.\nIn terms of having a multi-layered MVC construction, note how the Game Board MVC has no knowledge of or dependencies on the structure of the the CellView. To the Game Board MVC, and to the MineSweeperView, it\u0026rsquo;s just a Region. Once the Cells have been instantiated and the CellViews have been passed on to the MineSweeperView, the MineSweeperController only deals with the Cells as gameplay objects. The visual aspect of a Cell can be ignored by the MineSweeperController gameplay logic.\n","date":"3 May 2021","externalUrl":null,"permalink":"/javafx/projects/minesweeper/","section":"JavaFX","summary":"MineSweeper!  Everyone’s favourite time-waster Windows game.  Implemented in JavaFX showing how the reactive nature of JavaFX can be used with MVC to create a game.","title":"MineSweeper in JavaFX","type":"javafx"},{"content":"Many programmers look for on-line tutorials and examples to find out how to code common JavaFX structures and techniques. Unfortunately, not all the code you\u0026rsquo;ll find on the web is high quality and there are a number of bad techniques that have been replicated over and over again on numerous sites. One of these is the structure that I call the \u0026ldquo;Dispatch EventHandler\u0026rdquo;.\nA Real Example From the Web # There was an answer to a question on StackOverflow recently about adding actions to Buttons in JavaFX.\nThe solution was posted is in the listing below. To be fair, the person posting the answer was just solving the problem as asked, and kept the same overall approach that the person who posted the question had taken. I\u0026rsquo;ve used the code from the answer instead of from the question because this code actually runs and produces a partially correct result:\npublic class LatinTranslator extends Application { private Label myLabel; public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { myLabel = new Label(); Button leftButton = new Button(\u0026#34;Sinister\u0026#34;); Button centerButton = new Button(\u0026#34;Medium\u0026#34;); Button rightButton = new Button(\u0026#34;Dexter\u0026#34;); centerButton.setAlignment(Pos.CENTER); leftButton.setOnAction(new ButttonClickHandler()); centerButton.setOnAction(new ButttonClickHandler()); rightButton.setOnAction(new ButttonClickHandler()); VBox Vbox = new VBox(20, myLabel, leftButton, centerButton, rightButton); Scene scenebox = new Scene(Vbox, 300, 500); Vbox.setAlignment(Pos.CENTER); primaryStage.setScene(scenebox); primaryStage.setTitle(\u0026#34;Latin Translator\u0026#34;); primaryStage.show(); } class ButttonClickHandler implements EventHandler\u0026lt;ActionEvent\u0026gt; { @Override public void handle(ActionEvent event) { Object source = event.getSource(); if (source instanceof Button) { Button button = (Button) source; String text = button.getText(); if (\u0026#34;Sinister\u0026#34;.equals(text)) { myLabel.setText(\u0026#34;left\u0026#34;); } } } } } I\u0026rsquo;ve seen this approach a lot. I wrote code like this when I started out with JavaFX.\nThis is probably copypasta from the early days of JavaFX 2, most likely starting with a tutorial on creating a menu. The same type of structure was used, with the menu text being used to decide which action to take in the ButtonClickHandler code. It\u0026rsquo;s possible that this was a hold-over from Swing, where this approach was standard for building JMenu\u0026rsquo;s.\nSearching for examples today, I couldn\u0026rsquo;t find any online tutorials for JavaFX menus that used this technique, which is good. However, the fact that it would pop up in a recent StackOverflow question posted by someone who was probably new to JavaFX seems to indicate that there are examples out there for beginners to find.\nThe Problem # It\u0026rsquo;s the ButtonClickHandler class that is the problem. It\u0026rsquo;s a \u0026ldquo;Dispatch EventHandler\u0026rdquo;. The idea is that you create a single EventHandler that will called by a number of controls, and it will somehow figure out which one called it, and what it should do for each one.\nIt\u0026rsquo;s a very round-about way to program this. It\u0026rsquo;s worse than going around three sides of a square - more like going around seven sides of an octagon - and it introduces a level of complexity that simply doesn\u0026rsquo;t serve any purpose at all. The person writing the answer in this example didn\u0026rsquo;t even bother to put in the actions for two out of the three buttons; probably because it was too much work and didn\u0026rsquo;t contribute to the solution.\nUsing this approach treats the Buttons like data, and ignores the fact that Button is a Control with the ability to perform actions on its own. This, even though the program invokes the Buttons\u0026rsquo; OnAction() method!\nAnother big issue with this code is that it tightly couples the display elements to the action elements. What happens if you discover that you\u0026rsquo;ve spelled one of the Latin words on the buttons incorrectly? Now you have to correct both the button and the action code. Furthermore, if you\u0026rsquo;re coming at this as a maintenance programmer to fix the spelling error, you need to understand that the button text isn\u0026rsquo;t just display but is actually used as data to control the logic flow somewhere else. That\u0026rsquo;s an easy thing to miss!\nA Better Approach # The program could be much simpler:\npublic class Translator extends Application { private final Label myLabel = new Label(); public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { VBox vBox = new VBox(20, myLabel, createButton(\u0026#34;Sinister\u0026#34;, \u0026#34;Left\u0026#34;), createButton(\u0026#34;Medium\u0026#34;, \u0026#34;Middle\u0026#34;), createButton(\u0026#34;Dexter\u0026#34;, \u0026#34;Right\u0026#34;)); vBox.setAlignment(Pos.CENTER); primaryStage.setScene(new Scene(vBox, 300, 500)); primaryStage.setTitle(\u0026#34;Latin Translator\u0026#34;); primaryStage.show(); } private Node createButton(String buttonText, String labelText) { Button button = new Button(buttonText); button.setOnAction(evt -\u0026gt; myLabel.setText(labelText)); return button; } } First of all, this new version is much smaller. There are roughly half as many lines of code compared to the original version - especially considering that this version handles the translations for all of the Buttons, not just the first one. Generally speaking, less code is going to be easier to read and to understand; and that certainly is the case here.\nI\u0026rsquo;ve also stripped out some unneeded variables and their initialization to streamline and simplify the code. This contributes to the reduction in lines of code.\nI\u0026rsquo;ve put the actual instantiation of the Buttons into its own method here for a couple of reasons:\nFirst, the code in that method would otherwise be repeated over three times, and you\u0026rsquo;d need to create a variable for each one. If you had more than three buttons, the situation would just get worse.\nSecondly, note that the method returns \u0026ldquo;Node\u0026rdquo;, not button. This is an important distinction because it really emphasizes that to the layout it\u0026rsquo;s just a generic screen element. Everything that makes it different and unique is contained within it, and the layout code has no need to even know that this is a button with an action. The nature of the button is now completely decoupled from the layout of the GUI, as it should be.\nThere are Even More Benefits # Given that the context for this code seems to be an application for showing translations of words from one language to another, it seems unlikely that you\u0026rsquo;d want to hand code each word and translation on screen after screen after screen. In that case, you\u0026rsquo;d probably have some database of translations, and you\u0026rsquo;d pass some subset of your database to the screen for display.\nTo simulate that, we\u0026rsquo;ll add a method that will create a Map\u0026lt;String, String\u0026gt; of words and translation to provide our database results. Then we\u0026rsquo;ll stream that Map and turn it into a list of Buttons that can be put in the VBox:\npublic class Translator extends Application { private final Label myLabel = new Label(); public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { VBox vBox = new VBox(20, myLabel); vBox.getChildren() .addAll(initializeData().entrySet() .stream() .map(entry -\u0026gt; createButton(entry.getKey(), entry.getValue())) .collect(Collectors.toList())); vBox.setAlignment(Pos.CENTER); primaryStage.setScene(new Scene(vBox, 300, 500)); primaryStage.setTitle(\u0026#34;Latin Translator\u0026#34;); primaryStage.show(); } private Node createButton(String buttonText, String labelText) { Button button = new Button(buttonText); button.setOnAction(evt -\u0026gt; myLabel.setText(labelText)); return button; } private Map\u0026lt;String, String\u0026gt; initializeData() { Map\u0026lt;String, String\u0026gt; results = new HashMap\u0026lt;\u0026gt;(); results.put(\u0026#34;Sinister\u0026#34;, \u0026#34;Left\u0026#34;); results.put(\u0026#34;Medium\u0026#34;, \u0026#34;Middle\u0026#34;); results.put(\u0026#34;Dexter\u0026#34;, \u0026#34;Right\u0026#34;); return results; } } Philosophically, what\u0026rsquo;s important here is that the key-value pairs are turned into independent Button controls work on their own. These can then be placed anywhere on the scene without requiring any modification of anything else.\nYou can also see how the createButton() method does not need any modification to work as a translation routine to turn each key-value pair into a JavaFX Node.\nOne Last Fix # There\u0026rsquo;s one dependency on the screen that is still hanging around, and that\u0026rsquo;s the direct update of myLabel.setText(). This is forcing the myLabel element to be a field in the class. It also forces each Button to be aware of the presence of the myLabel screen element, which is more needless coupling at the GUI layout level.\nThis can be fixed by creating a StringProperty to hold the translation, and then binding the Text property of myLabel to that StringProperty. This may seem like nit-picking, but if you were building this screen with an MVC structure, then both the Map of words and translations and the translation for the word associated with a clicked button would be defined in the Model. This would meant that the buttons and the translation label are coupled to the Model, as they should be, and not to each other:\npublic class Translator extends Application { private final StringProperty translation = new SimpleStringProperty(\u0026#34;\u0026#34;); public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { Label myLabel = new Label(); myLabel.textProperty().bind(translation); VBox vBox = new VBox(20, myLabel); vBox.getChildren() .addAll(initializeData().entrySet() .stream() .map(entry -\u0026gt; createButton(entry.getKey(), entry.getValue())) .collect(Collectors.toList())); vBox.setAlignment(Pos.CENTER); primaryStage.setScene(new Scene(vBox, 300, 500)); primaryStage.setTitle(\u0026#34;Latin Translator\u0026#34;); primaryStage.show(); } private Node createButton(String buttonText, String translationText) { Button button = new Button(buttonText); button.setOnAction(evt -\u0026gt; translation.set(translationText)); return button; } private Map\u0026lt;String, String\u0026gt; initializeData() { Map\u0026lt;String, String\u0026gt; results = new HashMap\u0026lt;\u0026gt;(); results.put(\u0026#34;Sinister\u0026#34;, \u0026#34;Left\u0026#34;); results.put(\u0026#34;Medium\u0026#34;, \u0026#34;Middle\u0026#34;); results.put(\u0026#34;Dexter\u0026#34;, \u0026#34;Right\u0026#34;); return results; } } And that\u0026rsquo;s all. This has gone from something clumsy enough that the original author couldn\u0026rsquo;t be bothered to complete the coding for all of the Buttons in the sample to something so simple that it was trivial to simulate how it would work integrated with a database. Most importantly, by making the Buttons stand-alone controls, the layout of the screen is now completely decoupled from the actions of the screen. Those Buttons don\u0026rsquo;t even have to be Buttons anymore, and myLabel could change to some other kind of Node.\n","date":"19 April 2021","externalUrl":null,"permalink":"/javafx/techniques/dispatchEventHandler/","section":"JavaFX","summary":"Stop treating your JavaFX controls like data and passing them global EventHandlers.","title":"JavaFX Anti-Pattern: The Dispatch EventHandler","type":"javafx"},{"content":"","externalUrl":null,"permalink":"/series/absolute-beginners-guide/","section":"Series","summary":"","title":"Absolute Beginner's Guide","type":"series"},{"content":"Content under construction\n","externalUrl":null,"permalink":"/javafx/elements/fxat/","section":"JavaFX","summary":"Content under construction\n","title":"Empty Page","type":"javafx"},{"content":"Content under construction\n","externalUrl":null,"permalink":"/javafx/elements/listeners/","section":"JavaFX","summary":"Content under construction\n","title":"Empty Page","type":"javafx"},{"content":" Introduction # So far we\u0026rsquo;ve built an application that does little, but demonstrates a lot. Now we\u0026rsquo;re going to write one of the most common types of application out there - CRUD (Create, Retrieve, Update and Delete).\nOur example is going to be something super common, some sort of a customer database where we can add new customers, look them up in a number of ways, and then modify or delete them.\nSoftware Architecture # Most software applications have a structure that looks something like this:\nThe top three horizontal boxes (in green hues) represent the GUI elements of the application. Individual Labels, TextFields, ComboBoxes and such are at the top, then actual layouts that contain them. The bottom layer of the GUI is the part that contains the mechanics. These are the things that control actions from the GUI, instantiate the GUI and, of course, the State.\nThe blue layer is the business logic layer. This is the part that contains the logic which is specific to whatever function the GUI supports.\nUnder that, in pink colours are the \u0026ldquo;external\u0026rdquo; elements that connect your application to other applications, the persistence layer and calls to external API\u0026rsquo;s. The \u0026ldquo;Brokers\u0026rdquo; layer is responsible for fetching data from these other services and packaging the results into \u0026ldquo;domain objects\u0026rdquo;, which can then be dealt with by the Business Logic layer.\nFeatures and Scope # The most important thing, is that when you develop an application you implement one feature at a time, and feature always cuts a vertical slice through the application structure - from screen widget right through to external API call. When you expand the scope of a feature, you are cutting a wider slice through the entire application structure.\nWhat this means is that you can\u0026rsquo;t just add a TextField to a screen and not create the rest of the structure to handle the data in that TextField right down to your database. On on the other end, if you\u0026rsquo;re also creating the database, you can\u0026rsquo;t add fields to it without creating the Brokers and DAO\u0026rsquo;s and business logic to pass it to the GUI and put it on the screen.\nYou build your application by starting with a single feature, no matter how small, and implement it from top to bottom in the application structure. And all the way, you stick to YAGNI (You Ain\u0026rsquo;t Gonna Need It) as your guiding principle. This means that you ONLY build things directly required to implement the feature that you\u0026rsquo;re implementing. No peeking ahead.\nAs you continue to build your application, you can expand the scope of a feature, or add new features (very often this is just a matter of semantics). All the while, making sure that you build out each feature from top to bottom of the application structure, and only building the things you need.\nThis is the process we\u0026rsquo;re going to follow to create our Customer Database application.\nTesting # The unfortunate truth is that GUI\u0026rsquo;s are problematic to test. There are automated tools, but they are complicated and difficult to set up. The best thing to do is test by hand.\nHowever, the rest of your system can, and should, be tested, and if your architecture allows for a clean separation between the GUI and everything else (and ours will), you can do unit testing easily.\n","externalUrl":null,"permalink":"/javafx/beginners/crud/","section":"JavaFX","summary":"Introduction # So far we’ve built an application that does little, but demonstrates a lot. Now we’re going to write one of the most common types of application out there - CRUD (Create, Retrieve, Update and Delete).\n","title":"Moving On - Building A CRUD Application","type":"javafx"},{"content":" A Short Introduction # This is a guide to help you learn how to build JavaFX applications the right way. It\u0026rsquo;s not a reference guide, but an instructional guide intended to be read in order. Depending on your learning style, it\u0026rsquo;s probably a good idea to follow along with the guide in your IDE, trying out the examples and experimenting with some of the concepts along the way.\nI\u0026rsquo;ve tried to keep each part short and easily digestible, so that you can stop at the end of any part and pick later from the next part.\nMy hope is that you fully understand the content of each part before moving on to the next. If you are confused about anything you can usually research in on the web, or find it in the JavaDocs for the components involved. If you are really stuck, you can send me an email and I\u0026rsquo;ll help. In any event, each part of this guide is written with the assumption that the content that preceded it has been fully understood.\nWhat We\u0026rsquo;ll Build # To start with, we\u0026rsquo;ll build a \u0026ldquo;throw-away\u0026rdquo; application to learn some basic concepts. This will start out as a simple \u0026ldquo;Hello World\u0026rdquo; application that we\u0026rsquo;ll add some styling and user interaction to. Then we\u0026rsquo;ll put it aside and build a real application, a \u0026ldquo;Create, Retrieve, Update and Delete\u0026rdquo; (CRUD) application for a simulated customer database.\nJavaFX as a Reactive Framework # This tutorial series concentrates on using JavaFX as a \u0026ldquo;Reactive\u0026rdquo; GUI framework.\nWhat is a Reactive framework?\nFrom WikiPedia:\nIn computing, reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change\u0026hellip;\nFor example, in an imperative programming setting, a := b + c would mean that a is being assigned the result of b + c in the instant the expression is evaluated, and later, the values of b and c can be changed with no effect on the value of a. On the other hand, in reactive programming, the value of a is automatically updated whenever the values of b or c change, without the program having to explicitly re-execute the statement a := b + c to determine the presently assigned value of a.\nOkay, but what does this get you?\nIn a word, simplicity.\nNow your GUI is designed purely with the intent to reflect State at any given time in the best possible way. There is no code anywhere in your GUI that concerns itself with application logic or business rules or any of that.\nAt the other end, the business logic is concerned simply with maintaining and reacting to State without any idea of how it impacts the GUI.\nIt\u0026rsquo;s much easier to see it in practice than to describe it. In this series, we\u0026rsquo;ll start right from the beginning utilizing the tools and features in JavaFX that allow Reactive designs to be implemented. You\u0026rsquo;ll see that it\u0026rsquo;s not complicated, and a natural way to build an application with JavaFX.\nCompanion Project # In order to make it easier to follow along at home, all of the source code from this series has been included in a companion project available on GitHub\u0026hellip;\nGo To the Companion Project\nThe source code is divided up into packages, each package containing the code as of the end the part of the series to which it corresponds. So the package ca.pragmaticcoding.beginners.part2 contains the code as described in part2 of this series.\nIn this way, you can see the whole application at each stage of its evolution, you can run it for yourself and see how it works. You can change code. You can break code. You can try your own ideas and experiment as much as you like.\nOn To It! # Let\u0026rsquo;s get started!\n","externalUrl":null,"permalink":"/beginners/intro/","section":"JavaFX","summary":"Introducing a course for programmers starting out to learn JavaFX","title":"The Absolute Beginners Guide to Reactive JavaFX","type":"javafx"}]