4

What exactly is the right way to create a custom ScalaFX control? I'm coming from Swing and Scala Swing, where custom components are simply created by extending Component or Panel. But when I try to extend ScalaFX's Control, I can't extend it without a JavaFX Control delegate. Should I just create custom ScalaFX components by extending the base JavFX classes instead of the ScalaFX classes?

4

1 回答 1

5

一般来说,你会想要:

  • 创建自定义 JavaFX 控件。
  • 然后可以选择在与默认模型相同的模型上创建自定义 ScalaFX 包装器。请注意,即使没有特定的 ScalaFX 包装器,某些 ScalaFX 功能(如绑定)也应该可以正常工作 - 您可以在此处查看一些示例

要创建自定义 JavaFX 控件,首先要查看的资源是这个 Oracle 教程,但这篇博文更进一步。ControlsFXJFXtras等开源项目提供了大量的控件示例。

显然,所有这些资源都展示了如何在 Java 中做到这一点。我看不出为什么你不能在 Scala 中做到这一点(只要你使用 JavaFX 类而不是 ScalaFX 类)——但我找不到任何相关文档,所以我猜它可能是在 Java 中创建控件更安全。

编辑:在 github 上放了两个带有 ScalaFX 包装类的简单自定义 JavaFX 控件的示例。一个版本YieldingSlider是扩展类的单个 JavaSlider类;另一个版本,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 代码中添加一个丑陋的 hack,这样它就可以customscalafx.*scalafx.*. 但这只是在使用 ScalaFXML 时需要考虑的问题。

编辑 2:当我在这里时,这是用 Scala 而不是 Java 编写的相同 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 回答