Virtuous Programmer Adventures of an Autodidact

27Dec/102

Scala 4/4: GUI

Writing GUI Code in Scala

This is the last article in a series, to read the whole series go here.

The Plan

For the last week of Scala I've put together a GUI for week 3's RSS aggregator using the Swing library. Scala's Swing library is a fairly thin wrapper around Java's Swing library. The primary difference between them is that Scala's library takes advantage of its more powerful type system.

What to Expect

When this article is complete:

  • You will have:
    • A front end for the RSS agregator I wrote in week 3.
  • You will know:
    • How to write a basic Swing application in Scala.
Screenshot:

RSS GUI

Files Used in this Project

The Code

Libraries

  1. import RssReader._
  2. import swing._
  3.  

You can use the libraries that you've written in the same way you call any other Java class library. Remember that you must compile RssReader.scala before you try to compile RssGui.scala.

A Top Level Object

  1. object RssGui extends SimpleSwingApplication {
  2.  

The SimpleSwingApplication incorporates all of the basics that you need for a standard windowed application in order to: create, draw and destroy the window when it's closed.

Container for RSS Data

  1.   var rssFeeds = List(("None Loaded", Seq("")))
  2.  

This will hold the data for the RSS feeds. I've chosen to initialize it with "None Loaded" and an empty string because it gives the GUI something to display until a config file is selected and loaded, and it let's Scala know what the rssFeeds's type is.

File Chooser

  1.   var configFileChooser = new FileChooser(new java.io.File("./"))
  2.   var configFileName = new String
  3.  

The configFileChooser is a FileChooser that will open, defaulting to the current working directory. configFileName will hold the value selected, the details for how this will work are in the "Defining Behaviors" section.

Layout

For the most part the code here stands for itself, which is wonderful. Once you understand the basics for creation and layout for one component, they're all the same. As a result I've be described each new idea in detail when I encountered it and let the rest alone.

Channel Panel Construction
Channel Select
  1.   val channelLabel = new Label {
  2.     text = "Select a channel:"
  3.   }
  4.  
  5.   val channelSelect = new ComboBox(rssFeeds(0)._1) { }
  6.  
  7.   val channelSelectPanel = new BoxPanel(Orientation.Vertical) {
  8.     contents += channelLabel
  9.     contents += channelSelect
  10.     border = Swing.EmptyBorder(0, 0, 0, 30)
  11.   }
  12.  

Lines 1-3: Create a new label with "Select a channel:" for its value.
Line 5: Create a ComboBox that is initialized to the first tuple value in the first list value: "None Loaded"
Line 7: Create a new panel that is filled top to bottom.
Line 8-9: Add the Label and ComboBox to the panel.
Line 10: Surround the panel in an empty border with 30 pixels on the right side (to separate it from the RssDisplay).

Rss Display
  1.   val rssDisplay = new ListView(rssFeeds(0)._2)
  2.  

This creates a nearly empty ListView. It has one line with an empty string. Until it's populated with something more complex, it won't display at all.

Combining channelPanel
  1.   val channelPanel = new BoxPanel(Orientation.Horizontal) {
  2.     contents += channelSelectPanel
  3.     contents += rssDisplay
  4.     border = Swing.EmptyBorder(0, 0, 30, 0)
  5.   }
  6.  

This creates a new panel with Orientation.Horizontal so each element will be added left to right. The channelSelectPanel and rssDisplay are added to complete the channelPanel.

Button Panel Construction
  1.   val setConfigFileButton = new Button {
  2.     text = "Set Config File"
  3.   }
  4.  
  5.   val loadRssButton = new Button {
  6.     text = "Load Feeds"
  7.   }
  8.  
  9.   val buttonPanel = new BoxPanel(Orientation.Horizontal) {
  10.     contents += setConfigFileButton
  11.     contents += loadRssButton
  12.   }
  13.  

Create the two buttons and lays them out in a panel.

Top Window
  1.   val topWindow = new BoxPanel(Orientation.Vertical) {
  2.     contents += channelPanel
  3.     contents += buttonPanel
  4.     border = Swing.EmptyBorder(10, 10, 10, 10)
  5.   }
  6.  

Add the two top level panels together and put a border around them to make it neat.

Defining Behaviors

The Last Bit of Layout

  1.   def top = new MainFrame {
  2.     title = "Rss Reader"
  3.  
  4.     // Overall construction
  5.     contents = topWindow
  6.  

This creates the overall window and assigns topWindow as its contents. The title "Rss Reader" will appear in the title bar of the window.

Listening

  1.     listenTo(channelSelect.selection, setConfigFileButton, loadRssButton)
  2.  

Each of the GUI elements that you want to be able to have act as controllers must be listed in listenTo. I've chosen to add them all at once, you can also add them one at a time as in:

  1.    listenTo(channelSelect.selection)
  2.    listenTo(setConfigFileButton)
  3.    listenTo(loadRssButton)
  4.  

To determine when a value has been selected we are listening to channelSelect.selection instead of channelSelect.

Reacting

For each action performed on one of the listenTo'ed elements it will run through each of the listed reactions to see if any of them are appropriate for the action/object combination.

  1.     reactions += {
  2.       case swing.event.SelectionChanged(`channelSelect`) =>
  3.         rssDisplay.listData = rssFeeds(channelSelect.selection.index)._2
  4.         pack
  5.       case swing.event.ButtonClicked(`setConfigFileButton`) =>
  6.         if(configFileChooser.showOpenDialog(channelSelectPanel) ==
  7.             FileChooser.Result.Approve) {
  8.           configFileName = configFileChooser.selectedFile.getAbsolutePath
  9.         }
  10.       case swing.event.ButtonClicked(`loadRssButton`) =>
  11.         if(configFileName != "") {
  12.           rssFeeds = combineFeeds(getRssFeeds(configFileName))
  13.           channelSelect.peer.setModel(ComboBox.newConstantModel(rssFeeds.map(_._1)))
  14.           rssDisplay.listData = rssFeeds(0)._2
  15.           pack
  16.         }
  17.     }
  18.   }
  19. }
  20.  

