4

カスタム ScalaFX コントロールを作成する正しい方法は何ですか? 私は Swing と Scala Swing の出身です。カスタム コンポーネントはComponentorを拡張するだけで簡単に作成できPanelます。しかし、ScalaFX の を拡張しようとすると、JavaFXデリゲートControlなしでは拡張できません。ControlScalaFX クラスの代わりにベースの JavaFX クラスを拡張して、カスタム ScalaFX コンポーネントを作成する必要がありますか?

4

1 に答える 1

5

一般的に言えば、次のことを行います。

  • カスタム JavaFX コントロールを作成します。
  • 次に、オプションで、デフォルトのものと同じモデルでカスタム ScalaFX ラッパーを作成します。一部の ScalaFX 機能 (バインディングなど) は、特定の ScalaFX ラッパーがなくても正常に動作することに注意してください。ここでいくつかの例を確認できます。

カスタム JavaFX コントロールを作成するには、最初にチェックアウトするリソースはこの Oracle チュートリアルですが、このブログ記事はさらに詳しく説明しています。ControlsFXJFXtrasなどのオープン ソース プロジェクトには、コントロールの例がたくさんあります。

明らかに、これらのリソースはすべて、Java でそれを行う方法を示しています。Scala でそれを実行できなかった理由はわかりません (ScalaFX クラスではなく JavaFX クラスを使用している限り) - しかし、それに関するドキュメントを見つけることができなかったので、おそらくJava でコントロールを作成する方が安全です。

編集: ScalaFX ラッパー クラスを使用した単純なカスタム JavaFX コントロールの 2 つの例をgithubに掲載しました。1 つのバージョン はYieldingSlider、クラスを拡張する単一の Java クラスSliderです。もう 1 つのバージョン はFxmlYieldingSlider、基本的に同じものですが、FXML ファイルとコントローラー クラスを使用してコントロールを構築する方法を示しています。このプロジェクトからビルドされた JAR ファイルは Scene Builder 2.0 にインポートできるため、Scene Builder はFXML で<YieldingSlider>およびコントロールを使用できることに注意してください。<FxmlYieldingSlider>

シンプルバージョンはこんな感じ。

JavaFX コントロール:

package customjavafx.scene.control;

import javafx.scene.control.Slider;
import javafx.scene.input.MouseEvent;

public class YieldingSlider extends Slider {

    public YieldingSlider() {
        addEventFilter(MouseEvent.MOUSE_PRESSED, event -> lastTimeMousePressed = System.currentTimeMillis());
    }

    public YieldingSlider(final double min, final double max, final double value) {
        this();
        setMin(min);
        setMax(max);
        setValue(value);
    }

    private long lastTimeMousePressed = 0;

    public boolean mouseWasPressedWithinLast(final long t) {
        return (System.currentTimeMillis() - lastTimeMousePressed) <= t;
    }
}

ScalaFX ラッパー:

package customscalafx.scene.control

import scala.language.implicitConversions
import customjavafx.scene.{control => jfxsc}
import scalafx.scene.control.Slider

object YieldingSlider {
  implicit def sfxSlider2jfx(v: YieldingSlider) = v.delegate
}

class YieldingSlider(override val delegate: jfxsc.YieldingSlider = new jfxsc.YieldingSlider) extends Slider {

  /** Constructs a Slider control with the specified slider min, max and current value values. */
  def this(min: Double, max: Double, value: Double) {
    this(new jfxsc.YieldingSlider(min, max, value))
  }
}

FXMLで使用できます:

<?xml version="1.0" encoding="UTF-8"?>

<?import customjavafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <YieldingSlider AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" />
   </children>
</AnchorPane>

または ScalaFX DSL では:

package guilgaly.fxtest.mp3player

import customscalafx.scene.control.YieldingSlider

import scalafx.application.JFXApp
import scalafx.scene.Scene

object TestApp extends JFXApp {
  stage = new JFXApp.PrimaryStage {
    scene = new Scene {
      content = new YieldingSlider
    }
  }
}

最後に、ScalaFXML で使用する場合、ScalaFXML はパッケージが で始まるクラスを検索するため、コントローラーに適切に挿入されないことに注意してくださいscalafx.*(同じパッケージ内の対応する JavaFX クラスが で始まると想定しますjavafx.*)。ただし、 で始まるパッケージを使用する場合javafx.*、コントロールを Scene Builder にインポートすることはできません。私の解決策は、ScalaFXML コードに醜いハックを入れて、 のように処理することcustomscalafx.*でしscalafx.*た。しかし、これは ScalaFXML を使用する場合にのみ問題になります。

編集 2:私はそれに取り組んでいますが、これは Java ではなく inScala で記述された同じ JavaFX コントロールです。同じように機能し、必要に応じて同様の ScalaFX ラッパーでラップできます。

package customjavafx.scene.control

import javafx.event.EventHandler
import javafx.scene.control.Slider
import javafx.scene.input.MouseEvent

class ScalaYieldingSlider extends Slider{
  def this(min: Double, max: Double, value: Double) = {
    this()
    setMin(min)
    setMax(max)
    setValue(value)
  }
  // Support for Java 8 SAMs (lambdas) is still experimental in Scala 2.11.
  // I used the old-school anonymous class instead.
  addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler[MouseEvent] {
    def handle(event: MouseEvent): Unit = lastTimeMousePressed = System.currentTimeMillis
  })

  private var lastTimeMousePressed: Long = 0

  def mouseWasPressedWithinLast(t: Long): Boolean =
    (System.currentTimeMillis - lastTimeMousePressed) <= t
}
于 2014-09-03T19:02:17.023 に答える