Как заставить ScalaFX хорошо работать в консоли SBT?

Я пишу библиотеку изображений для студентов, изучающих программирование. (Идею и шаблоны я украл из библиотеки изображений для DrRacket.)

https://github.com/dupontmanualhs/dm-image

В основном он написан на Swing (это ветка master), но я пытаюсь преобразовать его в ScalaFX (см. ветку scalafx), и возникают некоторые проблемы. В идеале учащийся должен уметь делать что-то вроде:

scala> import org.dupontmanual.image._
scala> TrainEngine.display()

и появится диалоговое окно с паровозом. Я пытался использовать код на

https://github.com/scalafx/ScalaFX-Tutorials

в проекте stand-alone-dialog, но если я включаю System.exit(0) после dialog.showAndWait(), я получаю эту ошибку:

Not interrupting system thread Thread[process reaper,10,system]
Exception while removing reference: java.lang.InterruptedException
java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
    at com.sun.glass.utils.Disposer.run(Disposer.java:69)
    at java.lang.Thread.run(Thread.java:744)
Not interrupting system thread Thread[Prism Font Disposer,10,system]
Exception in runnable
Exception in thread "JavaFX Application Thread"

(Обратите внимание, что я получаю ту же ошибку, если пытаюсь запустить App из stand-alone-dialog в консоли, поэтому я предполагаю, что вызов System.exit(0) в консоли SBT не лучшая идея.)

Если я оставлю строку System.exit(0), то в основном все будет работать нормально. После того, как я впервые отобразил диалоговое окно, оно не фокусируется на диалоговом окне, поэтому мне нужно щелкнуть его, чтобы закрыть диалоговое окно. Но настоящая проблема заключается в том, что когда я :q выхожу из консоли, SBT зависает, и мне приходится Ctrl-C печатать снова. (И да, Ctrl-C полностью выходит из SBT, а не только из консоли.)

Я думаю, что мне может понадобиться создать поток специально для материала ScalaFX. Например, у меня есть метод наложения одного изображения поверх другого, и я получил IllegalStateException, когда попытался вызвать эту функцию, хотя на самом деле она ничего не отображает, а просто создает новый Group с двумя предыдущими Node, наложенными друг на друга. соответственно. К сожалению, я не знаю, как создать новый поток и убедиться, что все, что связано с изображением, проходит через него.

Я уже установил fork := true в build.sbt, но, похоже, это не имеет значения для консоли.

== Обновление ==

Я нашел initialCommands и cleanupCommands в документации по SBT и пробовал после всего подчищать при запуске и завершении консоли. Значения:

initialCommands in console := """import org.dupontmanual.image._; org.dupontmanual.image.initialize()"""

cleanupCommands in console := """org.dupontmanual.image.cleanUp()"""

которые определяются следующим образом:

package object image {
  var masterFrame: JFrame = _

  def initialize() {
    masterFrame = new JFrame()
    masterFrame.add(new JFXPanel())
    masterFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
  }

  def cleanUp() {
    println("exiting platform")
    Platform.exit()
    println("disposing of frames")
    Frame.getFrames().foreach {
      _.dispose()
    }
    println("frames all disposed")
    System.exit(0);
  }

Вот результат запуска консоли и последующей попытки выхода:

> console
[info] Compiling 1 Scala source to /home/sysadmin/dm-workspace/dm-image/target/scala-2.10/classes...
[info] Starting scala interpreter...
[info] 
import org.dupontmanual.image._
Welcome to Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_51).
Type in expressions to have them evaluated.
Type :help for more information.

scala> Hacker.display()

scala> :q
exiting platform
disposing of frames
frames all disposed
Not interrupting system thread Thread[XToolkt-Shutdown-Thread,5,system]
Not interrupting system thread Thread[AWT-XAWT,6,system]
Not interrupting system thread Thread[Prism Font Disposer,10,system]
Not interrupting system thread Thread[Java2D Disposer,10,system]
Exception while removing reference: java.lang.InterruptedException
java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
    at com.sun.glass.utils.Disposer.run(Disposer.java:69)
    at java.lang.Thread.run(Thread.java:744)

Exception: sbt.TrapExitSecurityException thrown from the UncaughtExceptionHandler in thread "run-main-0"

и это даже не выходит из консоли. Вам все еще нужно использовать Ctrl-C, который полностью выходит из SBT.

Что-то все еще работает, но я не могу понять, что именно. Гррр.


