Homepage von Jean-René Thies — Projekte & Tools rund um Java, Groovy, Grails, PHP, Scala, MySQL...
    « Eine tierische Basic-to-Java ÜbersetzungScala-Anwendungen außerhalb von Netbeans starten »

    Tutorial: Erste GUI-Anwendungen mit Scala

    Permalink 30.07.09 14:12, von jrt, Kategorien: Project Plaza , Schlagworte: action, actor, gui, progressbar, scala, swing

    Dank Programming in Scala stellen sich nach und nach erste Erfolgserlebnisse mit dieser Programmiersprache ein, und sie macht alles in allem richtig Spaß – obwohl die Unterstützung in den gängigen IDEs bislang nur rudimentär ist.
    Was mich besonders verblüfft: Die extrem kompakte Art, mit Scala grafische Benutzeroberflächen auf Swing-Basis aufzubauen. Vorher hatte ich schon mit JavaFX und Groovy SwingBuilder experimentiert, aber keines von beiden war so überzeugend.
    Hier ein paar grundlegende Schritte zur GUI-Erzeugung.

    Fortsetzung:

    Wir beginnen mit einem einfachen Objekt, das die Klasse SimpleGUIApplication erweitert:

    Code:

    package gui
     
    import swing._
     
    object Main extends SimpleGUIApplication {
       def top = new MainFrame {
          title="Test"
       }
    }

    Das ergibt ein einfaches Programmfenster mit dem Titel „Test“. Die Import-Anweisung bezieht sich auf das Package „scala.swing“. Dabei kann man „scala“ einfach weglassen und der Unterstrich entspricht dem Wildcard-Sternchen in Java.
    Wahrscheinlich erscheint das Programmfenster in der linken oberen Ecke auf dem Bildschirm in geringer Größe. Das kann man sehr leicht ändern, indem man in der „top“-Methode Koordinaten und eine Größenangabe hinzufügt. Mit etwas mehr Code bekommt man auch eine automatische Zentrierung auf dem Bildschirm hin:

    Code:

    package gui
     
    import swing._
     
    object Main extends SimpleGUIApplication {
     
       def top = new MainFrame {
     
          title="Test"
     
          //Größe und Position auf dem Bildschirm
          val framewidth = 640
          val frameheight = 480
          val screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize()
          location=new java.awt.Point((screenSize.width-framewidth)/2, (screenSize.height-frameheight)/2)
          minimumSize=new java.awt.Dimension(framewidth, frameheight)
     
       }
    }

    Damit haben wir ein Fenster in 640 x 480 Pixel Größe in der Mitte des Bildschirms. Um dem ganzen noch etwas Ästhetik zu verleihen, fügen wir ein LookAndFeel sowie ein Programm-Icon hinzu, das im Titelbalken erscheinen soll.

    Code:

    package gui
     
    import swing._
    import javax.swing.UIManager
     
    object Main2 extends SimpleGUIApplication {
     
       def top = new MainFrame {
     
          //Look and Feel
          try{
             UIManager.setLookAndFeel(new com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel);
          } catch {
             case _ => UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
          }
     
          //Frametitel und Icon
          title="Test"
          iconImage = java.awt.Toolkit.getDefaultToolkit.getImage(resourceFromClassloader("/gui/img/icon.png"))
     
          //Größe und Position auf dem Bildschirm
          //(...)
       }
    }

    Die Auswahl des LookAndFeel kann eine Exception auslösen – in diesem Fall genau dann, wenn das NimbusLookAndFeel nicht vorhanden ist. Die Exception wird Scala-typisch mit einer „case“-Anweisung abgefangen. Normalerweise sollte man hier für jede mögliche Exception eine eigene Anweisung schreiben. Ich habe mich für den Default-case mit „_“ entschieden und als Fallback das System-LookAndFeel eingestellt.
    Auch diese Anweisung kann theoretisch eine Exception auslösen, aber Scala zwingt nicht dazu, diese auch noch abzufangen. Für den Wechsel des LookAndFeel braucht man zwingend den javax.swing.UIManager.

    Mit „iconImage“ kann man nun das Programmicon als Resource laden und dem Frame hinzufügen. Ich empfehle dazu PNG-Grafiken mit transparentem Hintergrund.

    Die geerbte Klasse SimpleGUIApplication setzt auf dem Swing Application Framework auf. Damit lassen sich GUI-Elemente wie Menübefehle und Buttons sehr leicht an Actions binden. Und Actions kann man natürlich auch in Scala schreiben. Diese hier zeigt einfach einen Dialog mit einer kurzen Nachricht an:

    Code:

    val diagAction = Action("notify me") {Dialog.showMessage(panel, "Here is a message for you.")}

    In den runden Klammern nach Action ist der Name angegeben. Wird diese Action später einem Button oder einem Menübefehl zugewiesen, übernehmen sie automatisch den Namen für die Beschriftung. Für „Dialog“ braucht man keinen zusätzlichen Import, da diese Klasse in scala.swing enthalten ist. Als Parameter der showMessage-methode muss mindestens eine Komponente wie ein Panel und der anzuzeigende Text als Parameter angegeben werden.

    Sind die benötigten Actions definiert, kann man sie z.B. an Menübefehle koppeln.

    Code:

    menuBar = new MenuBar{
             contents += new Menu("File") {
                contents += new MenuItem(quitAction)
             }
             contents += new Menu("Edit") {
                contents += new MenuItem(parallelAction)
             }
          }

    Hier haben wir zwei Menüs namens „File“ und „Edit“ mit jeweils einem Menübefehl darunter. Zur „parallelAction“ komme ich gleich noch. Die „quitAction“ kann man einfach so definieren:

    Code:

    val quitAction = Action("Quit") {System.exit(0)}

    Langsam wird’s interessant: Der nächste Menübefehl soll nun einen Vorgang im Hintergrund starten, der seinen Fortschritt an die Benutzeroberfläche zurückmeldet. Und das möchten wir natürlich mit einer ProgressBar mitverfolgen.
    Aus Swing ist das Observer-Pattern bekannt, Scala bietet aber noch eine Alternative: Actors. Die kann man im Prinzip als Threads mit Briefkasten verstehen. Actors laufen in separaten Threads und können auf Nachrichten von außen reagieren. Ein sehr einfacher Actor könnte z.B. so aussehen:

    Code:

    import actors._
    import actors.Actor._
     
    class MyCounter() extends Actor {
       def act() {
          loop {
             react {
                //start action and permanently send feedback
                case (reciever:Actor) => for(i <- 1 to 100000) reciever ! i
             }
          }
       }
    }

    Wenn der Actor gestartet wird, passiert zunächst nichts. Er wartet auf eine Nachricht, die ihm einen Empfänger mitteilt, der ebenfalls ein Actor sein soll. Sobald eine solche Nachricht eintrifft, startet die for-Schleife und mit jeder Iteration erfolgt eine Rückmeldung an den Empfänger.

    Der Empfänger ist natürlich die Benutzeroberfläche, die die Werte auffängt und an eine ProgressBar weitergibt. Allerdings muss diese nicht die ganze Zeit über auf Empfang sein, sondern nur ab dem Start des externen Actors. Hier eine Action der Benutzeroberfläche:

    Code:

    val parallelAction = Action("hundred thousand messages"){
             val reciever: Actor = actor {loop {react {case (increment:Int) => pbar.value=increment}}}
             val task=new MyCounter().start
             task ! reciever
          }

    Die Action „hundred thousand messages“ erzeugt zunächst einen Empfänger und startet ihn sofort. Dieser Empfänger ist ein Actor, der auf alle Nachrichten reagiert, die nur aus einem Integer-Wert bestehen. Man könnte das noch verfeinern, indem man nur Nachrichten von einem ganz bestimmten Absender berücksichtigt.
    Direkt danach kommt der externe Actor „MyCounter“, der aber erst mit der letzten Zeile „task ! reciever“ tatsächlich zu zählen beginnt.

    Hier das komplette Beispiel, ergänzt um einen SplitPane mit Labels, Buttons und der ProgressBar:

    Code:

    /*
    * Main.scala
    */
    package gui
     
    import swing._
    import actors._
    import actors.Actor._
    import javax.swing.UIManager
     
    object Main extends SimpleGUIApplication {
     
       def top = new MainFrame {
     
          // 1. Define some actions
     
          //action using actors
          val parallelAction = Action("hundred thousand messages"){
             val reciever: Actor = actor {loop {react {case (increment:Int) => pbar.value=increment}}}
             val task=new MyCounter().start
             task ! reciever
          }
          //action displaying a Dialog
          val diagAction = Action("notify me") {Dialog.showMessage(firstpanel, "Here is a message for you.")}
          //action exit
          val quitAction = Action("Quit") {System.exit(0)}
     
          
          // 2. Set up the GUI
     
          //Look and Feel
          try{
             UIManager.setLookAndFeel(new com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel);
          } catch {
             case _ => UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
          }
      
          //window title and icon
          title="Test"
          iconImage = java.awt.Toolkit.getDefaultToolkit.getImage(resourceFromClassloader("/gui/img/icon.png"))
     
          //arrange size and center frame on screen
          val framewidth = 640
          val frameheight = 480
          val screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize()
          location=new java.awt.Point((screenSize.width-framewidth)/2, (screenSize.height-frameheight)/2)
          minimumSize=new java.awt.Dimension(framewidth, frameheight)
     
          //the menubar with menuitems connected to actions
          menuBar = new MenuBar{
             contents += new Menu("File") {
                contents += new MenuItem(quitAction)
             }
             contents += new Menu("Edit") {
                contents += new MenuItem(parallelAction)
             }
          }
          
          //frame contents with buttons connected to actions
          val firstpanel=new FlowPanel{
             contents += new Label{text="A Label"}
             contents += new Button{action=parallelAction}
          }
          val pbar=new ProgressBar{labelPainted=true; max=100000; value=0}
          val secondpanel=new FlowPanel{
             contents += new Label{text="Another Label"}
             contents += new Button{action=diagAction}
             contents+=pbar
          }
          contents = new SplitPane(Orientation.Vertical, firstpanel, secondpanel){
             dividerLocation=250
             dividerSize=8
             oneTouchExpandable=true
          }
     
       }
    }
     
     
    class MyCounter() extends Actor {
       def act() {
          loop {
             react {
                //start action and permanently send feedback
                case (reciever:Actor) => for(i <- 1 to 100000) reciever ! i
             }
          }
       }
    }

    Und so sieht es am Ende aus:

    1 Kommentar »

    1 Kommentar

    Kommentar von: Mr.Green [Besucher]
    Da es schon sehr spät (oder früh?) ist, habe ich das Tutorial bisher nur überflogen. Werde es mir später genauer anschauen.

    Ich hoffe das es nicht das letzte bleiben wird, da mich Scala interessiert. ;)
    07.07.10 @ 02:40

    Einen Kommentar hinterlassen


    Ihre E-Mail-Adresse wird nicht auf dieser Seite angezeigt.

    Ihr URL wird angezeigt.
    (Zeilenumbrüche werden zu <br />)
    (Name, E-Mail-Adresse & Webseite)
    (Benutzern erlauben, Sie durch ein Kontaktformular zu kontaktieren (Ihre E-Mail-Adresse wird nicht weitergegeben))

    ©2010 by Jean-René Thies

    Kontakt |