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:
Files Used in this Project
- RssReader.scala: A library for reading RSS information, that I wrote in the previous project.
- RssGui.scala: The sourcecode for this project.
- feeds.txt: A sample input file.
The Code
Libraries
import RssReader._
import swing._
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
object RssGui extends SimpleSwingApplication {
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
var rssFeeds = List(("None Loaded", Seq("")))
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
var configFileChooser = new FileChooser(new java.io.File("./"))
var configFileName = new String
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
val channelLabel = new Label {
text = "Select a channel:"
}
val channelSelect = new ComboBox(rssFeeds(0)._1) { }
val channelSelectPanel = new BoxPanel(Orientation.Vertical) {
contents += channelLabel
contents += channelSelect
border = Swing.EmptyBorder(0, 0, 0, 30)
}
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
val rssDisplay = new ListView(rssFeeds(0)._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
val channelPanel = new BoxPanel(Orientation.Horizontal) {
contents += channelSelectPanel
contents += rssDisplay
border = Swing.EmptyBorder(0, 0, 30, 0)
}
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
val setConfigFileButton = new Button {
text = "Set Config File"
}
val loadRssButton = new Button {
text = "Load Feeds"
}
val buttonPanel = new BoxPanel(Orientation.Horizontal) {
contents += setConfigFileButton
contents += loadRssButton
}
Create the two buttons and lays them out in a panel.
Top Window
val topWindow = new BoxPanel(Orientation.Vertical) {
contents += channelPanel
contents += buttonPanel
border = Swing.EmptyBorder(10, 10, 10, 10)
}
Add the two top level panels together and put a border around them to make it neat.
Defining Behaviors
The Last Bit of Layout
def top = new MainFrame {
title = "Rss Reader"
// Overall construction
contents = topWindow
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
listenTo(channelSelect.selection, setConfigFileButton, loadRssButton)
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:
listenTo(channelSelect.selection)
listenTo(setConfigFileButton)
listenTo(loadRssButton)
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.
reactions += {
case swing.event.SelectionChanged(`channelSelect`) =>
rssDisplay.listData = rssFeeds(channelSelect.selection.index)._2
pack
case swing.event.ButtonClicked(`setConfigFileButton`) =>
if(configFileChooser.showOpenDialog(channelSelectPanel) ==
FileChooser.Result.Approve) {
configFileName = configFileChooser.selectedFile.getAbsolutePath
}
case swing.event.ButtonClicked(`loadRssButton`) =>
if(configFileName != "") {
rssFeeds = combineFeeds(getRssFeeds(configFileName))
channelSelect.peer.setModel(ComboBox.newConstantModel(rssFeeds.map(_._1)))
rssDisplay.listData = rssFeeds(0)._2
pack
}
}
}
}
For readability I’ve left the entire set intact above, I’ll look at them for each case here.
A New Channel is Selected
case swing.event.SelectionChanged(\`channelSelect\`) =>
rssDisplay.listData = rssFeeds(channelSelect.selection.index)._2
pack
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
case swing.event.ButtonClicked(\`setConfigFileButton\`) =>
if(configFileChooser.showOpenDialog(channelSelectPanel) ==
FileChooser.Result.Approve) {
configFileName = configFileChooser.selectedFile.getAbsolutePath
}
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
case swing.event.ButtonClicked(\`loadRssButton\`) =>
if(configFileName != "") {
rssFeeds = combineFeeds(getRssFeeds(configFileName))
channelSelect.peer.setModel(ComboBox.newConstantModel(rssFeeds.map(_._1)))
rssDisplay.listData = rssFeeds(0)._2
pack
}
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:
- The error messages are sometimes a bit obscure, but this is a common flaw in all but the most mature open source projects.
- 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.
I’m learning Clojure and Scala, thanks for writing such a good tutorial.
I’m happy to have helped. Thank you for the feedback.