Shutting Down from the OSX Application Menu

On OS X, there are two kinds of menus:

  1.   Application-specific ones you define yourself
  2.   The Application menu all applications get from OS X
The Application Menu

This is a non-issue on Linux and Windows, as there is no application menu. You create your own “quit application” menu item in JavaFX to shutdown you application and that’s it.

Unfortunately, JavaFX has limited support for configuring or using the OS X application menu. So what can you do with the application menu on OS X?

  1. Set the menu name (via tools like the javapackager tool)
  2. Process some events and add handlers via Apple specific JDK packages (via com.apple.eawt package)
  3. Use the JavaFX stage.setOnCloseRequest to handle the quit event

I will describe how to add a CloseRequest handler to application’s primary stage, as this took me the longest time to figure out how to do. This will enable you to shutdown your JavaFX Application when it has been installed as a real OS X Application. Once you know how, it’s dead simple. The only reason for this blog post is that it took me longer than expected to find this out. Maybe it was a bad day, or maybe I’m just an idiot. Either way, if I had some trouble getting to the bottom of it, no doubt someone else did as well.

In my flimsy defence, I had only done something similar in Swing before, and a search on StackOverflow resulted in questions related to, but not exactly answering the question.

Here is the logic for your main application class. That’s it. Embarrassing really.

 override fun start(stage: Stage) { 
 ...
  stage.setOnCloseRequest { 
    shutdown() 
  } 
  ...
}

fun shutdown() { 
  // your additional shutdown logic
  Platform.exit()
}

JavaFX appears to know which operating system is running and automatically uses the system menu bar if possible (unlike Swing). So, no need for code like this anymore:

if(System.getProperty("os.name").contains("mac"))
   menuBar.isUseSystemMenuBartrue 
else
   menuBar.isUseSystemMenuBarfalse 

Other Implications

I took a look at a few other OS X applications, and confirmed they only had a Quit Application menu item in the application menu, and nowhere else. I spend so much time hitting the ⌘-Q key (a habit learned way back in 1985), I never pay much attention to the quit menu items.

So, if you have a ‘Quit’ menu item in you JavaFX menu (expected for operating systems other than OS X), don’t show it on OS X – you only need it for Windows and Linux versions.

JavaFX, Kotlin and Spring Framework

This is a brief post before the Easter break. In a spasm of productivity, I wrote a sample application which upgrades my previous application with multiple controllers to setup application services as Spring beans. These are injected into controllers as required.

The controllers delegate work to these service beans, which may be shared between controllers.

Not finished yet, but the sample application works.

Decoupled Controllers and a Simple Event-Driven Design

app3 in Github

This post is about using multiple controllers inside an application, whose composition is determined dynamically, so that all the FXML files are completely independent of each other. This differs a bit from the idea of nested controllers where the application FXML ‘includes’ component FXML files via the <fx:include> tag. In practice, I don’t think there is much between them. If in doubt, go with the nested controller option, as it’s a bit simpler.

I first discovered the method detailed in this post through ignorance, before realising nested controllers were even possible. I still prefer my way over nested controllers, but I am weird.

App3 in the repository allows you to drag files into a ListView which then fires events processed by other controllers to update UI elements. This app has 4 controllers, one ‘application’ controller which manages the while UI on  behalf of the application, and three specific controllers:

  • FileListController – Handles a list of files, and supports dragging from the desktop
  • FilePropertiesController – responsible for displaying a single file’s properties
  • FileDataController – responsible for displaying a single file’s contents.

All the File* controllers communicate via a simple set of File Events, managed by Google Guava’s EventBus. To keep the sample code manageable, the events only flow one-way from the FileListController to the other controllers.

For complex applications, it’s best to partition the user interface elements into separate controllers, each controlling a particular set of functionality. How you slice and dice your user interface is a strictly personal affair. Once you do that, you realise that each FXML-controller pairing lives in splendid isolation from the rest of your application (assuming you don’t stuff your controllers with fields imported from everywhere).

This quickly becomes an issue for application-wide functions that span multiple views. Here is where an events-driven approach comes to the rescue.

I will discuss a simpler architecture for my first post on multi-controller applications, whereby controller created events are published to other events subscribers, who also happen to be controllers. Future posts will address more realistic layered architectures and how these can be made to use events.

Key Points

  • A single event bus for the entire application
  • No ‘service’ or ‘logic’ layer for controllers to delegate to
  • Controllers are decoupled from each other
  • Controllers are decoupled from FXML

Key Point – A Single Event Bus

For application-wide messaging, a single bus is all that is necessary. Some complex controllers (dialogs for example) may require their own dedicated event bus, but I won’t discuss that in this post.

The event bus is initialised in the ApplicationController – which is the top level controller managing the application UI and all other controllers. All the other controllers are constructed with this single EventBus object.

