Luet oppimateriaalin englanninkielistä versiota. Mainitsit kuitenkin taustakyselyssä osaavasi suomea. Siksi suosittelemme, että käytät suomenkielistä versiota, joka on testatumpi ja hieman laajempi ja muutenkin mukava.

Suomenkielinen materiaali kyllä esittelee englanninkielisetkin termit. Myös suomenkielisessä materiaalissa käytetään ohjelmaprojektien koodissa englanninkielisiä nimiä kurssin alkupään johdantoesimerkkejä lukuunottamatta.

Voit vaihtaa kieltä A+:n valikon yläreunassa olevasta painikkeesta. Tai tästä: Vaihda suomeksi.


Chapter 12.3: GUIs with the Swing Library

About This Page

Questions Answered: How can I put some windows and buttons onscreen? How can a write a GUI without O1’s toolkit?

Topics: The GUI library Swing: creating and laying out GUI elements; registering event listeners and writing event handlers in Swing.

What Will I Do? Read and follow along by trying things out.

Rough Estimate of Workload:? One and a half hours. (This is an entirely optional chapter, so you may just skip it, too.)

Points Available: None.

Related Projects: SwingExamples (new).

../_images/robot_fight1.png

Introduction

First: a llama program

Fetch the SwingExamples project and run o1.llama.LlamaApp.

The application has a GUI with a single window. The window’s contents are divided in three parts: there’s some text at the top, an image in the middle, and a button at the bottom. The program reacts to mouse clicks and the mouse wheel. Try it.

Chapter outline

Chapters 2.7 and 3.1 discussed how you can represent a graphical view as an object and give it methods that handle GUI events. Which is pretty much all you need to learn about GUIs in O1, officially. However, since it’s fun and useful to learn some more about about this very concrete topic, we’ve included this optional chapter in the ebook.

Back in the early weeks, we also noted that there are assorted GUI-building libraries for programmers to pick from. So far in O1, you’ve used O1’s custom library, especially its classes View and Pic. In this chapter, we’ll use a different toolkit.

By the time you finish the chapter, you should understand how the llama app’s GUI works. However, it makes sense to start with a few simpler examples that teach you how to work with individual GUI elements: buttons, text fields, and the like. Here’s what’s coming:

  1. a few words about GUI libraries in general;
  2. using individual GUI elements via the REPL; adjusting component properties;
  3. laying out multiple components;
  4. giving an app a GUI;
  5. reacting to GUI events: mouse clicks, etc.;
  6. a closer look at LlamaApp;
  7. another example GUI: an app that generates random text; a related practice task.

GUI Libraries

One of Scala’s libraries is a GUI library named Swing. Alternatives to the Swing library exist but it’s what we’ll use in this chapter.

O1:n View and Pic are particularly handy then the GUI you need is fairly simple and consists of pictures or geometric shapes that are positioned relative to each other. O1’s toolkit also gives your apps a steadily ticking clock that is easy to use but limited in functionality.

Swing is somewhat less wieldy but works for a wider variety of purposes.

The Swing library consists of classes that represent a variety of GUI elements: there’s a class for buttons, another for windows, still another for menus, and so forth. In your own applications, you can instantiate these classes and combine the various objects as fits your needs. You may also define new kinds of GUI elements by having your classes inherit from the library classes.

The following diagram is replicated from Chapter 7.3. It shows the relationships between some of the main classes in the Swing library; you’ll see examples of most of them in this chapter.

../_images/inheritance_swing1.png

What GUI libraries are there other than Swing?

Eclipse, for instance, has been built using the SWT library. Among Java libraries, Swing’s competitors also include JavaFX, which has a Scala equivalent in ScalaFX.

Qt Jambi and Tcl/Tk are two more examples of well-known GUI libraries.

Some functional programmers adopt a reactive approach to building GUIs.

In case of a web application, the GUI may be take the form of one or more web pages. Web browsers commonly execute JavaScript, but it has recently become possible to embed user-interface code written in Scala onto web pages: Scala.js compiles Scala into JavaScript.

GUI Elements

A frame

Let’s create a GUI element via the REPL. Follow along! As you launch the REPL, select the SwingExamples project.

First, we’ll create a window, also known as a Frame. Frames are reminiscent of the View objects you know from package o1.

