In software development there are no silver bullets, but I am always looking out for the next bronze one.

Wednesday, November 14, 2007

GUI Building with Scala

One the strengths of Scala is a flexible syntax that allows for Scala code be more like a Domain Specific Language. A while back, I started to put such a Scala "DSL" together inspired in part by what was then known as F3, but has since been re-branded, JavaFX. This idea languished for few months while I got distracted by other pursuits. I've decided to publish what I have produced so far to get feedback about it and determine if such a thing is a worth continuing.


I've published the supporting classes in a jar file available here
along with a simple example program. Ensure the jar above is on the CLASSPATH, compile and run the example program:

C:\tmp>scalac -cp ScalaGuiBuilderV0_1.jar GuiBuilderSimple.scala

C:\tmp\scala -cp .;ScalaGuiBuilderV0_1.jar tfd.scala.guibuilder.demo.GuiBuilderSimple


Simple Example Code:

package tfd.scala.guibuilder.demo

object GuiBuilderSimple extends Application {
import java.awt.{Color, FlowLayout}
import java.awt.event.ActionEvent
import javax.swing.JFrame

import tfd.scala.guibuilder._

var frm = frame(
attributes(
'title->"GUIBuilder Simple Demo",
'defaultCloseOperation->JFrame.EXIT_ON_CLOSE
),
flowlayout(attributes('alignment -> FlowLayout.LEFT)),
contents(
button("PressButton", attributes('text->"Press" ,'foreground->Color.blue)),
label("PressLabel", attributes('text->"Not Pressed",'foreground->Color.red))
)
)

button.onAction("PressButton",(ae: ActionEvent) => {
label.applyAttributes("PressLabel", attributes('text -> "Pressed", 'foreground -> Color.black))
})

frm.pack
frm.setVisible(true)
}


Running the program will create a window like the first one below:






Before the button is pressed

After the button is pressed


So what's going on here ? Each component is represented by a Scala object with overloaded "apply" methods that represent various options of constructing the component. In other words, they are factories. To prevent confusion or collisions with existing class names, the component objects names are all lowercase. If "Frame" was used instead of "frame" then it becomes unwieldy if the program wants to "import java.awt.Frame". The "attributes" are also processed by a factory object via its "apply" method. I could have used "List(<attribute1>,<attribute2>...)" or "<attribute1> :: <attribute2> ... :: Nil", for listing the attributes, but I feel it's more concise to use "attributes". It would be nice if Scala supported lists in more shorthanded way like Erlang or Ocaml then I could have done something like "[<attribute1>,<attribute2>...]".


I opted to use Scala symbols for the attribute names to save a keystroke and reduce problems from trailing quotes being omitted. Using strings would have required code like:

attributes(
"title"->"GUIBuilder Simple Demo",
"defaultCloseOperation"->JFrame.EXIT_ON_CLOSE
)

Feedback on the using string versus symbols would be appreciated.


Let's break down what is going on in the sample above:


  • The "frame" factory object returns the JFrame it constructed and takes the following parameters:
    - The "attributes" represent mappings of bean properties to values. The library will use introspection to look for get methods to invoke in order to set the attribute.
    - A layout manager specification. Here the "flowlayout" object will return a java.awt.Flowlayout to be used for the content pane of the frame.
    - The "contents" object specifies the components to be added to the frame's content pane using the specified layout manager. Here, since we are using a FlowLayout, we don't need to provide constraints for the layout manager in the contents. Using more complicated layout managers will require such constraints in the contents.
    - The components added here are simply a button and label which map to Swing JButton and JLabel components respectively. The first parameter passed to their "constructor" object is an optional string identifier that allows the component to be looked up later using an "id" method on the factory object. Example 'var btn = button.id("PressButton")' will assign the JButton with id of "PressButton" to variable "btn".

  • The button.onAction method takes the string id for the button and function object with an ActionEvent parameter and adds an ActionListener to the button that delegates it's actionPerformed call to the passed function object. The body of the function object invokes an applyAttributes method on the label applies attribute to a label specifies by the string id. The same can can accomplished in a more Java like style with the code below:

    button.onAction("PressButton",(ae: ActionEvent) => {
    var label = label.id("PressLabel")
    label.setText("Pressed")
    label.setForeground(Color.black)
    })


  • Finally, the JFrame is packed and made visible.



As of yet, the library only supports a subset of the Swing components. I plan to follow up with more posts shortly with examples showing how to use constrained components in frames and panels with other layout managers (including GridBagLayout), show more supported component types and other features I have built into the library. I will also publish the source for the library.

10 comments:

Anonymous said...

Nice. I've been looking for some Scala examples that aren't parser combinators ;)

I think MigLayout could make for a nice Scala DSL as well.

tdalton said...

Funny, I just looked at MigLayout and thought that a demonstration of how to integrate it with the Scala GUI Builder would be a good subject for a future post.

ZackMan said...

Cool, I didn't know that Scala had symbols. Since you're using them, why not use them for IDs too? Is it a matter of compatibility with Swing?

Two other things:
[1] If there is a small set of common properties for all Swing components, case classes would be nicer than symbols, since they could be checked at compile time. But I don't think there is enough homogeneity to Swing properties to make it worthwhile.
[2] Is it possible to use variable-arity arguments to get rid of "attributes"? I would think the type of button, for example, could change from
def apply(id : String, attrs : attributes)
to
def apply(id : String, attrs : (Symbol, attribute)*)
and used thus
button("id", 'text -> "Press", 'foreground -> Color.blue)

I'm not certain this will work, though. Actually, what type is attributes.apply? It looks like you're already doing variable arity there.

Disclaimer: I know very little about Swing.

Augusto said...

Is it me or does a lot of that code look very similar to JavaFX (or the other way around)?

Wojciech Halicki-Piszko said...

Just to make it clear F3 was rebranded to JavaFX Script. Yes, I know everone under the Sun will call it JavaFX or even JFX, but that not the name.

Nice to see Scala showing its strengths in GUI building.

Anonymous said...

Fantastic... something that makes Swing GUI building easy in Scala can only help...

Looking forward to full Swing API support and source code so that others (including myself) may assist in getting it off of the ground.

Anonymous said...

button.onAction("PressButton",(ae: ActionEvent) => {
label.applyAttributes("PressLabel", attributes('text -> "Pressed", 'foreground -> Color.black))
})


That's pretty ugly, why not:

label.text = "Pressed";
label.foreground = Color.black;

In boo for example the whole event handler would look like:

button.PressButton += do:
label.text = "Pressed";
label.foreground = Color.black;


It seems like Scala is replacing Java's bulky object-declarative syntax with ugly functional closures.

Anonymous said...

button.onAction("PressButton",(ae: ActionEvent) => {
label.applyAttributes("PressLabel", attributes('text -> "Pressed", 'foreground -> Color.black))
})

Perhaps I'm getting something wrong, but how can you access an object "button" in this line. Why do you lateron still need an identifier to get the the button?
I don't like the idea of using string identifiers instead of plain old references because it will cut down ide support for tracking elements.

tdalton said...

button.onAction("PressButton",(ae: ActionEvent) => {
label.applyAttributes("PressLabel", attributes('text -> "Pressed", 'foreground -> Color.black))
})

Perhaps I'm getting something wrong, but how can you access an object "button" in this line.


Object "button" is a singleton object that is accessible everywhere and can be used to look up buttons by id.

Why do you later on still need an identifier to get the the button?

Ideally, I don't want to look up the label every event. I do so in the example in the interest of brevity. One could look-up the label beforehand access it within the event handler (which is a closure):


val pressLabel = label.id("PressLabel")
button.onAction("PressButton",
 (ae: ActionEvent) => {
   label.applyAttributes(pressLabel,
   attributes(
    'text -> "Pressed",
    'foreground -> Color.black))
})


or call setters directly:

val pressLabel = label.id("PressLabel")
button.onAction("PressButton",
 (ae: ActionEvent) => {
  pressLabel.setText("Pressed")
  pressLabel.setForeground(Color.black)
})



I don't like the idea of using string identifiers instead of plain old references because it will cut down ide support for tracking elements.

You don't have to use the string identifiers you can create the components and embed them by reference:


val pressMeButton = button("Press Me")

val pnl = panel(
  contents(pressMeButton)
)


I'm re-doing the event handling so you can do something like the following:


button(
  attributes('text->"Press Me"),
  events(actionPerformed {
     System.out.println("Pressed")
  })
)


I'm hoping to update SQUIB and blog about it soon.

datSilencer said...

Hi, nice post. Looking at the sample Scala GUI code made me think that it bears a striking resemblance to what can be done with SwiXML, i.e. an XML file specifying the GUI's layout, elements, properties, etc.

Thanks!