3

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.

My other car is a cadr
  • 1,429
  • 1
  • 13
  • 21
  • Please note that there is no reason why the import statement would have made any difference if both classes have the same package name. The compiler needs to find the already compiled class in its classpath, or to find the source code amongst the files it's trying to compile. Can you tell us how you are compiling? (Also, out of curiosity, what is the reason for trying to run `HelloStageDemo` from another main class?) Edit: I think I know what's the issue - I'll test and write an answer. – Cyäegha Oct 22 '14 at 18:29
  • I'm using Eclipse to compile but I just tried `sbt` and it gives out the exact same error message. I'm writing a command line application that launches a GUI to graph out the results. Thank you in advance for your help! – My other car is a cadr Oct 22 '14 at 18:58

3 Answers3

2

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.

Cyäegha
  • 4,191
  • 2
  • 20
  • 36
  • Thanks! Calling main works beautifully. But how do I pass information (other than `String`) to the app? Let's say I want to pass a `BufferedImage` to the app. An `object` can't take any parameters and using a global variable seems too hacky. – My other car is a cadr Oct 22 '14 at 19:43
  • If you only need to pass information once, before launching, you can just expose a public setter method for a private var in the main ScalaFX object; and call it before calling the main method. I think that's the simplest way. If information needs to be passed repeatedly from the CLI part of the app to the ScalaFX part, your main ScalaFX object can expose some public property (in the JavaFX/ScalaFX sense of the word property), so that you ScalaFX code can use bindings or listeners to react to changes of that property's value. – Cyäegha Oct 22 '14 at 20:01
  • Silly me: in the first case, you can just expose a public method which takes whatever parameters you want (and probably assigns them to private `var`s), and which itself calls `this.main`... Just one method to call that way. – Cyäegha Oct 22 '14 at 20:08
  • Thanks again! Somehow the private variable gets reset though. Could you please take a look at `https://github.com/SummerStorm/ScalaFxTest` ? – My other car is a cadr Oct 22 '14 at 20:23
  • Looks like it would be because of `JFXApp`'s using a delayedInit mechanism - the initialization code is executed when `AppHelper.start()` calls `JFXApp.ACTIVE_APP.init()` (from what I understand). So the main ScalaFX class isn't properly initialized before being launched. I think what I would do is try the other approach I mentioned - it involves setting a property after the ScalaFX app was launched, so it should work; and it adds some flexibility in case your CLI app needs to display another graph later (no need to stop/restart the ScalaFX app), though I don't know if that's an issue for you. – Cyäegha Oct 22 '14 at 21:32
  • Thanks again. I did the property thing and now it works. I'm a little disappointed that ScalaFX isn't as functional as the rest of Scala though. A property is basically a global variable, even though that I really want is a immutable field since there's only one graph per invocation and the graph doesn't change. – My other car is a cadr Oct 22 '14 at 21:50
  • Well, at the end of the day, ScalaFX is just a wrapper around JavaFX - it's convenient in some ways, but it doesn't transform JavaFX's nature. I'd also point out that your situation is a bit unusual, since your ScalaFX application is launched after some other code has already run and produced a result; usually, the calculation would be launched from within the ScalaFX application itself, so I think the event-driven approach would seem more obvious. – Cyäegha Oct 22 '14 at 22:04
0

Here is a simple template for launching ScalaFX applications.

object MyApp {
   def main(args: Array[String]) {
        MyApp.launch(classOf[MyApp], args: _*)
    }
}

class MyApp extends JFXApp {

   override def start(primaryStage: Stage): Unit = {
      // initialization here
   } 
}
Eugene Ryzhikov
  • 17,131
  • 3
  • 38
  • 60
0

Non ScalaFX solution:

I know this question is answered and old. But if you want to use javafx.application.Application, to launch. Just set different names for the class and the object, with the result that there is no "MyClass$.class" and "MyClass.class" where the second one isn't the Applications Child but the object.

Solved my problem in a neat way.

sascha10000
  • 1,245
  • 1
  • 10
  • 22