import scala.swing._import scala.swing._
val window = new FramemyWindow: scala.swing.Frame = scala.swing.Frame$$anon$1[frame0,0,0,0x0,invalid,hidden, ... ]

Even though no window appeared onscreen yet, we now have a new Frame object that represents a GUI window. Its toString method returns a litany of various window properties, which the REPL prints out.

An instance of Frame is capable of displaying itself onscreen. It doesn’t actually do that, though, since a newly created Frame is invisible by default. We need explicitly to change that:

Adjusting a GUI element’s properties

A Frame’s visibility is governed by its visible property:

myWindow.visible = truemyWindow.visible: Boolean = true

As soon as you issue that instruction, the window shows up. It may be hard to spot at first, though, since it’s very small and may be located in the top corner of your display.

../_images/gui1.png

You can move the window and resize it just like you can do with other windows in your operating system.

If you click X to close the window, the window merely becomes invisible and you can bring it back by making it visible again.

The window doesn’t have a title yet. You can give it one by changing the Frame object’s title property:

myWindow.title = "Experiment"myWindow.title: String = Experiment

Try that, and you’ll see an immediate change in the window title.

A button inside a window

Let’s add a button to the window. Swing’s Button class is easy to instantiate:

val myButton = new Button("Press here")myButton: scala.swing.Button = scala.swing wrapper scala.swing.Button$$anon$1[,0,0,0x0, ... ]

We’ve created another GUI element that doesn’t automatically show up. Even though we have a button object and a visible window, our code doesn’t specify that the button should be part of the window.

If all we want inside our window is that one button, this is all we need:

myWindow.contents = myButtonmyWindow.contents: Seq[scala.swing.Component] = List(scala.swing wrapper scala.swing.Button$$anon$1 ... )

Now the window will look like this:

../_images/gui2-en.png

You can press the button. It gets pressed but nothing really happens as consequence, since we haven’t defined any event handler methods to react to button presses. (We’ll get to that.)

You’ll have noticed that Swing Frames don’t have a makePic method like o1.Views (Chapter 2.7) do. We didn’t construct a picture of the Swing frame; we compose the window’s contents from GUI elements. Correspondingly, the tools for laying out a Swing GUI are different from the lones you know from o1.Pic (place, leftOf, etc.).

Using panels to lay out components

A Swing Frame’s contents property specifies which GUI component should appear within the window frame. Above, we created a window whose entire contents consist of a single button object.

Only a single element may be stored in contents at any given time. However, we’d usually like our GUI windows to contain multiple components. We might want to create this window, for instance:

../_images/gui33.png

The problem is solved by putting components inside one another. Components that go together can be grouped into a panel (paneeli), which panel you can then set as the window’s contents. A panel is a GUI component that can contain multiple other components.

Let’s break down the above image:

The window and its title bar are represented by a Frame object.
A label is a GUI element that contains some text, an image, or both. Swing provides a class scala.swing.Label.
As you already saw, there’s a class scala.swing.Button for buttons.
This window’s contents isn’t either of the buttons or the label but a panel that contains all those three. One of that panel’s properties is that it organizes its contents in a vertical column.

It isn’t hard to create that window. Let’s start by creating the two buttons and the label:

val firstButton = new Button("Press me, please")firstButton: scala.swing.Button = scala.swing wrapper scala.swing.Button$$anon$1[,0,0,0x0, ... ]
val secondButton = new Button("No, press ME!!")secondButton: scala.swing.Button = scala.swing wrapper scala.swing.Button$$anon$1[,0,0,0x0, ... ]
val prompt = new Label("Press one of the buttons.")prompt: scala.swing.Label = scala.swing wrapper scala.swing.Label$$anon$1[,0,0,0x0, ... ]

Now to create a panel that lays out those pieces:

val allPartsTogether = new BoxPanel(Orientation.Vertical)allPartsTogether: scala.swing.BoxPanel = scala.swing wrapper scala.swing.BoxPanel$$anon$1[,0,0,0x0,invalid,
layout=javax.swing.BoxLayout, alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=]
BoxPanel is a particular sort of panel: a BoxPanel’s contents are laid out either horizontally or vertically.
Vertical is a constant. We use it to mean that we want the components in the panel to appear one below the next. (Vertical is a member of the singleton object Orientation, which also provides Horizontal.)

