[Note: this is an extended version of the introductory materials I asked you to read before class. I've highlighted the tasks I'm asking you to in this face, but there's a little more reading to do around them, as well.]
In this exercise, we're going to focus on the Model-View-Controller design pattern we talked about briefly in class. There are two reasons for this: (1) MVC is a very commonly used design pattern, across many languages and application contexts; (2) MVC is (I think) a helpful way of thinking concretely about design, about how you can create effective relationships among objects in your applications.
This exercise has two parts. First, we'll look at another version of the "SwingLine" code that we looked at during the application activities, and we'll see how the code can be "refactored" to make it even more MVC-ish. Then, we'll look at how Swing itself uses MVC to design many of the Swing components. This will involve a fair amount of API-reading, and not so much code-writing... but that's actually how things go a lot of the time: you have to read a fair amount before you figure out how to write a couple lines of code.
I talked for a few minutes in class about Model-View-Controller. That was no accident. While there are many design patterns floating around Java, MVC shows up a lot in Swing. And beginning to think in terms of design patterns is an important step in your development as a software developer. (For example, there are a ton of books written about design patterns, many focusing on a particular programming language.)
As we saw in the big-small-circle application, MVC is often used to structure the user interface of an application; it's a useful way make sure we're keeping the three kinds of behavior (keeping track of data [the model], displaying the data [the view], and letting the user change the data [the controller]) fairly separate from each other (and therefore making sure that we're not programming like that jerk Larry).
SwingLine
MVCHere is the source code for a "fully" Model-View-Controller version of the SwingLine application we also looked at last week. Take a look. The big difference between last week's version and this week's version is that I put the "application data" (in this case, the length and thickness of the line) into its own class, which I called LineModel
. Most of the behavior in this class is essentially getter and setter behavior. But there is some additional code relating to the fact that this is part of an MVC design: when the data in the model changes, other objects (especially the View) may want to know about the change so they can update in response. So, I wrote LineModel
to extend Observable
. The Observable
class offers the basic behavior of allowing other objects to "register" as "listeners" (similar to buttons and ActionListeners
s). So a class that extends Observable
gets all that behavior automatically. You'll also see that the main application class implements the Observer
interface, which includes a single method, update()
. My definition of the update()
method tells the various elements of my View to update themselves with the new data from the Model. Read the documentation for Observable and Observer—you'll see that these have been deprecated because they're not as flexible and powerful and modern applications require, but they're still very good for exploring the foundational concepts.
Here are two small tasks for you to do with this code:
LineModel
offered methods to increment and decrement the thickness. Add those methods, and update the rest of the code to use those methods. Hint: these new methods should do very little work—they should re-use code you've already written, i.e. they should call existing methods whenever possible.lengthDisplay TextField
. This button should set the length to 100 and the thickness to 1. Pay attention to what code you need to write and what code you don't need to write. (And as above, re-use as much code as you can.) While you're at it, maybe you should declare 100 and 1 as constants in the LineModel
class.Let's switch gears a bit and look at Swing components themselves. For example, what do we know about the JTextField
class. Which of the following are responsibilities/behaviors of this class?
While you're thinking about that, look at the JList
discussion on p. 417 of the book. Which of these are
responsibilities of this class?
JButton
doesn't have that much going
on—but that's one of the exceptions.) By the way, for those
two multiple choice questions, all of the listed items are
correct. In preparation for this week's exercise read
this short excerpt that discusses MVC generally as well as its application within Swing. Also, read the introductory text in the documentation for
JList, paying particular attention to the discussions about ListModel
, ListSelectionModel
, and how cells are "painted" (i.e. the View).
Currency
classIn Project 2, we'll use these ideas to assemble
a slightly complex Swing application. But for this
exercise, we'll focus on one non-Swing class (Currency
)
and the JList
component. First, Currency
. As
the docs say, this is a class whose instances represent currencies, using the ISO 4217 standard. This is an international standard; the Wikipedia page for the standard includes a table of active codes—basically, this is the standard that assigns three-letter codes, like USD
for the United States dollar. If you ever exchange money, you see these codes in use. And software applications that deal with currency exchange also use these codes (as well as some related standards).
JList
But for now, let's work on making a list of Currency
s
available to our users. Take a look
at this code. It declares an array
of Currency
objects, then creates
a JList<Currency>
that holds those objects (note
the JList
constructor that lets me create the list
from an array of objects). Maybe you want to check the wikipedia page about currency codes to find out what those codes represent. Notice, too: this is not
a JList
of String
s, and I haven't given
any instructions about how the JList
is supposed to
display each of the currencies in the list. What will happen? Make a
hypothesis, then run the
code and find out. Was your hypothesis correct? Why does the program
have this behavior?
Maybe you don't think this behavior is the best; I certainly
don't. How can we address this? Well, remember that one of the
responsibilities of JList
is to display its
contents. If we
consult the
documentation, we learn (several paragraphs down) that
"Painting of cells in a JList
is handled by a delegate
called a cell renderer." That is, every JList
object
has an instance variable that refers to an object implementing
the ListCellRenderer
interface. And, importantly, we
can tell the JList
to use a different cell renderer by passing a reference (to the
cell renderer we want) to the setCellRenderer()
method
of JList
.
So, if we want to customize how our list items are displayed, we
just need to write our own class implementing that interface. Note
that the interface only includes one
method, getListCellRendererComponent()
, but it looks
like it has to do a lot of work. We don't want to do that much
work!—we just want to tweak the "default behavior" a little
bit. This sounds a little bit like the situation with
the paintComponent()
method. The "default painting behavior" of
a JPanel
is not helpful to us (it basically does
nothing), but the paintComponent()
method does a lot of
work that we actually can't do ourselves. The solution there was to
start with the JPanel
class and then override the
default behavior of paintComponent()
with our own
behavior. This raises a question: is there a class out there that
does the "default cell renderer" behavior? If you look closely at
the JList
documentation, you might find something that
says "See also: .... DefaultListCellRenderer
." Hmmm
interesting! Look at that documentation: not only does it
implement the ListCellRenderer
interface, but it
also extends JLabel
! That is, by default,
a JList
is displayed with a bunch
of JLabel
s!
But the real point is that, just as we can change how a JPanel
is painted by overriding the paintComponent()
method, we can change how JList
cells are rendered just by subclassing the default renderer. So, this is your first
task (after all this reading): modify
the CurrencyList
code to display
each Currency
in a more user-friendly way.
A few notes about this, though:
getListCellRendererComponent()
method to use the
"default" behavior from the superclass first,
then make your modifications.JLabel
, so you have all those JLabel
methods
available to you through inheritance.Currency
documentation to see if there's anything that can help you get a
better-looking description.
JList
customizationOK, let's do something crazy... let's let the user add
currencies to this list. According to the Currency
API,
we can create Currency
objects using the getInstance()
method, which takes a three-letter currency code as a parameter. So, let's add to the user interface— put in
a JTextField
where a user can enter a currency code,
and a button labeled "Add" that the user can click to add the
language to the list of currencies. (While you're at it, you might
want to put a few more JLabel
s in so the user can tell what
she's looking at.) To make this work, the main thing we need to do
is implement the button listener. There's two issues here...
First: how do we add something to a JList
?
There are a lot of methods in the API, but none seem to involve
changing the contents of the list. But wait!
A JList
is an MVC-style component (which element of MVC
did we fiddle with above?)... and if we're talking about
the contents of the list, it sounds like we're talking
about the model. And actually, yeah, this is the first
thing discussed in the API documentation. The third sentence says
something about "the JList
constructor that
automatically builds a read-only ListModel
instance
for you." Uh-oh. Read-only? That sounds like I can look
at the contents, but I can't change them. I don't
want a read-only ListModel
. But hang on—what is
this ListModel
? Hmmm, let's keep
reading. Doo-de-doo... ah! "Simple
dynamic-content JList
applications can use
the DefaultListModel
class to maintain list elements."
That sounds promising... so pursue that idea in order to complete
your button's ActionListener
.
The second issue has to do with input validation: what if the user enters an invalid currency code? The getInstance()
method says it will "throw an IllegalArgumentException
" if it doesn't get a supported currency code, but we haven't studied exceptions yet. We can also check ourselves: the static
method Currency.getAvailableCurrencies()
will return a collection (a Set
, specifically) of supported currencies, and we can use Set
methods to see if the user's input is valid. (And let's be subtle: if it's
a legal code, then when the button is clicked, a new currency is added
to the JList
and the text field is cleared. If the
currency code is not legal, then nothing happens to
the JList
and nothing happens to
the JTextField
either.