创建自定义ScalaFX控件

时间:2014-08-20 09:21:39

标签: scala scalafx

创建自定义ScalaFX控件的正确方法究竟是什么?我来自Swing和Scala Swing,只需通过扩展ComponentPanel即可创建自定义组件。但是当我尝试扩展ScalaFX Control时,我无法在没有JavaFX Control委托的情况下扩展它。我应该通过扩展基本JavFX类而不是ScalaFX类来创建自定义ScalaFX组件吗?

1 个答案:

答案 0 :(得分:4)

一般来说,您需要:

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

要创建自定义JavaFX控件,要检出的第一个资源是this Oracle tutorial,但this blog post会更进一步。像ControlsFXJFXtras这样的开源项目提供了大量的控件示例。

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

编辑:我已经使用ScalaFX包装器类提供了on github两个简单自定义JavaFX控件的示例。一个版本YieldingSlider是一个扩展Slider类的Java类;另一个版本FxmlYieldingSlider基本上是相同的,但它显示了如何使用FXML文件和控制器类构造控件。请注意,可以在Scene Builder 2.0中导入从此项目构建的JAR文件,以便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:虽然我正在使用它,但这是用斯卡拉而不是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
}