Daniel Seidewitz
Daniel Seidewitz

Reputation: 720

Interacting with actors in scala swing applications

I'm writing a small application in scala. The application processes simple log files. Because the processing takes some time, I've decided to let my application core extend Actor.

class Application extends Actor {
  def react() {
    loop {
      react {
        case Process(file) => // do something interesting with file...
      }
    }
  }
}

The processing of a log file is triggered by clicking a button in the gui. The gui uses scala swing.

object Gui extends SimpleSwingApplication {
  val application = new Application().start()

  def top = new MainFrame {
    val startButton = new Button

    reactions += {
      case ButtonClicked(`startButton`) => application ! Process(file)
    }
  }
}

Now, the application core needs to notify the gui about the current progress.

  sender ! Progress(value) // whenever progress is made

I've solved this by creating a separate actor inside the gui. The actor is executed inside the edt thread. It listens to messages from the application core and updates the gui.

  object Gui extends SimpleSwingApplication {
    val actor = new Actor {
      override val scheduler = new SchedulerAdapter {
        def execute(fun: => Unit) { Swing.onEDT(fun) }
      }
      start()

      def act() {
        loop {
          react {
            case ForwardToApplication(message) => application ! message
            case Progress(value) => progressBar.value = value
          }
        }
      }
    }
  } 

Since the application core needs to know about the sender of the message, I also use this actor to forward messages from the gui to the application core, making my actor the new sender.

  reactions += {
    case ButtonClicked(`startButton`) => actor ! ForwardToApplication(Process(file))
  }

This code works just fine. My question: Is there a simpler way to do this? It whould be nice to simple use the reactions mechanism for my application messages:

  reactions += {
    case Progress(value) => progressBar.value = value
  }

Any ideas how to achieve this?

Upvotes: 9

Views: 2547

Answers (2)

Daniel Seidewitz
Daniel Seidewitz

Reputation: 720

I have extended on gerferras idea of making my application a swing.Publisher. The following class acts as intermediator between a swing.Reactor and an Actor.

import actors.Actor
import swing.Publisher
import swing.event.Event
import swing.Swing.onEDT

case class Send(event: Any)(implicit intermediator: Intermediator) {
  intermediator ! this
}
case class Receive(event: Any) extends Event

case class Intermediator(application: Actor) extends Actor with Publisher {
  start()

  def act() {
    loop {
      react {
        case Send(evt) => application ! evt
        case evt => onEDT(publish(Receive(evt)))
      }
    }
  }
}

Now my reactions can include both swing events and application events.

implicit val intermediator = Intermediator(application)
listenTo(intermediator, button)

reactions += {
  case ButtonClicked(`button`) => Send(Process(file))
  case Receive(Progress(value)) => progressBar.value = value
}

Note how the case class Send provides some syntactic sugar to easily create events and pass them to the intermediator.

Upvotes: 6

gerferra
gerferra

Reputation: 1519

Maybe this is simpler but don't know if it's better. Instead of making your application backend an actor, you can create an anonymous actor every time you need to process the file:

reactions += {
  case ButtonClicked(`startButton`) => application.process(file, { v: Int => Swing.onEDT(progressBar.value = v) })
}

For the progress update part, you can pass a callback to the process method to be executed every time a new progress is made:

import scala.actors.Actor.actor

def process(f: File, progress: Int => Unit) {
  actor {
    // process file while notifying the progress using the callback
    progress(n)
  }  
}

Alternatively (haven't tested) you could make your application a scala.swing.Publisher and, instead of using the callback, publish and event every time. So the code could be:

listenTo(startButton, application) //application is a Publisher

reactions += {
  case ButtonClicked(`startButton`) => application.process(file)
  case Progress(v) => progressBar.value = v
}

And in the application:

import scala.actors.Actor.actor

def process(f: File) {
  actor {
    // process file while notifying the progress using an scala.swing.event.Event
    publish(Progess(n))
  }  
}

Upvotes: 4

Related Questions