2011-01-06 8 views
9

Piszę małą aplikację w scala. Aplikacja przetwarza proste pliki dziennika. Ponieważ przetwarzanie zajmuje trochę czasu, postanowiłem, że moja aplikacja rozszerzy się na aktora.Interakcja z aktorami w aplikacjach scala swing

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

Przetwarzanie pliku dziennika jest wyzwalane przez kliknięcie przycisku w gui. Gui używa huśtawki scala.

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

    def top = new MainFrame { 
    val startButton = new Button 

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

Teraz rdzeń aplikacji musi powiadomić GUI o bieżącym postępie.

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

Rozwiązałem to, tworząc osobny aktor wewnątrz gui. Aktor jest wykonywany wewnątrz wątku edt. Wysłuchuje wiadomości z rdzenia aplikacji i aktualizuje 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 
      } 
     } 
     } 
    } 
    } 

Ponieważ rdzeń aplikacja musi wiedzieć o nadawcy wiadomości, ja też użyć tego aktora do przekazania wiadomości z GUI do rdzenia aplikacji, dzięki czemu moja aktor nowego nadawcę.

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

Ten kod działa dobrze. Moje pytanie: czy istnieje prostszy sposób na zrobienie tego? Byłoby miło prostym użyciem mechanizmu reakcji dla moich wiadomości aplikacyjnych:

Jakieś pomysły, jak to osiągnąć?

Odpowiedz

6

mam przedłużony na gerferras idei dokonywania moja aplikacja swing.Publisher. Następująca klasa działa jako pośrednik między swing.Reactor i 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))) 
     } 
    } 
    } 
} 

Teraz moje reakcje mogą obejmować zarówno zdarzenia wahadłowe, jak i zdarzenia aplikacji.

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

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

Uwaga jak case class Send zapewnia cukier syntaktyczny łatwo tworzyć zdarzenia i przekazać je do funkcji pośrednika.

+0

Myślę, że to dobre rozwiązanie. Może w zamian zasługuję na odwrót;) – gerferra

+0

Myślę, że brakuje ci 'pośrednika! 'W twojej reakcji na' ButtonClicked' – gerferra

+0

@geferra Wywołanie "pośrednika!" Znajduje się w konstruktorze 'case case Send'. 'Pośrednik' jest przekazywany przez niejawny parametr. Uaktualniam twoją odpowiedź, ponieważ stanowi ona inspirację dla mojego własnego rozwiązania. –

4

Może to prostsze, ale nie wiem, czy jest lepiej. Zamiast tworzenia aplikacji backend aktora, można utworzyć anonimowy aktora za każdym razem trzeba przetworzyć plik:

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

dla części aktualizacji postęp, można przekazać wywołania zwrotnego aby być wykonywany w każdym sposobie procesowego razem nowy postępów:

import scala.actors.Actor.actor 

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

Alternatywnie (nie badane) można dokonać do zgłoszenia scala.swing.Publisher i zamiast używać wywołania zwrotnego, publikować i wydarzenie za każdym razem. Więc kod może być:

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

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

oraz w aplikacji:

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

Twój pierwszy pomysł z funkcją oddzwaniania działa, ale myślę, że moje podejście do aktora jest bardziej rozszerzalne, jeśli muszę przekazać dodatkowe wiadomości. –

+0

Co z drugim podejściem?Twoja aplikacja będzie w pewnym stopniu powiązana z frontendem Swing, ale myślę, że nie jest tak źle i, jeśli poprawnie przeczytałem twoje pytanie, to o to pytasz pod koniec. – gerferra

+0

Próbowałem twojego drugiego podejścia i nieco go rozszerzyłem. Zobacz moją własną odpowiedź, jak wygląda to rozwiązanie. –