Creating custom ScalaFX controls

What exactly is the way to create a custom ScalaFX control? I come from Swing and Scala Swing where custom components are simply created by extension Component

or Panel

. But when I try to extend ScalaFX Control

, I cannot extend it without a JavaFX delegate Control

. Should I just create custom ScalaFX components by extending the base JavFX classes instead of the ScalaFX classes?

+3


source to share


1 answer


Generally speaking, you will want:

  • Create your own JavaFX control.
  • Then additionally create your own ScalaFX wrapper, in the same model as the standard ones. Note that some ScalaFX features (like bindings) should work fine even without a specific ScalaFX wrapper - you can see some examples here .

To create a custom JavaFX control, the first resource to check out is this Oracle tutorial , but this blog post goes further. Open source projects such as ControlsFX and JFXtras provide many control examples.

Obviously, all of these resources show you how to do this in Java. I see no reason why you could not do this in Scala (as long as you are using JavaFX classes, not ScalaFX), but I could not find any documentation on this, so I guess it might be safer create controls in Java.

Edit: I have put two examples of a simple JavaFX custom control with a ScalaFX wrapper class on github . One version YieldingSlider

is one Java class that extends the class Slider

; the other version FxmlYieldingSlider

is basically the same, but it shows how to create a control with an FXML file and a controller class. Note that the JAR file created from this project can be imported in the Scene Builder 2.0, so that the Scene Builder can use the controls <YieldingSlider>

and <FxmlYieldingSlider>

in FXML.

Here's what the simple version looks like.

JavaFX Control:

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 wrapper:



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))
  }
}

      

Can be used in 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>

      

Or in 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
    }
  }
}

      

Finally, note that if you use it with ScalaFXML, it will not be injected correctly into the controller, because ScalaFXML looks for classes whose package starts with scalafx.*

(and expects the corresponding JavaFX class to be in the same package but looks from javafx.*

). However, if you are using a package that starts with javafx.*

, you cannot import your control into Scene Builder. My solution was to put the code in ScalaFXML that will handle, so that it handles customscalafx.*

like scalafx.*

. But this is only a problem when using ScalaFXML.

Edit 2: While I'm here, here's the same JavaFX control written by inScala instead of Java. It works the same way and can be wrapped in a similar ScalaFX wrapper if needed.

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
}

      

+4


source







All Articles