Update Asynchronous User Interface Using Swing

I wrote a Scala program that I would like to run using the user interface (also in Swing). The problem is that when I run it, the user interface freezes until the background program completes. I thought the only way around this would be to run the program in another thread / actor and update the user interface as needed. The update will include a status bar, which will display the file that is currently being processed, and a progress panel.

Since Scala actors are outdated, it's hard for me to plow through Akka to get some basic multithreading. The examples on the Akka website are also quite complex.

But more than that, it's hard for me to wrap my head around how to try to solve this problem. I can come up with the following:

  • The background program works like a single actor
  • The user interface is the main program
  • There is another actor who tells the user interface to update something

Step 3 is what scares me. How to tell the user interface without closing any variable somewhere?

In addition, I am sure that this problem has been resolved earlier. Any sample code for it would be greatly appreciated.

=========================

EDIT: What worked for me

Instead of using Actors, I used futures similar to what pagoda_5b explained in his answer:

Move the status bar and progress bar out def topand make them global variables:

object MyGUIProject extends SimpleSwingApplication {

  val txtStatus = new Label //Global
  val progressBar = new ProgressBar //Global

  def top = new MainFrame {
  ...
  }
}

Calling the background process in the future when I click the button:

val btnStart = new Button {
      text = "Click me to start"
      reactions += {
        case ButtonClicked(_) => {
            val myFuture = future {
            doLongBackgroundProcess(someString)
            }

            myFuture onSuccess {
                case _ => Swing.onEDT {
                    txtStatus.text = "Done"
                }
            }
        }
    }
}

future Swing.onEDT:

def updateStatus(txt: String) = {
    val fut = future {
      Swing.onEDT {
        txtStatus.text = txt
      }
    }
}

def doLongBackgroundProcess(str:String) = {
    val taskList = getTasks(str)
    taskList foreach {x=>
        updateStatus("Currently doing task: " + x)
        //Start doing it
    }
}
+5
3

scala 2.10

scala.concurrent.future, . GUI EDT.

!

//in your swing gui event listener (e.g. button clicked, combo selected, ...)
import scala.concurrent.future
//needed to execute futures on a default implicit context
import scala.concurrent.ExecutionContext.Implicits._ 


val backgroundOperation: Future[Result] = future {
    //... do that thing, on another thread
    theResult
}

//this goes on without blocking
backgroundOperation onSuccess {
    case result => Swing.onEDT {
        //do your GUI update here
    }
}

:

  • ,

(1), , map/flatMap Future. , ( , Swing.onEDT

//example progress update
val backgroundCombination = backgroundOperation map { partial: Result =>
    progress(2)
    //process the partial result and obtain
    myResult2
} //here you can map again and again

def progress(step: Int) {
    Swing.onEDT {
        //do your GUI progress update here
    }
}

(2), onFailure onComplete.

: scaladocs SIP ( SIP , )

+7

, .

:

  • WorkerActor, ( Thread.sleep). :
  • GUIUpdateActor - , handleGuiProgressEvent

handleGuiProgressEvent . , Actor Akka Swing.onEDT Swing Swing.

, , .

println("Current thread:" + Thread.currentThread())

- Swing/Akka.

import akka.actor.{Props, ActorRef, Actor, ActorSystem}
import swing._
import event.ButtonClicked

trait GUIProgressEventHandler {
  def handleGuiProgressEvent(event: GuiEvent)
}

abstract class GuiEvent

case class GuiProgressEvent(val percentage: Int) extends GuiEvent
object ProcessingFinished extends GuiEvent


object SwingAkkaGUI extends SimpleSwingApplication with GUIProgressEventHandler {

  lazy val processItButton = new Button {text = "Process it"}
  lazy val progressBar = new ProgressBar() {min = 0; max = 100}

  def top = new MainFrame {
    title = "Swing GUI with Akka actors"

    contents = new BoxPanel(Orientation.Horizontal) {
      contents += processItButton
      contents += progressBar
      contents += new CheckBox(text = "another GUI element")
    }

    val workerActor = createActorSystemWithWorkerActor()

    listenTo(processItButton)

    reactions += {
      case ButtonClicked(b) => {
        processItButton.enabled = false
        processItButton.text = "Processing"
        workerActor ! "Start"
      }

    }

  }

  def handleGuiProgressEvent(event: GuiEvent) {
    event match {
      case progress: GuiProgressEvent  => Swing.onEDT{
        progressBar.value = progress.percentage
      }
      case ProcessingFinished => Swing.onEDT{
        processItButton.text = "Process it"
        processItButton.enabled = true
      }
    }

  }

  def createActorSystemWithWorkerActor():ActorRef = {
    def system = ActorSystem("ActorSystem")

    val guiUpdateActor = system.actorOf(
      Props[GUIUpdateActor].withCreator(new GUIUpdateActor(this)), name = "guiUpdateActor")

    val workerActor = system.actorOf(
      Props[WorkerActor].withCreator(new WorkerActor(guiUpdateActor)), name = "workerActor")

    workerActor
  }


  class GUIUpdateActor(val gui:GUIProgressEventHandler) extends Actor {
    def receive = {
      case event: GuiEvent => gui.handleGuiProgressEvent(event)
    }
  }


  class WorkerActor(val guiUpdateActor: ActorRef) extends Actor {
    def receive = {
      case "Start" => {
        for (percentDone <- 0 to 100) {
            Thread.sleep(50)
            guiUpdateActor ! GuiProgressEvent(percentDone)
        }
      }
      guiUpdateActor ! ProcessingFinished
    }
  }

}
+4

If you need something simple, you can run a long task in a new thread and just be sure to update it in EDT:

  def swing(task: => Unit) = SwingUtilities.invokeLater(new Runnable {
     def run() { task }
  })
  def thread(task: => Unit) = new Thread(new Runnable {
     def run() {task}
  }).run()

  thread({
    val stuff = longRunningTask()
    swing(updateGui(stuff))
  })
+1
source

All Articles