class ApplicationController { 
   ...
 ...
   // this bus is used application wide
   private val applicationEventBus = EventBus()
   private val fileListController = FileListController(applicationEventBus)
   private val fileDataController = FileDataController(applicationEventBus)
   private val filePropertiesController = FilePropertiesController(applicationEventBus)
   ...
}

Key – No Service/Logic Layer

For this simple example, there is no service or logic layer, as the focus of this application is to demonstrate how to load multiple controllers and how to use an basic event-driven design for controller communication. Any real application would use controllers that delegate to some sort of service layer.

Key – Controllers Are Decoupled From Each Other

Since controllers only interact via event messages, the only information that passed between them is the event itself (“A File was Added”) and any model object inside the event. Events can contain data, but this should be part of the application domain/data model and NOT controller specific.

Key – Controllers Are Decoupled From FXML

Defining the controller class in the FXML mandates a default constructor, requires a setter based dependency injection approach. This works, but I prefer constructor based dependency injection to ensure controllers are always in a consistent state – one less thing to get wrong. This makes unit testing controllers more straightforward as well.

Interestingly, this could also mean one controller is mapped dynamically to multiple FXML files – something I have never needed to do.

The ApplicationController class has a simple method setupController() for loading FXML files and linking them to a controller. This is done from the JavaFX method initialize().

@FXML
fun initialize() {
    ...
    val fileListPane = setupController("/app3/fileList.fxml", fileListController)
    val filePropertiesPane = setupController("/app3/fileProperties.fxml", filePropertiesController)
    val fileDataPane = setupController("/app3/fileData.fxml", fileDataController)
    ...
}

/**
* Utility function to load FXML and link it to its controller
*/
private fun setupController(fxmlPath: String, controller: EventAwareController): Pane {
   val loader = FXMLLoader(javaClass.getResource(fxmlPath))
   loader.setController(controller)
   return loader.load<Pane>()
}

Code Setup

Event Bus – Controller Registration

Each controller inherits from the EventAwareController class whose function is to store a reference to the event bus (for event publishing) and to register the controller with the evenBus (for event subscribing). Inheritance is just one way of doing this, but enables all subclasses to publish events via the EventBus.post() method.

abstract class EventAwareController(val eventBus: EventBus){ 
   init{
      eventBus.register(this)
   }
}

Event Definitions

The events themselves are defined as standalone classes which both inherit from an abstract SingleFileEvent:

abstract class SingleFileEvent(val file: File) 

class FileAddedEvent(file: File) : SingleFileEvent(file)

class FileSelectedEvent(file: File) : SingleFileEvent(file)

If necessary, all events can implement some sort of event interface as well. Up to you. I generally use different, layered event class hierarchies to enable event propagation and translation between application layers.

Event Handlers (Subscribers)

In this application, all the event handlers are controllers. For each event of interest, a controller implements a subscriber method for that event type, annotated with the @Subscribe annotation from the Guava Libraries Event Bus.

In the example below, a single controller handles two types of events, which happen to have the same implementation. 

@Subscribe
fun handleFileAdded(e: FileAddedEvent) {
   println("FileDataController processing FileAddedEvent")
   loadAndDisplayContents(e.file)
}

@Subscribe
fun handleFileSelectionChanged(e: FileSelectedEvent) {
   println("FileDataController processing FileSelectedEvent")
   loadAndDisplayContents(e.file)
}

Since both are SingleFileEvent subclasses, one handler could have processed both events, given they both do the same thing, but I think having them split is a bit neater.  Here is a possible handler for all types of SingleFileEvent events.

@Subscribe
fun imaginaryHandleAllFileEvents(e: SingleFileEvent) {
   println("Some subscriber processing all SingleFileEvents here")
   loadAndDisplayContents(e.file)
...
}

Events In Action

A FileEvent is Published

A File Event is created when:

  1. A file is dragged into the ListView (FileAddedEvent)
  2. An item in the ListView is selected (FileSelectedEvent)

Here are the two code sections that generate these events (complete file):

File Drag ==> FileAddedEvent

fileListView.onDragDropped = EventHandler { event -> 
    ...
    eventBus.post(FileAddedEvent(firstFile))
  ...
}

ListView Selection ==> FileSelectedEvent

// fire event on EventBus every time selection changes
fileListView.selectionModel.selectedItemProperty().addListener { _, _, newSelection ->
...

eventBus.post(FileSelectedEvent(newSelection))
...
}
}

The Event is Processed

For the FileAddedEvent, two controllers process the events in different ways:

  • FileDataController loads the file content and displays it
  • FilePropertiesController displays basic file properties

Here are the same event subscribers for both Controllers:

// from FileDataController
@Subscribe
fun handleFileAdded(e: FileAddedEvent) {
   println("FileDataController processing FileAddedEvent")
   loadAndDisplayContents(e.file)
}

// from FilePropertiesController
@Subscribe
fun handleFileAdded(e: FileAddedEvent){
  println("FilePropertiesController processing FileAddedEvent")
  nameLabel.text = e.file.name
  pathLabel.text = e.file.path
  sizeLabel.text = e.file.length().toString()
}

Summary

This post focused on a single application, app3, in my repo. Like the other applications in the repo, you run it with gradlew (checkout the repository README.md for setup details):

gradlew runApp3 

Hopefully I have given you an understanding of a way to separate your controllers from each other, and from any particular visual representation in FXML, and introduced the excellent EventBus library along the way.

Application Number 1

I started this blog after spending several years of my private time building Kotlin and JavaFX desktop applications for fun and very little profit. My day job has nothing to do with either. Along the way, I have picked up hard-won experience with Kotlin and JavaFX.  I often struggled with getting answers for my issues, and having collected lots of private repos in bitbucket, thought… “Why not blog about my experiences?”

Nothing sharpens your skills like creating demos and explaining things, if only to avoid writing something stupid. 

Anyway, enough crap about me. On with the show.

I plan to blog about issues that I came across developing Kotlin desktop applications such as Drag and Drop, Custom Controls, Multiple Controllers and so on. Each post will discuss a specific, working application on GitHub.

This first post is mostly about showing how to get started with a basic Kotlin/JavaFX/Gradle setup.

I have created a GitHub repo with fully working example:

https://github.com/SergeMerzliakov/javafx-app-1

Here is a screen shot. Nothing fancy, just a basic demo.

Up and Running in 30 Seconds

If you have Java 11 and OpenJDK 12:

git clone git@github.com:SergeMerzliakov/javafx-app-1.git

<edit build.gradle. Update JFX_INSTALL to point to JFX 12>

gradlew clean build

gradlew runApp1

JavaFX is now a Separate Library

Duh. It’s 2019 and all of us with more than 30 seconds of Googling behind us will know this. JavaFX is now OpenFX and its downloaded as a plain old zip file. So you can install it anywhere. This has implications for your gradle config.

My build.gradle repositories block looks like this now.

repositories {
mavenCentral()
flatDir {
dirs "${JFX_INSTALL}/lib"
}
}

A variable called JFX_INSTALL now defines where it’s installed. This will come in handy later. The install contains a lib directory with all the jars you need.

I don’t know if this is the best way to do it, but it satisfies rule 1 – Keeping Things Simple.

Given that as of today, we have Java 11 and 12, with OpenJFX 11 and 12, what are the allowed permutations? Well I know that:

  • Java 11 + OpenJFX 11 ==> Works
  • Java 11 + OpenJFX 12 ==> Works
  • Java 12 + OpenJFX 12 ==> Works
  • Java 12 + OpenJFX 11 ==> Not sure ++

++ The OpenJFX website tells you to use Java 11 and that’s about it. I quote:

If you have newer or older versions of Java installed along with JDK 11, you need to make sure that the JAVA_HOME environment variable points to JDK 11

Some basic tests with Java 11 and switching between OpenJFX 11 and 12 has shown zero difference in behaviour so far.

Gradle Oddities

The dependencies section references the OpenJFX install location rather than the Maven repository details. That’s because the Maven artefact JFX jars are all empty. WTF? No obvious reason after some research. I ploughed on, and did this:

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
compile name: 'javafx.fxml'
compile name: 'javafx.controls'
compile name: 'javafx.base'
compile name: 'javafx.graphics'
}

Inelegant, but I am nothing if not goal oriented. Stints as a pre-sales consultant really drilled that into me.

I suspect that this may be due to the use of Java Modules, which I have zero experience with. No one at my day job has ever used them either. So I will blithely coast over this and worry about it ‘real soon now’.

Running The Application

Since we need to use modules, starting an OpenJFX 11+ app requires some extra VM parameters, basically telling the JVM where you installed OpenJFX and which modules you require loaded. Weird. Why can’t this be worked out some other way? Surely as part of the compile/link/resolve dependencies process?

Below is the gradle task for running App1. Hopefully the first of many.

task runApp1(type: JavaExec) {
group = "Application"
description = "Runs Application 1"
classpath sourceSets.main.runtimeClasspath
main = 'org.epistatic.app1.Main'
doFirst {
jvmArgs = [
'--module-path', "${JFX_INSTALL}/lib",
'--add-modules', 'javafx.fxml,javafx.controls'
]
}
}

Summary

Well that’s it for the first post. Nothing revolutionary here, but hopefully the code repository works out of the box. If you cannot get the demo running, let me know so I can fix the bastard.