Saturday 8 May 2010

Scala Swing ComboBox part 2

In the previous entry here I showed how to create an application using Scala Swing that contained a ComboBox and Button.

In this post I want to expand that example by showing you how to select a ComboBox item programmatically and also listen to changes to the ComboBox.

The code below shows how to change the ComboBox selection programmatically.

import swing.Swing._
import swing._
import event.{ButtonClicked, WindowClosing}
import swing.ListView._

class ScalaSwingDemo2 extends Frame {
title = "Scala Swing Demo 2"
case class OS(name:String, version:String)
val snowLeopard = OS("Snow Leopard", "10.6.3")
val comboBox = new ComboBox(List(OS("Ubuntu", "10.04"), snowLeopard, OS("Windows", "7"))) {
renderer = Renderer(_.name)
}
val button = new Button {
text = "Select Snow Leopard OS"
}
contents = new FlowPanel {
contents += new Label("Select OS:")
contents += comboBox
contents += button
}
listenTo(button)
reactions += {
case WindowClosing(e) => System.exit(0)
case ButtonClicked(`button`) => comboBox.selection.item = snowLeopard
}
pack
centerOnScreen
visible = true
}
object ScalaSwingDemo2Runner {
def main(args: Array[String]) {
onEDT(new ScalaSwingDemo2)
}
}
This code is very similar to before so I won't bother explaining it again. The difference is what happens when the button is clicked. The code called is:

comboBox.selection.item = snowLeopard

This sets the selected item of the ComboBox to the specified OS class, in this case Snow Leopard. Notice that it is expecting a class of type OS without casting. This is statically checked and I can prove this by changing the line to:

comboBox.selection.item = "Snow Leopard"

If you try and compile this you will get a compile time error.

This is very simple but is just another example of how concise Scala can be.

The next bit of code shows how to listen to selection changes on the ComboBox.

import swing.Swing._
import swing._
import event.{SelectionChanged, ButtonClicked, WindowClosing}
import swing.ListView._

class ScalaSwingDemo3 extends Frame {
title = "Scala Swing Demo 3"
case class OS(name:String, version:String)
val snowLeopard = OS("Snow Leopard", "10.6.3")
val comboBox = new ComboBox(List(OS("Ubuntu", "10.04"), snowLeopard, OS("Windows", "7"))) {
renderer = Renderer(_.name)
}
val button = new Button {
text = "Select Snow Leopard OS"
}
contents = new FlowPanel {
contents += new Label("Select OS:")
contents += comboBox
contents += button
}
listenTo(button)
listenTo(comboBox.selection)
reactions += {
case WindowClosing(e) => System.exit(0)
case ButtonClicked(`button`) => comboBox.selection.item = snowLeopard
case SelectionChanged(`comboBox`) => {
val selectedOS = comboBox.selection.item
println("Selected OS Name: " + selectedOS.name + " Version: " + selectedOS.version)
}
}
pack
centerOnScreen
visible = true
}
object ScalaSwingDemo3Runner {
def main(args: Array[String]) {
onEDT(new ScalaSwingDemo3)
}
}
We've added another reaction to our Frame and told our Frame to listen to another publisher. We want to listen to the selection of the ComboBox so achieve this with this line:

listenTo(comboBox.selection)

When a user selects a different item in the ComboBox, the block of code after the case SelectionChanged(`comboBox`) => is run. In this case the block of code just prints out information about the selected item.

The button functionality is still available. If you click the button (when Snow Leopard is not selected), you will see that Snow Leopard is selected, and the SelectionChanged block is run. This is what you would expect. However, when selecting something programmatically, sometimes you would not want that block of code to be run. Scala Swing provides an easy way to deal with this, which the following code demonstrates.

import swing.Swing._
import swing._
import event.{SelectionChanged, ButtonClicked, WindowClosing}
import swing.ListView._

class ScalaSwingDemo4 extends Frame {
title = "Scala Swing Demo 4"
case class OS(name:String, version:String)
val snowLeopard = OS("Snow Leopard", "10.6.3")
val comboBox = new ComboBox(List(OS("Ubuntu", "10.04"), snowLeopard, OS("Windows", "7"))) {
renderer = Renderer(_.name)
}
val button = new Button {
text = "Select Snow Leopard OS"
}
contents = new FlowPanel {
contents += new Label("Select OS:")
contents += comboBox
contents += button
}
listenTo(button)
listenTo(comboBox.selection)
reactions += {
case WindowClosing(e) => System.exit(0)
case ButtonClicked(`button`) => {
deafTo(comboBox.selection)
comboBox.selection.item = snowLeopard
listenTo(comboBox.selection)
}
case SelectionChanged(`comboBox`) => {
val selectedOS = comboBox.selection.item
println("Selected OS Name: " + selectedOS.name + " Version: " + selectedOS.version)
}
}
pack
centerOnScreen
visible = true
}
object ScalaSwingDemo4Runner {
def main(args: Array[String]) {
onEDT(new ScalaSwingDemo4)
}
}
Here, when the button is clicked, before we select the snowLeopard item, we tell the frame to be deaf to the ComboBox selection with this line of code:

