The latest instance of the course can be found at: O1: 2024
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).
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:
- a few words about GUI libraries in general;
- using individual GUI elements via the REPL; adjusting component properties;
- laying out multiple components;
- giving an app a GUI;
- reacting to GUI events: mouse clicks, etc.;
- a closer look at
LlamaApp
; - 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.
Swing O1Library:n GUI-työkaluissa
O1Library’s GUI toolkit is actually built using Swing. Moreover, it’s possible to mix and match classes from O1Library and Swing; Chapter 7.5’s CitySim GUI, for instance, does just that.
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.
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
. Frame
s 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.
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:
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 Frame
s don’t have a makePic
method like o1.View
s
(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:
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:
scala.swing.Label
.scala.swing.Button
for buttons.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:
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
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:
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 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
}
ComponentTestApp
represents our application;
it derives from SimpleSwingApplication
. A SimpleSwingApplication
serves as the application’s entry point (cf. extends App
;
Chapter 2.7).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.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"
}
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
}
++=
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
andonTick
methods ono1.View
objects are examples of event handlers.
- The
- 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.
- An
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:
- 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.
- This isn’t something we did with O1’s
library, where each
- 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 theView
’s event-handler methods. We’ll do something quite similar in Swing.
- When using
Let’s create a Swing GUI that reacts to button clicks as shown here:
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 }
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")
}
}
scala.swing.event
.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.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
.ButtonClick
, store a reference to the event object in
clickEvent
and execute the following code:”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
Sound familiar?
ButtonClicked
and Swing’s other event classes are exactly the
ones you used in Chapter 3.1 if you did the optional assignments
at the bottom of the chapter.
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
, andslap
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
}
}
ImageIcon
objects.Llama
object (at a time).Label
s (one with a text and
another with an image) and one button.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()
}
LlamaApp
object listens to both mouse-button events
and and mouse-wheel events.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:
We have two new challenges here:
- 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.
- 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:
BoxPanel
as the window’s contents.
It contains two things: the upper row and a large TextArea
component.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...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
}
TextField
and TextArea
. The constructor
parameters specify the widths of these components as well as
the number of tow in the text area.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...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:
- Try the app as given. The GUI components are there, but the program doesn’t really do anything.
- 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 theRandomTextApp
object to an instance ofRandomTextGenerator
.- Cf. how
LlamaApp
stored a reference to aLlama
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.
- Cf. how
- Make the
RandomTextApp
object listen to button clicks. - Add an event handler on
RandomTextApp
so that it reacts toButtonClicked
events by updating theoutputArea
component:- Generate nonsense text by calling
randomize
on the generator. As a parameter, pass in the text that’s insourceField
; you can access that string via the text field’stext
property. - Put the nonsense in
outputArea
by setting the text area’stext
property.
- Generate nonsense text by calling
- Test the program. If everything went well, you’ll see some output when you press Randomize!
- Since we set the text area’s
lineWrap
property totrue
, 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 enablingwordWrap
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.
Frame
object.