For readability I've left the entire set intact above, I'll look at them for each case here.

A New Channel is Selected
  1.       case swing.event.SelectionChanged(\`channelSelect\`) =>
  2.         rssDisplay.listData = rssFeeds(channelSelect.selection.index)._2
  3.         pack
  4.  

Line 1: The case looks for the event, SelectionChanged and to which element that event is applied, channelSelect, the backquotes above are important.
Line 2: When the selection changes, set the display to contain the story titles from the rssFeeds value with the same index as the value selected.
Line 3: Once the values have been changed, packs the window so everything fits.

The Config File Button is Clicked
  1.       case swing.event.ButtonClicked(\`setConfigFileButton\`) =>
  2.         if(configFileChooser.showOpenDialog(channelSelectPanel) ==
  3.             FileChooser.Result.Approve) {
  4.           configFileName = configFileChooser.selectedFile.getAbsolutePath
  5.         }
  6.  

Line 2-3: Opens the configFileChooser and checks to make sure that a value has come back.
Line 4: Assigns the absolute path to the selected file to configFileName.

The Load Rss Button is Clicked
  1.       case swing.event.ButtonClicked(\`loadRssButton\`) =>
  2.         if(configFileName != "") {
  3.           rssFeeds = combineFeeds(getRssFeeds(configFileName))
  4.           channelSelect.peer.setModel(ComboBox.newConstantModel(rssFeeds.map(_._1)))
  5.           rssDisplay.listData = rssFeeds(0)._2
  6.           pack
  7.         }
  8.  

Line 2: Verifies that some value has been assigned to configFileName.
Line 3: Uses combineFeeds from RssReader to read in the RSS values.
Line 4: Set's the channelSelect ComboBox to contain the names of each of the RSS feeds.
Line 5. Set's the rssDisplay to contain the titles from teh first feed.

Final Summary

Scala has been an extraordinarily interesting language. The XML package alone makes it worth adding to your toolbox and, as can be seen by how clean the GUI design code is, it's not a one trick pony. The type system, which sadly I haven't been able to go into nearly enough detail here, has many of the nice features you'll find in Haskell. And of course it's great to be able to make use of all of Java's libraries for free.

I do have a couple of small complaints:

  1. The error messages are sometimes a bit obscure, but this is a common flaw in all but the most mature open source projects.
  2. When code compiles, each anonymous function gets its own class. For anyone coding in a functional style, this quickly leads to a deeply cluttered directory.

These complaints are minor though, and the first can certainly be fixed. Overall it's been a pleasure to work with and I look forward to using it for real tasks in the near future.

Coming Up

Next week I'll be starting on Prolog. Prolog is a declarative language that is centered on formal logic.

Resources

Posted by Frank Berthold

Filed under: gui, scala Leave a comment
Comments (2) Trackbacks (0)
  1. I’m learning Clojure and Scala, thanks for writing such a good tutorial.


Leave a comment

No trackbacks yet.