创建自定义 ScalaFX 控件的正确方法究竟是什么?我来自 Swing 和 Scala Swing,其中自定义组件只是通过扩展 ComponentPanel 来创建的。但是当我尝试扩展 ScalaFX 的 Control 时,如果没有 JavaFX Control 委托(delegate),我就无法扩展它。我应该通过扩展基本 JavFX 类而不是 ScalaFX 类来创建自定义 ScalaFX 组件吗?

最佳答案

一般来说,你会想要:

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

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

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

    编辑: 我已经举了两个带有 ScalaFX 包装器类的简单自定义 JavaFX 控件的 on github 示例。一个版本 YieldingSlider 是扩展 Slider 类的单个 Java 类;另一个版本 FxmlYieldingSlider 基本相同,但它展示了如何使用 FXML 文件和 Controller 类构造控件。请注意,从该项目构建的 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 一起使用,它将无法正确注入(inject) Controller ,因为 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
    }
    

    关于scala - 创建自定义 ScalaFX 控件,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/25401094/

    10-12 17:34