How to write a Scala wrapper for javax.swing.Timer

2019-07-10 03:09发布

I'd like to use a timer in a Scala Swing application. I can use the Java version, only it means I have to implement the ActionListener interface. I'd rather use the Scala Publishers / Reactors model for consistency, so I can have things that listenTo the timer.

Here's what I tried:

class ScalaTimer(time: Int) extends Component {
  val t = new javax.swing.Timer(time, new java.awt.event.ActionListener {
    def actionPerformed(e: java.awt.event.ActionEvent) {
      publish(new scala.swing.event.ActionEvent(this))
    }
  })
  def start() {t.start()}
  def stop() {t.stop()}
  // etc

}

Doesn't work because this refers to the the ActionListener rather than the ScalaTimer class.

Also I probably shouldn't be extending Component... I tried this because I get the Publisher / Reactor functionality, but it doesn't really make sense. Should I do something like this instead? If so, are there other traits I need to include, and how do I know which methods I have to implement?

 class ScalaTimer extends javax.swing.Timer with Publisher {

(My IDE immediately flags "Missing arguments for method Timer(Int, ActionListener)" which seems a bit weird since I haven't invoked a method.)

5条回答
手持菜刀,她持情操
2楼-- · 2019-07-10 03:26

Doesn't work because this refers to the the ActionListener rather than the ScalaTimer class.

Tom is correct: you can use ScalaTimer.this.

(My IDE immediately flags "Missing arguments for method Timer(Int, ActionListener)" which seems a bit weird since I haven't invoked a method.)

Timer(Int, ActionListener) is the constructor of Timer, which you have invoked. You need to write extends javax.swing.Timer(constructor_args). Of course, the constructor arguments may depend on constructor arguments for ScalaTimer.

查看更多
乱世女痞
3楼-- · 2019-07-10 03:34

You would be able to achieve this in Java thus: ScalaTimer.this

It's probably the same in Scala

查看更多
虎瘦雄心在
4楼-- · 2019-07-10 03:40

The canonical way to do this is to introduce an alias at the top of the thing you want to refer to (usually self unless that's already taken):

class ScalaTimer(time: Int) extends Component {
  self =>
  val t = ...
    publish(new scala.swing.event.ActionEvent(self))
  ...
查看更多
Fickle 薄情
5楼-- · 2019-07-10 03:40

Simple wrapper:

import scala.swing.event.Event,
       scala.swing.Reactions,
       java.awt.event.ActionEvent,
       javax.swing.AbstractAction


case class Tick(source: Timer) extends Event
case class Timeout(source: Timer) extends Event

abstract class Timer {
  private val timer = this
  private var counter = 0
  private val tick = new AbstractAction(){def actionPerformed(e:ActionEvent) = {
    reactions(Tick(timer))
    if(_repeats > 0){
      counter -= 1
      if(counter == 0){run = false; reactions(Timeout(timer))}}}}
  val reactions: Reactions = new Reactions.Impl
  private var _interval = 1000
  def interval:Int = _interval
  def interval_=(i:Int):Unit = {_interval = i; peer.setDelay(i)}
  private var _repeats = -1
  def repeats:Int = _repeats
  def repeats_=(i:Int):Unit = {_repeats = i; counter = i}
  private var _run = false
  def run:Boolean = _run
  def run_=(f:Boolean):Unit = { _run = f; runStop(f)}
  private def runStop(f:Boolean) = f match{
    case true => {
      counter = _repeats
      if(counter != 0){peer.start()}else{reactions(Timeout(timer))}}
    case false => peer.stop()}
  val peer = new javax.swing.Timer(_interval, tick); peer.setRepeats(true)}

Use:

import scala.swing._

object Main extends Frame {
  //
  override def closeOperation() = {System.exit(0)}
  visible = true
  //
  def main(a:Array[String]):Unit = {
    val t = new Timer{
      interval = 500 //In milliseconds
      repeats = 10   //Repeats 10 times
      reactions += {case Tick(_) => println("tick")
                    case Timeout(_) => println("timeout")}
      run = true}}}
查看更多
淡お忘
6楼-- · 2019-07-10 03:46

I had to tackle this recently as well and this seems to work for me:

// First create a TimerEvent since using an ActionEvent necessitates that ScalaTimer extend
// Component, which isn't ideal as the OP indicated 
case class TimerEvent() extends scala.swing.event.Event

// extending javax.swing.Timer by and large avoids the hassle of writing wrapper methods
class ScalaTimer(val delay0:Int) extends javax.swing.Timer(delay0, null) with Publisher {

  // to mimic the swing Timer interface with an easier-to-user scala closure
  def this(delay0:Int, action:(()=>Unit)) = {
    this(delay0)
    reactions += {
      case TimerEvent() => action()
    }
  }

  addActionListener(new java.awt.event.ActionListener {
    def actionPerformed(e: java.awt.event.ActionEvent) = publish(TimerEvent())
  })
}

You could also override other methods to provide for better encapsulation, but this is functional as is.

查看更多
登录 后发表回答