Java offers many, many ways to make graphical user interfaces. Most ways, however, lead to later problems. Here are some of the choices that were made, and why I made them.
All "normal" programs are GUI based, so this question seems peculiar. It's only addressed here because of the peculiar situation that some Java textbooks are written without any GUI coverage, and others cover it as a marginal, or optional topic. I'll try to address why this is that case, and why GUIs should be taught from relatively early.
The biggest problem is that many textbooks are simply adaptations of an author's earlier C++, C, or Pascal books. These authors are no longer active programmers, so have never actually written GUI programs beyond simple textbook samples. Finally, publishers have no motivation to innovate, and don't want to make changes to the tried-and-true (tired-and-true) formulas of the past. Here are some of the objections I've heard from instructors who didn't want to teach GUIs.
Far from being seen as hard, students are very eager to learn it because it allows them to build real programs - programs that they can show their family and friends.
But there is a bit of truth to this in the sense that, as programs become larger, the percentage of the code that is related to the GUI becomes smaller and the portion of the code related to the logic or model becomes larger. In a large project, only a few programmers may work on the GUI.
Also, a number of programs that have no GUI interface. For example, Java is very popular on servers using the a web interface, which doesn't use the standard Java GUI interface.
First, of course, is some of the text I/O information. This is less of a problem since the introduction of the Java 5 Scanner class, but coverage of even this should be delayed until file I/O is covered, which itself should be delayed until the JFileChooser Dialog is covered.
Inheritance. A major issue in teaching Object-Oriented Programming is teaching inheritance. There are nice intuitive analogies to inheritance with examples like the relationship between animal phyla, but these all suffer from the difficulty of turning these into code. The standard bank account or employee examples are OK, but suffer from not furnishing any really convincing programming opportunities. Teaching inheritance is hard because there aren't simple problems that can use it.
GUI as inheritance example. In normal programming creating inheritance hierarchies is not all that common. It's very important to understand it, but it isn't used that often. One of the few places where it is used a lot, and to great advantage, is in GUIs. It is the GUI usage that can be used as a real, not contrived, example of inheritance.
GUI programming can (and should) be used as a real model for inheritance.
Interfaces. Far more important than
inheritance are interfaces. Again, it is hard
to create small, real, examples of interface usage
that can be related to real student programs at
a fairly early stage.
GUI programming, primarily in the form of ActionListener,
is a good model for interfaces at an early stage.
Later, in a data structures course, more excellent examples
of interfaces come up.
And more. In addition to providing real demonstrations of inheritance and interfaces, GUIs demonstrate other OOP concepts such as inner classes, method overriding, constructors, and constructor chaining. It also provides a good motivation for threads.
To a large extent teaching GUI programming doesn't have to mean leaving something out, but rather redirecting coverage of OOP topics to practical GUI examples.
The conversion of textbooks to early GUI will probably have to wait for a new generation of authors who grew up with GUIs.
The early hype about applets has passed, and they haven't provided as many solutions as applications. Distributed software is probably better done better in most cases with Java WebStart. Almost all examples in these notes are applications, but it's easy to modify most of them into applets.
No subclass. Building a GUI entirely in a static main program may be possible, but it eventually leads you into quicksand, involving greater and greater struggles to make it work. If you start to write static variables, other than constants, you're doing something wrong.
JFrame. The most common way to define a window is to create a class which extends JFrame, and build the GUI in the constructor of that class. Most of these example programs use this style because it's reasonable, and is the most popular, so it is useful to become familiar with it to read other programs comfortably.
JPanel. There are other reasonable alternatives, such as building the GUI from a subclass of JPanel which is used for the content pane. Some of these programs written this way, which is especially suitable for programs that are both applications and applets.
main?The main method
can be put in any class, and you will find a lot of programs that
just stick it in the JFrame subclass.
This style doesn't create many problems, so it isn't much
of a sin if you do it this way. However, I've chosen
another common way -- put it in a separate class.
main
creates an object of its own class. Moving main
to its own class is simple, and makes the code easier to
understand.
main makes it easier to change between
an application and an applet without
confusion. It's possible to put main
in a JApplet subclass, but again it leads
to confusion with no special advantage.
main
may have more initialization duties, so putting it in
its own class gets you used to a style that
will be useful when your programs expand.
Every class has certain responsibilities. Sometimes assigning these responsibilities is easy, and sometimes boundary cases are unclear. You'll see variation in the placement of JFrame initialization statements.
Because you might want to have a splash screen, it seems that when the window is made visible is the job of the master coordinating method (main). Similarly, exactly what action is taken when the window is closed also belongs with the controlling method. However, you will sometimes see these actions in the constructor. Here is how the Tiny Window Subclass might be written will all initialization in the JFrame subclass. These few statements are so easy to change that it doesn't seem like much to worry about.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// File : gui-tutorial/tw2/TinyWinSubclass.java
// Purpose: I don't like it, but here's an alternative
// placement of JFrame initialization statements.
// Author : Fred Swartz
// Date : 2005-06-17
import javax.swing.*;
class TinyWinSubclass {
public static void main(String[] args) {
new TinyWinGUI(); //Note 1
}
}
class TinyWinGUI extends JFrame {
public TinyWinGUI() {
setTitle("Tiny Window using JFrame Subclass");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
}
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE), a window listener, or nothing?
Window listeners. When a window of a program is closed, you typically want to check to see if there is any unsaved data, and if so, ask the user if they want to save it. To do this you need to add a window listener.
setDefaultCloseOperation. Although adding a window listener is the right way to handle the close box click in a real program, simple programs often don't have any state that needs to be saved, so I've used the common shortcut of terminating the program when the window is closed. The default action of closing a window just makes it invisible, but the program continues to run, making it hard to stop the program.
JFrames (and your subclasses of them) have a content pane,
which is where you add components, which are positioned by the
layout manager when you call pack().
There are no special problems with this,
so if you just interested interested in getting your program
running, stop reading now. What follows is information
that will help you understand variations you'll see in other programs.
You will see all of these approaches used. They are all reasonable. Choose the one which makes you most comfortable.
getContentPane for every reference.this.getContentPane().add(...); this.getContentPane().add(...); . . .
Container content = this.getContentPane(); content.add(...); content.add(...); . . .
JPanel content = new JPanel(); content.add(...); content.add(...); . . . this.setContentPane(content);
When you create a JFrame, it comes with a content pane which has already been created. [TODO: Discuss small advantages of using a JPanel as opposed to Container].
[TODO: Discuss this "new" Java 5 feature mistake.]
Buttons (and other components), activate an action listener
when clicked (or whatever), which calls the actionPerformed
method is an object that implements the ActionListener
interface. There are several choices, and some of them are bad.
JFrame class implements ActionListener (bad). You may see this style used elsewhere in small example programs. If there is only one component which generates action events (true only for very small programs), there's no problem, but as soon as multiple components generate them, you are forced to decode the events yourself, which becomes increasingly complex as the numbers and types of components in your GUI increase. This was the only option available in Java 1 (1994). You would like to have a separate listener for each logical action, which is best accomplished with separate listeners.
Named inner class listeners (good). Most examples in this text use named inner classes. Each class is defined
Anonymous inner class listeners (leads to confusing structure).
AbstractAction (good).
There are a number of "GUI editors" that allow you to build your GUI with a drag-and-drop style of programming. You may have to modify this code, and certainly will have to figure out how to implement the listeners and reference the components. Although they may save you some aggravation in the layout, you will still need to understand how things work. Therefore it is imperative that you understand how GUIs work. And building them by hand is the way to learn them. After you know what's going on, you might want to switch to a GUI editor - some programmers like to use them, and some think they don't really help much.
There are a couple promising approaches: the new Matisse GUI builder supposedly to appear in the next version of NetBeans (future software always sounds great - you can see a demo at www.netbeans.org/files/documents/4/475/matisse.html), and the Abeille GUI editor for FormLayout (I'll try this out soon).
Native components. One question that constantly comes up is whether GUI components should be implemented by using the local operating system components. If so, on Windows the Java buttons would be mapped into native Windows buttons, on the Macintosh the native Macintosh buttons, etc. The alternative is to define these components in a portable way so that the same code is used on all systems. {TODO: Add a link to one of the better summaries of the issues in this debate.]
This tutorial uses the Java Swing GUI classes which are portable implementations of components, which support the Java Look and Feel interface which allows them to be rendered very close to native components, although not pixel-perfect. This is the mainstream choice.
Java's first attempt at a GUI interface was called the Abstract Windowing Toolkit (AWT).
which was based on the underlying system components. For a number of reasons
this was largely replaced by the Swing version, although the old AWT classes
are still supported. Many AWT classes, not including components, are still used,
which is why you typically have to
import from both java.awt and javax.swing.
You will find programs that still use AWT, but they are either very old,
or they are written to be the absolutely most portable.
The Micro Edition of Java (for PDAs, phones, ...) use something like AWT.
SWT. Early versions of Swing had performance problems, which now seem to be largely solved. However, IBM decided to build a set of GUI classes which were based on native components, called SWT. Portability is not too big a problem since SWT versions have been written for the major operating systems. Some programmers prefer this to Swing, but the improvements in Swing have nullified most advantages.
XUL. AWT, Swing, and SWT are very similar approaches. A radically different way of thinking about user interfaces is to represent them in XML. The XUL group has championed this approach, and Microsoft has instantiated this as XAML. There is a steady stream of Java toolkits using this way of designing interfaces. Last year I used a couple of them, including SwixML, one of the more mature implementations, but you have to be willing to work a little harder. The documentation is generally weak or non-existent. They may not be ready for prime time quite yet, but they are very promising.
I don't want to exaggerate the difficulties of working with Java GUIs because they are much easier to build than Microsoft Windows interfaces in C++. But they are harder than those in Visual Basic, for example.
Flexibility in the big Java program -- there are a huge number of GUI classes, a huge number of methods that can be used in these classes, and many ways to structure the elements that you do build.