Search code examples
scalajavafx-8scalafx

How do Iaunch a ScalaFX application from Scala?


I'm trying to launch the ScalaFX Hello World application from http://www.scalafx.org with the following code:

package car.cadr

object ApplicationStarter {
    def main(args: Array[String]) =
        javafx.application.Application.launch(classOf[HelloStageDemo], args: _*)
}

To clarify, I have two Scala files in the car.cadr package: ApplicationStarter.scala and HelloStageDemo.scala. HelloStageDemo.scala starts and runs perfectly fine, but the compiler is complaining about not found: type HelloStageDemo on ApplicationStarter.scala. Even if I manually import it with import car.cadr.HelloStageDemo the compiler still complains.

I'm using Scala 2.11.1 and ScalaFx 8.0.20-R6.


Solution

  • You have several problems here.

    Let's start with the one the compiler is telling you about: not found: type HelloStageDemo. This makes sense because the HelloStageDemo example defines an object and not a class: so the scalac compiler actually outputs a class named HelloStageDemo$ (because you could also define a class HelloStageDemo, and both need to be compiled with different names).


    Next, if you change your object HelloStageDemo for a class HelloStageDemo, you will get the following error:

    Error:(7, 36) overloaded method value launch with alternatives:
      (x$1: String*)Unit <and>
      (x$1: Class[_ <: javafx.application.Application],x$2: String*)Unit
     cannot be applied to (Class[car.cadr.HelloStageDemo], String)
    

    This is because the launch method exists only with the following signatures (here in Java):

    • public static void launch(Class<? extends javafx.application.Application> appClass, String... args)
    • public static void launch(String... args)

    But HelloStageDemo is neither a String nor a kind of javafx.application.Application, so this cannot work.


    This is because of the way ScalaFX's JFXApp trait works. Here's the main metrhod that gets executed when you launch a ScalaFX application the usual way (ie., the main class is the one extending JFXApp):

    import javafx.{application => jfxa}
    
    trait JFXApp extends DelayedInit {
      // ...code removed for clarity...
      def main(args: Array[String]) {
        JFXApp.ACTIVE_APP = this
        arguments = args
        // Put any further non-essential initialization here.
        /* Launch the JFX application.
        */
        jfxa.Application.launch(classOf[AppHelper], args: _*)
      }
      // ...code removed for clarity...
    }
    

    So, in ScalaFX, the class extending javafx.application.Application isn't the one you implement, but a AppHelper class provided by ScalaFX. Notice that the main method first sets the ACTIVE_APP property on JFXApp's companion object: in practice, what AppHelper will do is start JFXApp.ACTIVE_APP. Here is the code:

    package scalafx.application
    
    private[application] class AppHelper extends javafx.application.Application {
      def start(stage: javafx.stage.Stage) {
        JFXApp.STAGE = stage
        JFXApp.ACTIVE_APP.init()
        if (JFXApp.AUTO_SHOW) {
          JFXApp.STAGE.show()
        }
      }
    
      override def stop() {
        JFXApp.ACTIVE_APP.stopApp()
      }
    }
    

    In conclusion, if you want to launch HelloStageDemo but, for some reason, you don't want HelloStageDemo to be the main class, the simplest solution would be to just call the main method - after all, it's just a method like any other:

    package car.cadr
    
    object ApplicationStarter {
      def main(args: Array[String]) =
        HelloStageDemo.main(Array())
    }
    

    But if, for some reason, you absolutely had to launch your ScalaFX application trough the javafx.application.Application.launch method, I think the best solution would be to re-implement the AppHelper class to your liking, which seems like it should be pretty simple.