The panel has a contents variable whose type is Buffer[Component]: a bufferful of GUI components. Adding components to the panel is just a matter of manipulating that buffer:

allPartsTogether.contents += prompt
allPartsTogether.contents += firstButton
allPartsTogether.contents += secondButtonres0: Buffer[Component] = Buffer(scala.swing wrapper scala.swing.Label$$anon$1[,0,0, ... ]

We have a panel that contains the buttons and the prompt. Now let’s create a window with that panel as its contents:

val buttonWindow = new FramebuttonWindow: scala.swing.Frame = scala.swing.Frame$$anon$1[frame1,0,0,0x0, ... ]
buttonWindow.contents = allPartsTogetherbuttonWindow.contents: Seq[scala.swing.Component] = List(scala.swing wrapper scala.swing.BoxPanel ... )
buttonWindow.visible = truebuttonWindow.visible: Boolean = true

The two-button window appears onscreen.

Notice that the order in which we created the Button and Label objects made no difference to their layout within the window? That layout is determined by the objects’ order within the panel’s contents buffer. In this example, we put the prompt first, followed by the buttons.

Displaying images

In the previous example, we used a label to display text. The Label class can do more: a label can display an image instead of text (or both so that the text captions the image).

For loading existing images, you can use the Swing class ImageIcon: when you create an ImageIcon object, you can specify where the image data is located. You can load an image from a local file within the project folder, elsewhere on your computer, or a network URL. (In this respect, ImageIcon is like o1.Pic.)

As an experiment, let’s create this window:

../_images/gui4.png

The REPL session below demonstrates how to do that: it creates a Label that displays an image from the internet.

First, let’s import the ImageIcon class and the URL class we’ll use for representing online addresses:

import javax.swing.ImageIconimport javax.swing.ImageIcon
import java.net.URLimport java.net.URL
The tools we use are originally part of Java’s standard library.

Here’s a Label with no text:

val imageLabel = new LabelimageLabel: scala.swing.Label = scala.swing wrapper scala.swing.Label$$anon$1[,0,0,0x0, ... ]

Instead of adding text, we’ll assign to the label’s icon variable. We’ll tell the label to display an image that is available at a particular URL:

imageLabel.icon = new ImageIcon(new URL("http://www.anttisorva.fi/other/Stories9.jpg"))imageLabel.icon: javax.swing.Icon = http://www.anttisorva.fi/other/Stories9.jpg

We still need a window that contains that label:

val imageWindow = new FrameimageWindow: scala.swing.Frame = scala.swing.Frame$$anon$1[frame5,0,0,0x0, ... ]
imageWindow.contents = imageLabelimageWindow.contents: Seq[scala.swing.Component] = List(scala.swing wrapper ... )
imageWindow.visible = trueimageWindow.visible: Boolean = true

javax.swing?

It may seem odd that one of the packages we used is named javax.swing. The Scala library that we’re using here is built upon an identically named library that was originally designed for Java.

Scala programmers make use of Java libraries when it’s convenient. For instance, the current version of Scala’s Swing doesn’t provide its own ImageIcon class; then again, it doesn’t need to, since we can use the class from Java’s Swing.

The letter x — for eXtension — appears in the name of the Java package for historical reasons.

Dialogs

Auxiliary windows that are appear temporarily as part of a GUI are commonly known as dialogs (dialogi). Dialogs serve various purposes, such as displaying messages to the user, asking the user to choose between alternatives, or otherwise prompting for input.

Many dialogs are small windows that contain just a handful of elements whose layout follows a standard pattern. Message-displaying dialogs, for instance, often consist of a little icon, a message, and an OK button:

../_images/gui5-en.png

There’s a Dialog class in scala.swing; it’s similar to Frame. However, it often isn’t necessary to directly instantiate this class, as the class’s companion object provides a selection of convenient factory methods that cater to many common needs. Below is an example of one of those methods, showMessage.

Dialog.showMessage(imageLabel, "Ave, Munde!", "This is a message")
showMessage’s first parameter specifies the dialog’s location: which component the dialog should appear on top of? In this example, we put the dialog on top of the image.
The other parameters specify the message to be displayed and the dialog’s title, respectively.

The above command creates a dialog and displays it. The dialog remains visible until the user presses OK or otherwise closes it.

An Application with A Swing GUI

So far, we did everything in the REPL. Now let’s look at saving a Swing application.

The code below demonstrates one way to put together an app that has the same two-button GUI that you saw in the previous example.

object ComponentTestApp extends SimpleSwingApplication {
  // Main components:
  val firstButton  = new Button("Press me, please")
  val secondButton = new Button("No, press ME!")
  val prompt = new Label("Press one of the buttons.")

  // Component layout::
  val allPartsTogether = new BoxPanel(Orientation.Vertical)
  allPartsTogether.contents += prompt
  allPartsTogether.contents += firstButton
  allPartsTogether.contents += secondButton
  val buttonWindow = new MainFrame
  buttonWindow.contents = allPartsTogether
  buttonWindow.title = "Swing Test App"
                              
  // A method that returns the app’s main window:
  def top = this.buttonWindow

}
The singleton ComponentTestApp represents our application; it derives from SimpleSwingApplication. A SimpleSwingApplication serves as the application’s entry point (cf. extends App; Chapter 2.7).
We create the components just like we did in the REPL earlier, except that we now use MainFrame where we had Frame before. MainFrame is a subclass of Frame that represents an application’s primary GUI frame. Closing a MainFrame terminates the entire app (whereas closing a regular Frame just hides that frame while the program keeps running), which is desirable in this standalone app, but not in the REPL.
SimpleSwingApplication is an abstract class. Our app, which inherits it, implements the superclass’s abstract top method, whose return value specifies this app’s primary (“top-level”) window. The superclass takes care of making that window visible as soon as the app launches.
For that reason, we don’t need to write buttonWindow.visible = true as we did in the REPL.

You’ll find this code in the SwingExamples project. Try running it and making changes. Try writing Horizontal instead of Vertical, for instance.

An alternative notation: nested definitions

In Chapter 2.4, you learned to add instance-specific methods to an object as you instantiate a class. We’ve made frequent use of that when instantiating o1.View.

The same trick also works for the Swing classes. For instance, the two snippets below do the same thing.

// Version 1: as above
val buttonWindow = new MainFrame
buttonWindow.contents = allPartsTogether
buttonWindow.title = "Swing Test App"
// Version 2: initializing the variables "on the fly"
val buttonWindow = new MainFrame {
  this.contents = allPartsTogether
  this.title = "Swing Test App"
}
You could well omit these explicit this keywords. They aren’t mandatory here and don’t really clarify this code either.

Here is the entire ComponentTestApp written in a more compact style:

object ComponentTestApp extends SimpleSwingApplication {
  // Main components:
  val firstButton  = new Button("Press me, please")
  val secondButton = new Button("No, press ME!")
  val prompt = new Label("Press one of the buttons.")

  // Component layout::
  val buttonWindow = new MainFrame {
    title = "Swing Test App"
    contents = new BoxPanel(Orientation.Vertical) {
      contents ++= Vector(prompt, firstButton, secondButton)
    }
  }

  // A method that returns the app’s main window:
  def top = this.buttonWindow

}
This notation highlights the nestedness of the GUI components and is arguably nicer to read.
Buffers have a ++= operator that appends multiple elements in one go.

Handling Events in Swing

Quick recap: GUI events, event handlers, and event listeners

From Chapter 3.1:

  • GUI events include mouse clicks and movements, key presses, and so forth.
  • An event handler is a subprogram that runs as a reaction to an event.
    • The onClick and onTick methods on o1.View objects are examples of event handlers.
  • An event listener is a program component that gets notified when a particular sort of event occurs and that reacts to those events in some way.
    • An o1.View object serves as an event listener for itself: when something happens in the view, the object itself is notified and calls the appropriate event-handler method.

Swing’s GUI events

Swing’s GUI components recognize when a GUI event occurs. When a Swing component observes an effect, it automatically notifies its own event listeners of the fact. For the application programmer, it remains to decide:

  1. Which object should be notified when an event occurs? In other words: which object or objects serve as event listeners for each Swing component?
    • This isn’t something we did with O1’s library, where each View was its own listener.
  2. Which program code should run when, say, a button is clicked? In other words: what should happen as a consequence of each event?
    • When using View, we’ve written this behavior in the View’s event-handler methods. We’ll do something quite similar in Swing.

Let’s create a Swing GUI that reacts to button clicks as shown here:

../_images/gui6-en.png

The program is given below, first as pseudocode and then as a concrete Scala program.

Handling events: pseudocode

Here is ComponentTestApp augmented with some event-handling pseudocode:

object EventTestApp extends SimpleSwingApplication {

  // Main components:
  val firstButton = new Button("Press me, please")
  val secondButton = new Button("No, press ME!")
  val prompt = new Label("Press one of the buttons.")

  // Component layout::
  val allPartsTogether = new BoxPanel(Orientation.Vertical)
  allPartsTogether.contents += prompt
  allPartsTogether.contents += firstButton
  allPartsTogether.contents += secondButton
  val buttonWindow = new MainFrame
  buttonWindow.contents = allPartsTogether
  buttonWindow.title = "Swing Test App"

  // Events:

  Make this EventTestApp object receive notifications when either
  firstButton or secondButton is pressed.

  Specify that whenever this object is notified of any button press, it displays
  a dialog with the message “You pressed the button that says: [text on the button]”.

  // A method that returns the app’s main window:
  def top = this.buttonWindow
}
We make the object an event listener for the two buttons. It will “keep its ears peeled” and react to events from those buttons.
We give the event listener (i.e., the app object) an event handler that it runs when it hears about an event.

Let’s refine the pseudocode part:

  Make this EventTestApp object listen to events from firstButton.
  Make this EventTestApp object listen to events from secondButton.

  Give the EventTestApp an event handler that does the following:
    1. Have a local variable clickEvent store a reference to an object that details the event.
    2. Ask that event object to provide the event’s source: the button that was pressed.
    3. Ask the button object to provide the text written on it.
    4. Call Dialog.showMessage with the message “You pressed the button that says: [text]”.

Handling events: concrete Scala code

import scala.swing._
import scala.swing.event._

object EventTestApp extends SimpleSwingApplication {
  // Otherwise identical to the previous version.

  this.listenTo(firstButton)
  this.listenTo(secondButton)
  this.reactions += {
    case clickEvent: ButtonClicked =>
      val clickedButton = clickEvent.source
      val textOnButton = clickedButton.text
      Dialog.showMessage(allPartsTogether, "You pressed the button that says: " + textOnButton, "Info")
  }
}
We need tools from scala.swing.event.
Calling listenTo on a GUI component makes the component sign up as an event listener. The parameter indicates the event source that will be listened to.
reactions is a collection of event handlers. Here, we add one handler to that collection.
We define the event-handling code within the curly brackets.
The case keyword, previously familiar from match expressions, comes in handy here.
ButtonClicked is a class from scala.swing.event. It represents button-click events. clickEvent is a programmer-chosen name for a variable that stores a reference to the event that is being handled. (From the application programmer’s point of view, clickEvent receives that value automatically; cf. match and method parameters.) The type of clickEvent is ButtonClicked.
The entire line thus means: “In case the event that occurred is a ButtonClick, store a reference to the event object in clickEvent and execute the following code:”
The event object’s source variable gives us the source of the event — in this case, that means the button that was pressed. The button object’s text variable gives us the string that’s written on the button.

You’ll find that code, too, in the SwingExamples project. Feel free to modify it and see what happens.

Event handlers in Scala

There are several ways to specify event handlers in Swing. We used the above style because it’s the simplest given what we’ve covered in O1.

Other types of events

The package scala.swing.event contains not just ButtonClicked but a variety of similar classes that correspond to a variety of GUI events. There’s MouseClicked, MouseWheelMoved, MouseMoved, KeyTyped, and so on. You’ll find examples of MouseClicked and MouseWheelMoved in the llama app, which we’ll return to presently.

Case Study: LlamaApp

You tried it already, right?

If you didn’t try LlamaApp at the beginning of the chapter, try it now. The text below may be quite hard to follow unless you know what the application looks like.

O1’s earlier chapters have repeatedly brought up the idea that an application can be divided into a user interface and an internal model. The user interface receives commands from the user and may modify the internal model as commanded.

Let’s start inspecting LlamaApp by looking at the model: the llama’s internal logic.

Class Llama

The app’s model has just a single class, Llama. We won’t pore over its implementation details; a quick run-through of its interface will do:

  • A single instance of class Llama represents a llama with a mutable state.
  • A llama’s key characteristic is its patience level, which starts at one hundred percent.
  • The parameterless methods tickle, poke, and slap each negatively impact the llama’s patience.
  • The parameterless isOutOfPatience method indicates whether the llama has completely blown its lid.
  • The stateOfMind method — likewise parameterless — returns a string that describes the llama’s commentary on the world.

The GUI components

The app’s GUI should:

  • display an image of a llama and, above it, a text that indicates the llama’s state of mind;
  • offer the user the chance to scratch (with the mouse wheel), poke (with the mouse button), or, in case of a nasty user, slap (double-click) the llama; and
  • enable the user to create a new llama by pressing Again!.

The following code lays out the GUI components. The event-handling code isn’t there yet.

object LlamaApp extends SimpleSwingApplication {
  // A couple of images:
  val alivePic = new ImageIcon("o1/llama/pics/alive.jpg")
  val deadPic  = new ImageIcon("o1/llama/pics/dead.jpg")

  // Access to the model (internal logic of the app):
  var targetLlama = new Llama

  // Components:
  val commentary = new Label
  val pictureLabel = new Label
  val startOverButton = new Button("Again!")
  this.updateLlamaView()

  // Layout:
  val verticalPanel = new BoxPanel(Orientation.Vertical)
  verticalPanel.contents += commentary
  verticalPanel.contents += pictureLabel
  verticalPanel.contents += startOverButton
  val llamaWindow = new MainFrame
  llamaWindow.title = "A Llama"
  llamaWindow.resizable = false
  llamaWindow.contents = verticalPanel

  def top = this.llamaWindow

  private def updateLlamaView() = {
    this.commentary.text = targetLlama.stateOfMind
    this.pictureLabel.icon = if (this.targetLlama.isOutOfPatience) this.deadPic else this.alivePic
  }
}
The app loads a couple of stored images from the project folder into ImageIcon objects.
The GUI needs to operate on the application’s internal model. The model consists of a single Llama object (at a time).
The GUI window contains two Labels (one with a text and another with an image) and one button.
We put lay out those elements in a panel and make set that panel as the window’s contents.
Setting resizable to false locks the window’s dimensions: the user can’t resize the window by dragging its sides with the mouse, for example.
updateLlamaView is a helper method that determines which text and image to display, given the llama’s current state, and updates the two labels accordingly.

Mouse and button events

Adding the following lines in LlamaApp makes it work as we intended.

// Events:
this.listenTo(startOverButton)
this.listenTo(pictureLabel.mouse.clicks)
this.listenTo(pictureLabel.mouse.wheel)
this.reactions += {
  case moveEvent: MouseWheelMoved =>
    targetLlama.scratch()
    updateLlamaView()

  case clickEvent: MouseClicked =>
    if (clickEvent.clicks > 1) {  // double-click (or triple, etc.)
      targetLlama.slap()
    } else {
      targetLlama.poke()
    }
    updateLlamaView()

  case clickEvent: ButtonClicked =>
    targetLlama = new Llama
    updateLlamaView()
}
The LlamaApp object listens to both mouse-button events and and mouse-wheel events.
We describe the sorts of events that the app object may hear about: mouse-wheel movements, mouse clicks, and presses of the GUI button. We give each case has its own event-handler code.
We invoke the helper method after each event so that the GUI updates to reflect the changes made to the model.
../_images/have_you_hugged.png

Sorry

Our apologies to any and all virtual llamas and llama sympathizers that may have been harmed by this chapter.

Llamas are nice and may not give you cancer.

Another Example: Random Text

Let’s look at another application. We’ll begin from the RandomTextGenerator class, which you’ll find in package o1.randomtext.

A RandomTextGenerator object generates text that more or less looks like natural human language but is nonsensical enough that it has entertainment value at best.

These objects have a randomize method, which receives a local file name or network URL as a parameter, reads existing text from that source, and returns a string of random text that resembles the input. (You’ll see examples if you fix the app in the small practice task below.)

Let’s aim for a GUI that looks like this:

../_images/gui7.png

We have two new challenges here:

  1. How to display a single-line text field and a multi-line text area? The random text should appear in the latter when the user presses the Randomize! button.
  2. How to lay out the components so that the prompt, the text field, and the button appear as a single row at the top while the text area fills the rest of the window?

Let’s consider the second problem first. The solution is ultimately the same as before: panels. Two panels were use used to construct the above window:

We again have a vertical BoxPanel as the window’s contents. It contains two things: the upper row and a large TextArea component.
The upper row is another panel; it contains a Label with text, a TextField and a Button. More specifically, this panel is of type FlowPanel. We could have used a horizontally aligned BoxPanel here instead, but...
... the result would have looked like this. A BoxPanel stretches its contents to the panel’s edges, which doesn’t look as nice in this case.

Here’s the Scala code that lays out the components:

object RandomTextApp extends SimpleSwingApplication {
  // Components:
  val prompt = new Label("Source file or URL:")
  val sourceField = new TextField("alice.txt", 50)
  val randomizeButton = new Button("Randomize!")
  val outputArea = new TextArea("Random stuff will appear here.", 30, 85)
  outputArea.editable = false
  outputArea.lineWrap = true

  // Layout:
  val topRow = new FlowPanel
  topRow.contents += prompt
  topRow.contents += sourceField
  topRow.contents += randomizeButton

  val wholeLayout = new BoxPanel(Orientation.Vertical)
  wholeLayout.contents += topRow
  wholeLayout.contents += outputArea

  val window = new MainFrame
  window.title = "Random Text Generator"
  window.resizable = false
  window.contents = wholeLayout

  def top = this.window
}
Note classes TextField and TextArea. The constructor parameters specify the widths of these components as well as the number of tow in the text area.
We mean for the computer to generate content in outputArea. We therefore set the text area’s editable property to false, which prevents the user from editing what’s written there. The lineWrap property allows the component to split long lines of text across multiple lines automatically.
FlowPanel gives us a somewhat nicer-looking GUI here than BoxPanel.
BoxPanel works fine for positioning the top-row panel above the text area.

There’s a copy of this program in o1.randomtext in the SwingExamples project.

But it looks ugly when I run it

If you try running that app in SwingExamples, you may find that the GUI doesn’t look quite like the image above. The components do appear in those positions but the components look displeasingly “retro” and unlike your operating system’s other windows and buttons. Why is that?

The underlying issue is that different environments display windows and other GUI components somewhat differently. (For instance, GUIs look similar yet obviously different on a Windows than on a Mac.) In other words, GUIs in different environments have a different look and feel.

Swing lets you to choose between several look-and-feel settings. By default, Swing uses a so-called cross-platform look and feel. If you’re unsatisfied with those visuals, you need to select a different option.

Let’s edit RandomTextApp so that it starts like this:

 import javax.swing.UIManager

object RandomTextApp extends SimpleSwingApplication {

  UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName)

  // ...
UIManager provides services related to the GUI’s surrounding graphical environment. Here, we use UIManager’s methods to...
... find out which look and feel matches the environment where the program is currently running, and...
... to indicate that we want our app to have that look and feel, too.

If you add those instructions to RandomTextApp, the program should look like you’d expect programs to look like in your environment. For instance, if you happen to run it in the Windows 7 operating system, it will look very much like the image above. That is, this version of the program doesn’t use Swing’s default visuals but adapts to whichever environment you run it in.

Assignment: Fix RandomTextApp

Fill in the missing parts of RandomTextApp so that it works as described the comments in the code. More specifically:

  1. Try the app as given. The GUI components are there, but the program doesn’t really do anything.
  2. The program should churn out nonsense text when the user presses the button. You can produce the text with a RandomTextGenerator object produce the text: link the RandomTextApp object to an instance of RandomTextGenerator.
    • Cf. how LlamaApp stored a reference to a Llama object.
    • Pass the integer 9 as a constructor parameter to the generator. The parameter’s meaning is described in the comments. Feel free to experiment with values other than nine on your own.
  3. Make the RandomTextApp object listen to button clicks.
  4. Add an event handler on RandomTextApp so that it reacts to ButtonClicked events by updating the outputArea component:
    • Generate nonsense text by calling randomize on the generator. As a parameter, pass in the text that’s in sourceField; you can access that string via the text field’s text property.
    • Put the nonsense in outputArea by setting the text area’s text property.
  5. Test the program. If everything went well, you’ll see some output when you press Randomize!
  6. Since we set the text area’s lineWrap property to true, the long output string splits across multiple lines. However, there’s no respect for word borders, which makes the text annoying to read. Fix the matter by also enabling wordWrap on the text area.

A+ presents the exercise submission form here.

User Interfaces as Tips of Icebergs

In 2002, Joel Spolsky, a well-known blogger and one of the founders of the question-and-answer site Stack Overflow, wrote about the Iceberg Secret of commercial software projects. Now that you’ve learned something about programming and user interfaces, this bit of arcana is within your reach.

You know how an iceberg is 90% underwater? Well, most software is like that too — there’s a pretty user interface that takes about 10% of the work, and then 90% of the programming work is under the covers.


That’s not the secret. The secret is that People Who Aren’t Programmers Do Not Understand This.


There are some very, very important corollaries to the Iceberg Secret.


Important Corollary One. If you show a nonprogrammer a screen which has a user interface that is 90% worse, they will think that the program is 90% worse.


Important Corollary Two. If you show a nonprogrammer a screen which has a user interface which is 100% beautiful, they will think the program is almost done.


—an extract from The Iceberg Secret, Revealed

This is one of the reasons why it’s good that ever more citizens know at least a little about programming.

Summary of Key Points

  • Programmers use various libraries for building graphical user interfaces. One such library is called Swing.
  • Many GUI libraries provide components such as windows, buttons, and text fields. Swing is object-oriented and represents these concepts as classes.
  • When you lay out components in a GUI window, it’s often helpful to group components together using auxiliary components known as panels.
  • You can make a GUI can react to user actions — GUI events — by designating an object to serve as a so-called event listener.
    • For example, when a button is clicked, the corresponding button object informs its event listeners of that event.
    • When it’s notified of an event, the listener object runs some event-handler code that defines what should happen as a response to that sort of event.
  • Links to the glossary: graphical user interface (GUI); GUI event, event listener, event handler; Swing.

I want more!

As far as GUIs are concerned, O1’s official learning objectives are modest. We haven’t gone deeper into this topic, because GUI programming tends to demand quite a bit of familiarity with the details of specific GUI libraries. To spend time building that familiarity isn’t optimal for our main goal of learning more generic skills and concepts.

You can learn more about GUIs and Swing on your own and in the course Programming Studio 2.

You may also want to explore the given GUIs in O1’s projects. Many of those GUIs feature techniques not covered in this chapter (such as creating new component types by inheriting them from Swing’s classes).

Feedback

Please note that this section must be completed individually. Even if you worked on this chapter with a pair, each of you should submit the form separately.

Credits

Thousands of students have given feedback that has contributed to this ebook’s design. Thank you!

Weeks 1 to 13 of the ebook, including the assignments and weekly bulletins, have been written in Finnish and translated into English by Juha Sorva.

Weeks 14 to 20 are by Otto Seppälä. That part of the ebook isn’t available during the fall term, but we’ll publish it when it’s time.

The appendices (glossary, Scala reference, FAQ, etc.) are by Juha Sorva unless otherwise specified on the page.

The automatic assessment of the assignments has been developed by: (in alphabetical order) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, and Aleksi Vartiainen.

The illustrations at the top of each chapter, and the similar drawings elsewhere in the ebook, are the work of Christina Lassheikki.

The animations that detail the execution Scala programs have been designed by Juha Sorva and Teemu Sirkiä. Teemu Sirkiä and Riku Autio have done the technical implementation, relying on Teemu’s Jsvee and Kelmu toolkits.

The other diagrams and interactive presentations in the ebook are by Juha Sorva.

The O1Library software has been developed by Aleksi Lukkarinen and Juha Sorva. Several of its key components are built upon Aleksi’s SMCL library.

The pedagogy behind O1Library’s tools for simple graphical programming (such as Pic) is inspired by the textbooks How to Design Programs by Flatt, Felleisen, Findler, and Krishnamurthi and Picturing Programs by Stephen Bloch.

The course platform A+ has been created by Aalto’s LeTech research group and is largely developed by students. The current lead developer is Jaakko Kantojärvi; many other students of computer science and information networks are also active on the project.

For O1’s current teaching staff, please see Chapter 1.1.

Additional credits appear at the ends of some chapters.

a drop of ink
Posting submission...

Submission received.