person Todd O'Bryan    schedule 10.03.2014    source источник
comment
Вы пробовали звонить scalafx.Platform.exit() вместо System.exit(0)? Первый способ является предпочтительным для завершения работы приложения ScalaFX.   -  person Mike Allen    schedule 11.03.2014
comment
Я не знал этого. Я попробую.   -  person Todd O'Bryan    schedule 11.03.2014
comment
Упс! Это должно быть scalafx.application.Platform.exit()! Извините за путаницу...   -  person Mike Allen    schedule 11.03.2014
comment
У меня все еще возникает проблема, когда SBT зависает после того, как я делаю :q в консоли.   -  person Todd O'Bryan    schedule 11.03.2014
comment
Да, я только что попытался использовать его вместо System.exit(0) в коде ScalaFX-Tutorials StandAloneFXDialog, и он просто блокирует консоль sbt. Согласно версии этой функции для JavaFX (которую ScalaFX вызывает под капотом), Примечание: если приложение встроено в браузер, этот метод может не иметь никакого эффекта. Это может быть проблемой. Я посмотрю на это еще...   -  person Mike Allen    schedule 11.03.2014
comment
Думаю проблема не в Platform.exit(), а в реализации функции FXUtils.runAndWait(...) в пакете stand_alone_dialog. По сути, он завершает исполняемый поток при очень ограниченном наборе условий, поэтому Platform.exit() не имеет никакого эффекта. Я собираюсь взглянуть на улучшение этого. Я размещу исправленный код на GitHub...   -  person Mike Allen    schedule 11.03.2014
comment
На самом деле, после дальнейшего изучения, я думаю, проблема в том, что это не совсем приложение ScalaFX/JavaFX — это приложение Swing с некоторыми элементами JavaFX. Вероятно, поэтому Platform.exit() ничего не делает. Если вы посмотрите на документацию для класса JavaFX JFXPanel - для встраивания JavaFX/ScalaFX в приложения Swing - есть аккуратный рабочий пример для подражания. Я думаю, поскольку это приложение Swing, вы завершаете приложение, вызывая метод dispose() основного фрейма. Надеюсь это поможет...   -  person Mike Allen    schedule 12.03.2014
comment
Любая идея, что вы делаете, если нет основного фрейма (например, вы просто хотите отобразить изображение в диалоговом окне)? И вы должны превратить свои последние два комментария в ответ. Если они окажутся подходящими, они будут достойны проверки. (К сожалению, в данный момент мой мозг мертв, поэтому я попробую это утром.)   -  person Todd O'Bryan    schedule 12.03.2014
comment
Хороший вопрос. Я думаю, что JFXPanel создает поток выполнения Swing, и я не уверен, есть ли также отдельный поток ScalaFX/JavaFX. Я думаю, вы могли бы попробовать позвонить Platform.exit(), а затем System.exit(0) и посмотреть, поможет ли это. Кстати, я с радостью превращу комментарии в ответ, когда выясню, как вам следует завершить работу вашего приложения, но спасибо! Это не должно быть это сложно... :-(   -  person Mike Allen    schedule 12.03.2014
comment
Новая идея... Что мне нужно сделать, чтобы создать новое приложение Scala/JavaFX и запустить его из метода display() моего класса Image? Я предполагаю, что мне придется запустить его в новом потоке или процессе (потому что я попытался просто запустить его, не делая этого, и получил ошибку).   -  person Todd O'Bryan    schedule 12.03.2014
comment
В ПОРЯДКЕ. Я отредактировал вопрос, чтобы отразить мои последние попытки. Кажется, что-то запускает поток, к которому я не могу получить доступ.   -  person Todd O'Bryan    schedule 21.03.2014
comment
Я думаю, вы исправили это? Только что проверил ваш проект и запустил пример, но не исключение;)   -  person Oliver Jan Krylow    schedule 22.03.2014
comment
К сожалению, не обращайте внимания на то, что я сказал, после ввода exit я вижу ошибку сейчас, хотя я вижу ее каждый раз, когда я exit запускаю консоль sbt, даже если не запускаю TrainEngine.   -  person Oliver Jan Krylow    schedule 22.03.2014
comment
По какой причине вы реализовали его как приложение Swing, включающее JavaFX, а не наоборот или только для JavaFX?   -  person Oliver Jan Krylow    schedule 22.03.2014
comment
Один из способов инициализировать инструментарий JavaFX — создать JFXPanel. К сожалению, это ставит вас в гибридность Swing/JavaFX.   -  person Todd O'Bryan    schedule 23.03.2014
comment
@OJKrylow, ошибка связана с тем, что JavaFX Toolkit инициализируется, когда консоль открыта, независимо от того, используете ли вы ее на самом деле. Подобная ошибка не возникает, если вы не инициализируете инструментарий.   -  person Todd O'Bryan    schedule 23.03.2014


Ответы (1)


Я думаю, проблема в том, что вам каким-то образом нужно будет разветвить консоль, так что, возможно, это проблема: https://github.com/sbt/sbt/issues/1918

Кажется, работает следующая идея: вместо консоли sbt вы встраиваете REPL, например Ammonite. Тем не менее sbt run не работает, даже с fork in run := true. Но упаковка толстой банки и запуск, похоже, работают:

build.sbt

name := "Foo"

version := "0.1.0"

scalaVersion := "2.11.7"

libraryDependencies ++= Seq(
  "org.scala-lang.modules" %% "scala-swing"   % "1.0.2",
  "com.lihaoyi"            %  "ammonite-repl" % "0.5.1" cross CrossVersion.full
)

project/build.properties

sbt.version=0.13.9

проект/plugins.sbt

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.1")

src/main/scala/foo/Main.scala

package foo

import scala.swing._

object Main extends App {
  def test(): Unit = Swing.onEDT {
    new MainFrame {
      contents = new Label("App exits if you close window")
    } .open()
  }

  ammonite.repl.Main.run("")
}

потом

$ sbt assembly
...
$ java -jar target/scala-2.11/Foo-assembly-0.1.0.jar
Loading...
Welcome to the Ammonite Repl 0.5.1
(Scala 2.11.7 Java 1.8.0_66)
@ foo.Main.test()

Единственная странность заключается в том, что после того, как приложение существует, эхо символов оболочки нарушается, возможно, это проблема Ammonite, вы можете попробовать использовать Scala REPL по умолчанию.

person 0__    schedule 10.12.2015
comment
Спасибо за ответ. Я надеюсь, что люди из SBT в конечном итоге исправят ошибку. - person Todd O'Bryan; 11.12.2015