deafTo(comboBox.selection)

Once the item has been selected we want to listen to the selection again so call:

listenTo(comboBox.selection)

That's it for this post. I hope Java Swing developers recognise what I'm doing here and how much easier it is when using Scala Swing. Remember as well, I'm only using the standard Scala Swing library.

Monday 26 April 2010

Scala Swing ComboBox

Swing has got a bad reputation. If it's not people saying it's too slow, it's people saying it's dead.

I've never had a problem with Swing. I've always been able to make it do what I want, and always managed to make it look good in the process.

I've been working completely in Scala now for at least the last year and recently I had to knock up a GUI. I could have done this using Swing from Scala as Scala is nice and flexible like that. However, I got to like the "Scala way of doing things" so much, I thought I would have a look at the scala.swing library.

I'm glad I did. It makes Swing a joy to use as I'll show you over the next couple of posts.

One of the problems people have with Swing is that it doesn't support generics in components such as JComboBox and JList. Scala Swing does allow this. In fact, they pick up the type automatically.

import swing.Swing._
import swing._
import event.{ButtonClicked, WindowClosing}
import swing.ListView._

class ScalaSwingDemo extends Frame {
title = "Scala Swing Demo"

case class OS(name:String, version:String)
val comboBox = new ComboBox(List(OS("Ubuntu", "10.04"), OS("Snow Leopard", "10.6.3"), OS("Windows", "7"))) {
renderer = Renderer(_.name) // The renderer is just the name field of the OS class.
}

val button = new Button {
text = "Output Selected OS"
}

contents = new FlowPanel {
contents += new Label("Select OS:")
contents += comboBox
contents += button
}

listenTo(button) // Means this frame will listen to this button.

reactions += {
case WindowClosing(e) => System.exit(0)
case ButtonClicked(`button`) => { // This is what happens when the button is clicked.
val selectedOS = comboBox.selection.item // This returns the type OS.
println("Selected OS Name: " + selectedOS.name + " Version: " + selectedOS.version)
}
}

pack
centerOnScreen
visible = true
}

object ScalaSwingDemoRunner {
def main(args: Array[String]) {
onEDT(new ScalaSwingDemo)
}
}
The main method is specified in the object ScalaSwingDemoRunner. I call onEDT which is a method on the scala.swing.Scala object. This method takes a function. In this case all I want to do is create my main frame.

The whole body of the ScalaSwingDemo is the constructor in Scala. I extend the scala Frame class. This allows me to set the title with an equals sign. Note that this is actually calling a method and not setting a field.

The next thing I do is define a class called OS. In Scala, case classes provide a nice way to specify a class without all the boilerplate associated with Java. I then create a ComboBox and pass through a List of these OS classes. It's nice to be able to specify a List with a "literal". I think this is coming in JDK7 as well.

Inside the ComboBox definition I specify the renderer. In this case I use a standard Renderer object that takes a function. I know that the OS class has got the "getter method" called name. I want the result of the name method call to be displayed in my ComboBox, so I specify that there. I say "whatever gets passed into this renderer, call the name method and display the result". This is all statically typed. The ComboBox knows that it is displaying OS objects so the renderer knows that it will get something with a name method on it.

I then create a Button and specify the text of the Button in a similar way to the way I specified the title of the Frame.

Next I specify the contents of the Frame. All I'm using here is a FlowPanel, which is very similar to a JPanel with a FlowLayout set.

If I want to react to the Button I have got various options but in this case I just say the Frame should listen to it.

I then add reactions to the Frame to specify what to do when certain events I'm interested in happen. In this case it is exiting when the Frame is closing and printing out info about the selection in the ComboBox when the button is clicked.

This is the interesting bit. Where I call, comboBox.selection.item I actually get back an object of type OS. That's pretty sweet and allows me to call methods on it without casting.

Here is a screen shot of it in action:


This is a pretty simple example but I hope it shows quite a few interesting ways in which Scala Swing makes creating a GUI a joy.

Oh, and there are no performance problems!