2011-01-06 9 views
9

Ich schreibe eine kleine Anwendung in Scala. Die Anwendung verarbeitet einfache Protokolldateien. Da die Verarbeitung einige Zeit in Anspruch nimmt, habe ich beschlossen, meinen Anwendungskern Actor erweitern zu lassen.Interaktion mit Schauspielern in Scala Swing-Anwendungen

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

Die Verarbeitung einer Protokolldatei wird durch Klicken auf eine Schaltfläche in der GUI ausgelöst. Die GUI verwendet Scala Swing.

Nun muss der Anwendungskern die GUI über den aktuellen Fortschritt benachrichtigen.

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

Ich habe dies gelöst, indem ich einen separaten Akteur in der GUI erstellt habe. Der Darsteller wird innerhalb des Edt-Threads ausgeführt. Er hört Nachrichten vom Anwendungskern ab und aktualisiert die 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 
      } 
     } 
     } 
    } 
    } 

Da der Anwendungskern muss über den Absender der Nachricht wissen, verwende ich diese Schauspieler auch Nachrichten von der GUI der Anwendung Kern zu übermitteln, meine Schauspieler den neuen Sender zu machen.

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

Dieser Code funktioniert einwandfrei. Meine Frage: Gibt es einen einfacheren Weg dies zu tun? Es wäre nett, den Reaktionsmechanismus für meine Anwendungsnachrichten einfach zu verwenden:

Irgendwelche Ideen, wie man das erreicht?

Antwort

6

Ich habe auf gerferras Idee erweitert meine Anwendung zur Herstellung einer swing.Publisher. Die folgende Klasse fungiert als Vermittler zwischen einer swing.Reactor und einer 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))) 
     } 
    } 
    } 
} 

Jetzt kann meine Reaktionen sowohl Swing-Ereignisse und Anwendungsereignisse umfassen.

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

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

Hinweis, wie die case class Send einige syntaktische Zucker liefert Ereignisse auf einfache Weise erstellen und sie an den Vermittler übergeben.

+0

Ich denke, es ist eine gute Lösung. Vielleicht verdiente ich eine Gegenleistung im Gegenzug;) – gerferra

+0

Ich glaube, Sie vermissen "Vermittler!" In Ihrer Reaktion auf "ButtonClicked" – gerferra

+0

@geferra Der 'intermediator!' Aufruf ist im Konstruktor der 'Fallklasse Send'. Der "Vermittler" wird durch einen impliziten Parameter übergeben. Ich erhebe Ihre Antwort, da sie meine eigene Lösung inspiriert hat. –

4

Vielleicht ist das einfacher, aber weiß nicht, ob es besser ist. Statt machen Ihre Anwendung ein Schauspieler Backend können Sie einen anonymen Schauspieler erstellen jedes Mal, müssen Sie die Datei verarbeiten:

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

Für den Fortschritt Update Teil können Sie einen Rückruf an den Prozess-Methode übergeben jeden ausgeführt werden Mal eine neue Fortschritte gemacht:

import scala.actors.Actor.actor 

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

Alternativ (nicht getestet) Sie Ihre Anwendung ein jedes Mal scala.swing.Publisher und statt mit der Callback, veröffentlichen und Ereignis machen könnte. So könnte der Code sein:

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

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

Und in der Anwendung:

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

Ihre erste Idee mit dem Rückruf funktioniert, aber ich denke, mein Ansatz mit dem Schauspieler ist mehr erweiterbar Wenn ich zusätzliche Nachrichten weitergeben muss. –

+0

Was ist mit dem zweiten Ansatz?Deine Bewerbung wird etwas an ein Swing-Frontend gebunden sein, aber ich denke, es ist nicht so schlimm und wenn ich deine Frage richtig gelesen habe, ist es das, wonach du am Ende fragst – gerferra

+0

Ich habe deinen zweiten Ansatz ausprobiert und etwas erweitert. Sehen Sie meine eigene Antwort wie diese Lösung aussieht. –