|
Course Notes Table of Contents Exercises | Online Training Index< /a>
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Swing Short Course, Part II from MageLang Institute |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
In Part II of this course you will:
In Part I of this course, you learned about the general component makeup, new layout managers, and new events that make up the Swing component set. In this second part, you'll take a look at some of the more advanced pieces of Swing. You'll start with an introduction to the Model-View-Controller (MVC) architecture, which is always working behind the scenes for you with all the Swing components. Then, the course describes how to use MVC to better design user interfaces. Next, the components that take advantage of MVC are more fully described:
Finally, the last part of the course describes the steps necessary to create your own pluggable look-and-feel. By the time you have completed this course, you'll be well on your way to becoming a Swing guru. Model-View-Controller ArchitectureThere are a number of ways to approach using Swing to develop GUIs. As shown in the first part of this course, you can use most of the Swing widgets in the same way AWT widgets are used. If you take this approach, the transition from AWT programming is very easy. However, Swing gives the programmer a more powerful interface to the widgets. Employing a technology called the Model-View-Controller (MVC) architecture, the Swing team has given you the ability to control how widgets look, how they respond to input, and, for some more complex widgets, how data is represented. This section provides some background information on MVC and its relationship to Swing. If you want to see how to properly create a GUI with Swing, skip ahead to the next section, "Three Ways to design a Swing GUI." Aside from a richer collection of widgets, the primary advantage of Swing over AWT is its use of MVC. MVC is a design pattern often used in building user interfaces. In an MVC UI, there are three communicating objects, the model, view, and controller. The model is the underlying logical representation, the view is the visual representation, and the controller specifies how to handle user input. When a model changes, it notifies all views that depend on it. This separation of state and presentation allows for two very powerful features.
A view uses a controller to specify its response mechanism. For instance, the controller determines what action to take when receiving keyboard input. Although the primary purpose of MVC is for building UIs, it can be used to establish an analogous notification protocol between non-visual objects. A model object can send change notifications to an arbitrary set of interested objects without knowing details about those objects. The Observer/Observable objects in java.util have served this need well, since Java 1.0. Swing Component Architecture and MVCSwing represents components by a common variation of MVC in which view and controller are combined into an object called a delegate. Delegates both represent the model, as a view does, and translate user input into the model, as a controller does. Communication between view and controller is very complex. Combining the two simplifies the job of component design. As an example, consider a checkbox widget. Regardless of visual representation, it has a state that can be either true or false. This corresponds to the checkbox's model. The way you represent these two states on the screen refers to its delegate-view. When a user clicks the mouse on the checkbox, the delegate-controller is responsible for notifying the model of the intended state change. Commonly, the delegate associated with a checkbox will use a checked box to represent the true state and an unchecked box to represent the false state and will toggle the state when a user clicks within the box. In this way, the delegate-view reflects the model and the delegate-controller translates user input into the model. Swing widgets are subclasses of JComponent, such as JButton. At any given time, a JComponent has a single model and a single delegate associated with it. Possible models for a particular JComponent are classes that implement a model interface specific to that JComponent. For a class to act as a JButton's model, it must implement the ButtonModel interface. Likewise, delegates are implementations of a delegate interface specific to the JComponent. The ButtonUI interface defines a JButton's delegate.
As stated earlier, a JComponent can have different models and delegates. You access models with the setModel and getModel methods, and delegates can be accessed with the setUI and getUI methods. Delegates and the ComponentUI InterfaceAll delegates, such as ButtonUI, extend the ComponentUI interface and are part of the com.sun.java.swing.plaf package. ComponentUI contains basic functionality to define how a delegate renders a JComponent. The primary method in this interface is one seen with applets, the paint method. Along with other methods, such as getPreferredSize and getMinimumSize, these ComponentUI methods describe the view portion of a delegate. Specific subinterfaces of ComponentUI determine the controller aspects of a delegate. To take the JButton example to completion, look more closely at the ButtonModel and ButtonUI interfaces and their default implementations. ButtonModel has methods such as isPressed and setPressed to reflect the state of a button, independent of visual representation. ButtonUI inherits most of its functionality from ComponentUI. The only additional information the ButtonUI offers is the inset size, via the getDefaultMargin method. DefaultButtonModel is JButton's default model. It is rare that its model will change; after all, a button is a button. BasicButtonUI is the default delegate for JButton on Microsoft Windows platforms. This is the Windows 95-like representation of a button and is supported only on MS Windows platforms. MotifButtonUI is the default delegate on Unix platforms. The MacButtonUI, or something similarly named, will be the default delegate for Macintosh platforms; however, no Macintosh look-and-feel is available at this time. Look and FeelCommon to both AWT and Swing is the concept of decoupling the rendering of a GUI from the Java classes that build the GUI. In AWT, each component has an associated native peer class that translates between a Java component and a native operating system widget. For instance, this means that the java.awt.Button component appears like a Windows 95 button when running under Windows 95 and like a Motif button when running under Solaris. However, the developer has no input into this process. There is only one way to render a button under Windows 95. The decoupling is simply a way to allow for platform independence. The basis of Swing components is the lightweight component architecture introduced in AWT 1.1. As such, components no longer have these peer classes nor do they use native operating system widgets. Instead, they participate in the MVC framework described above. Unlike AWT components, Swing components can appear multiple ways on the same platform. This concept describes its look and feel (L&F).
Look and Feel is "Pluggable"Because of the modular nature of MVC, you can make Swing-based GUIs, as a unit, look like Windows, the Java Look & Feel, Motif, or other user-defined views with minimal programming effort. You call this property a Pluggable Look and Feel. You can make such changes in visual representation at run-time. You accomplish this with an object called an AbstractLookAndFeel, which maintains a mapping of JComponents with ComponentUIs. Setting the AbstractLookAndFeel for an application switches the entire GUI. To make an application's interface appear like Java Look & Feel components, use the following:
try {
UIManager.setLookAndFeel (
"com.sun.java.swing.jlf.JLFLookAndFeel");
} catch (java.lang.ClassNotFoundException e) {
// Can't change factories
}
The Java Look & Feel is a Java-native look and feel provided with Swing. First seen in Swing 0.6, it is an attempt to create a common appearance across different computing environments. If you are wondering what happened to the Rose Look and Feel, read A Message to our Swing developers, from the Swing development team. Later, you'll see how to create your own look and feel. Three ways to design a Swing GUIFortunately, you can ignore much of the MVC widget internals discussed above for simple GUI design. You can approach widget placement in a GUI with Swing in exactly the same way as AWT: by instantiating widgets and adding them to containers. Additionally, two techniques use MVC to design flexible, powerful GUIs.
The first two methods of GUI design are treated in depth below. In the individual widget descriptions is a discussion of the third. Simple GUI Design with SwingGUI design with Swing can be approached in the same way as AWT, by instantiating components, adding them to a container, and setting up events among them.
// import the symbols from awt and swing packages
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
// Subclass JPanel to place widgets in a panel
class SimplePanel extends JPanel {
// Declare the two components
JTextField textField;
JButton button;
// Add a constructor for our JPanel
// This is where most of the work will be done
public SimplePanel() {
// Create a JButton
button = new JButton("Clear Text");
// Add the JButton to the JPanel
add(button);
// Create a JTextField with 10 visible columns
textField = new JTextField(10);
// Add the JTextField to the JPanel
add(textField);
// Add a listener to the JButton
// that clears the JTextField
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
textField.setText("");
}
});
}
}
// Next, create a simple framework
// for displaying our panel
// This framework may be used for displaying other
// panels with minor modifications
// Subclass JFrame so you can display a window
public class SimplePanelTest extends JFrame {
// Set up constants for width and height of frame
static final int WIDTH = 300;
static final int HEIGHT = 100;
// Add a constructor for our frame.
SimplePanelTest(String title) {
// Set the title of the frame
super(title);
// Set the background of the frame
setBackground(Color.lightGray);
// Instantiate and add the SimplePanel to the frame
SimplePanel simplePanel = new SimplePanel();
Container c = getContentPane();
c.add(simplePanel, BorderLayout.CENTER);
}
// Create main method to execute the application
public static void main(String args[]) {
// instantiate a SimplePanelTest object
// so you can display it
JFrame frame =
new SimplePanelTest("SimplePanel Example");
// Create a WindowAdapter so the application
// is exited when the window is closed.
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
// Set the size of the frame and show it
frame.setSize(WIDTH, HEIGHT);
frame.setVisible(true);
}
}
Complex GUI Design with SwingThis section deals primarily with how you handle events in a GUI. Two examples are given. In the first, you handle events in a very simple AWT 1.1 style, with adapters (similar to the previous example). Following this, there is a discussion of problems with this model related to its flexibility. Some alternatives are examined, and finally an example using MVC to design the GUI is given. It is far more complex, but also more maintainable and flexible in the long run.
/* This is a basic application that demonstrates a
* simple way to establish interaction among widgets
* in a GUI. Its event framework is fine for simple
* applications. Some shortcomings will be outlined
* below. It places a JTextField and a JTextArea on
* the screen. An ActionListener is added to the
* JTextField, so that, upon entering text into the
* JTextField, a line with the same text is appended
* to the JTextArea.
*/
// First, import the swing and awt symbols
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
/* The class is going to extend JFrame
* Most of the work of setting up the GUI
* will be done in the constructor for the frame
* Additionally, add a main method so you can
* run it as an application
*/
public class SimpleEvents extends JFrame {
// Constants to specify width and height of frame
// Used below in the main method
static final int WIDTH=350;
static final int HEIGHT=180;
// Declare a JTextField for getting user input
JTextField textField;
// Declare a JTextArea for receiving lines of
// text from textField
JTextArea textList;
// Declare a JScrollPane to hold the JTextArea
JScrollPane pane;
// Constructor for the frame class
public SimpleEvents(String lab) {
// Call JFrame's constructor
// This will set the label of the JFrame
super(lab);
// Set the layout to FlowLayout
setLayout(new FlowLayout());
// Set the background color to lightGray,
// a constant in the Color class
setBackground(Color.lightGray);
/********** Create a container for the textField *****/
// Instantiate a JPanel
JPanel textPanel = new JPanel();
// Give it a border so it stands out
// By default, panels have no border
textPanel.setBorder (
BorderFactory.createEtchedBorder());
// Set the layout of the textPanel to a BorderLayout
textPanel.setLayout(new BorderLayout());
// Create a label and add it to the panel
JLabel textTitle =
new JLabel("Type and hit
This GUI framework will work well for simple applications. If, however, the widgets in an application have a more complicated relationship, the simple adapter approach has some shortcomings. Consider another scenario in which you may want to create a third widget, an avgField that averages numbers entered into the textList and displays the result. Using the same simple adapter architecture, you could accomplish this in three ways. Each, however, has weaknesses. Look at these three ways and then a fourth approach, using MVC to solve the problem.
This association of the avgField with the textList is where the primary problem resides. If you decide that you no longer want to display the list, only the average, this causes a problem - you calculate the average from the textList. What you really need here is a List data structure for any number of widgets to observe. When data changes in that List, you want to notify these observers. You can add some methods to the List data structure to deal with notification of observers. Finally, when you enter a number in the textField, you add it to the List rather than to the textList. This is the MVC architecture. The List is acting as a model for two views, avgField and the textList. The textField is acting as a controller, passing user input into the List. You could design such an arrangement in the following manner: Both avgField and textList implement the ChangeListener interface and add themselves as listeners to the List model. By doing so, they have a stateChanged method to process any changes in the list. The textField (controller) uses an ActionListener to change data in the list. The List maintains a ChangeListener list and notifies them any time data has changed (calls their stateChanged methods).
The problem with using this architecture is that the stateChanged method doesn't contain any relevant data (it contains a ChangeEvent). The view objects have to be able to go get the model's data. Ideally, what you want to do is pass some model data to the stateChanged method. In this case, you could simply pass the complete, updated list. The reason Swing does not allow this probably has to do with Java's strong typing. Perhaps you could create another interface called ObjectStateChanged, and an object could be passed, in addition to the ChangeEvent. This is less attractive in Java since the object has to be downcast and a method call made from within the view to the model. It's best to keep the model and view "ignorant" of each other. You can solve this problem using adapters to establish the model-view relationship. The adapter acts as a ChangeListener of the model, rather than the view. The essential difference from the previous scenario is that the adapter contains type and method information about the model rather than the view. The constructor for the adapter has handles to both model and view as its parameters. The following is an implementation of such an arrangement. It is similar to the prior example with an additional view, avgView that maintains a running average of entered numbers. The other two objects acquired new names to reflect their roles in the MVC relationship. The textField object is now called controller, while the textList object is now called listView.
There are three primary classes involved:
ListViewThe ListView will act as a view to the IntVectorModel. It is a simple extension of a JTextArea. It does some initialization in the constructor and contains a changed method, which the adapter knows to call. This changed method receives data from the List (model) in the form of a Vector.
import java.util.*;
import com.sun.java.swing.*;
public class ListView extends JTextArea {
public ListView(int n) {
super("", n, 10);
setEditable(false);
}
/* This is NOT tied to a particular model's event.
* An adaptor is used to isolate the model's type
* from the view.
*
* Method called by adapter
* resets JTextArea and copies the data model
* Vector back in
*/
public void changed (Vector v) {
setText("");
Enumeration e = v.elements();
while (e.hasMoreElements()) {
Integer i = (Integer)e.nextElement();
append (i.toString() + "\n");
}
// Ensure scrollbar visible if necessary
getTopLevelAncestor().validate();
}
}
IntVectorModelThe IntVectorModel class contains the list of numbers and tracks and notifies ChangeListener objects using an EventListenerList. Take a close look at how to maintain a event listener list with EventListenerList.
import java.util.*;
import com.sun.java.swing.*;
import com.sun.java.swing.event.*;
public class IntVectorModel {
protected Vector data = new Vector();
protected EventListenerList changeListeners =
new EventListenerList();
public IntVectorModel() {
}
public void addElement(int i) {
data.addElement(new Integer(i));
fireChange();
}
public Vector getData() {
return data;
}
// Listener notification support
public void addChangeListener(ChangeListener x) {
changeListeners.add (ChangeListener.class, x);
// bring it up to date with current state
x.stateChanged(new ChangeEvent(this));
}
public void removeChangeListener(ChangeListener x) {
changeListeners.remove (ChangeListener.class, x);
}
protected void fireChange() {
// Create the event:
ChangeEvent c = new ChangeEvent(this);
// Get the listener list
Object[] listeners =
changeListeners.getListenerList();
// Process the listeners last to first
// List is in pairs, Class and instance
for (int i = listeners.length-2; i >= 0; i -= 2) {
if (listeners[i] == ChangeListener.class) {
ChangeListener cl =
(ChangeListener)listeners[i+1];
cl.stateChanged(c);
}
}
}
}
FirstMVCThe FirstMVC class is where the MVC framework is assembled. It creates a view and two models and places adapters between them.
/* Demonstrates use of MVC for GUI design: interaction
* *between* components. The model is a Vector of
* numbers. The views are a list of the numbers and
* the average of the numbers. The Views do not
* directly listen for changes from the model. Adaptors
* are used to isolate type information (promoting
* flexibility) from the model/views.
*
* Really the only Swing part is the ChangeListener
* stuff (plus a BoxLayout).
*/
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import com.sun.java.swing.event.*;
import java.util.*;
public class FirstMVC extends JFrame {
// The initial width and height of the frame
public static int WIDTH = 300;
public static int HEIGHT = 200;
// a View
ListView listView = new ListView(5);
// Another View
TextField avgView = new TextField(10);
// the Model
IntVectorModel model = new IntVectorModel();
// the Controller
TextField controller = new TextField(10);
/**Adaptor mapping IntVector to ListView;
* Hide specific types in adaptor rather
* than having view/model know about each other.
*
* A real system would allow the model to indicate
* WHAT had changed (for efficiency of execution
* and simpler design).
*/
private static class IntVectorToListviewAdaptor
implements ChangeListener {
IntVectorModel model;
ListView view;
public IntVectorToListviewAdaptor(
IntVectorModel m, ListView v) {
model = m;
view = v;
}
public void stateChanged(ChangeEvent e) {
view.changed(model.getData());
}
}
private static class IntVectorToAvgViewAdaptor
implements ChangeListener {
IntVectorModel model;
TextField view;
public IntVectorToAvgViewAdaptor(
IntVectorModel m, TextField v) {
model = m;
view = v;
}
public void stateChanged(ChangeEvent e) {
double avg = 0.0;
Vector d = model.getData();
Enumeration enum = d.elements();
while (enum.hasMoreElements()) {
Integer i = (Integer)enum.nextElement();
avg += i.intValue();
}
if (d.size()>0)
avg = avg / d.size();
view.setText(""+avg);
}
}
private static class TextFieldToIntVectorAdaptor
implements ActionListener {
IntVectorModel model;
TextField controller;
public TextFieldToIntVectorAdaptor(
TextField c, IntVectorModel m) {
model = m;
controller = c;
}
public void actionPerformed(ActionEvent e) {
String n = controller.getText();
n = n.substring(0, n.length()); // remove \n
controller.setText(""); // clear txt field
try {
model.addElement(Integer.parseInt(n));
} catch(NumberFormatException nfe) {
System.err.println("bad num: '"+n+"'");
}
}
}
public FirstMVC(String lab) {
super(lab);
setLayout(new FlowLayout());
setBackground(Color.lightGray);
// Display Controller
JPanel controlPanel = new JPanel();
controlPanel.setBorder (
BorderFactory.createEtchedBorder());
controlPanel.setLayout(new
BoxLayout(controlPanel,BoxLayout.Y_AXIS));
JLabel ctitle = new JLabel("Control");
ctitle.setHorizontalTextPosition(JLabel.CENTER);
controlPanel.add(ctitle);
controlPanel.add(Box.createVerticalStrut(10));
controlPanel.add(controller);
Container c = getContentPane();
c.setLayout (new FlowLayout ());
c.add(controlPanel);
c.add(Box.createHorizontalStrut(30));
// Display Views
JPanel viewPanel = new JPanel();
viewPanel.setBorder (
BorderFactory.createEtchedBorder());
viewPanel.setLayout(
new BoxLayout(viewPanel,BoxLayout.Y_AXIS));
JLabel title = new JLabel("Views");
viewPanel.add(title);
title.setHorizontalAlignment(JLabel.CENTER);
title.setHorizontalTextPosition(JLabel.CENTER);
viewPanel.add(Box.createVerticalStrut(10));
viewPanel.add(new JScrollPane(listView));
viewPanel.add(Box.createVerticalStrut(10));
viewPanel.add(avgView);
c.add(viewPanel);
// Hook the Controller up to the Model
TextFieldToIntVectorAdaptor CM =
new TextFieldToIntVectorAdaptor(controller, model);
controller.addActionListener(CM);
// Hook up the simple avg View up to the Model
IntVectorToAvgViewAdaptor MV1 =
new IntVectorToAvgViewAdaptor(model,avgView);
model.addChangeListener(MV1);
// Connect the View to the Model via the adapter,
// which isolates type information from each other.
IntVectorToListviewAdaptor MV2 =
new IntVectorToListviewAdaptor(model,listView);
model.addChangeListener(MV2);
}
public static void main(String args[]) {
FirstMVC frame = new FirstMVC("First MVC Example");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.setSize(WIDTH, HEIGHT);
frame.setVisible(true);
}
}
Now that you have a general feeling for the Model/View/Controller architecture, take a look at some of the JComponent objects that take advantage of them. JTreeSwing has a very flexible set of classes for creating tree controls. The JTree class is the basis to present hierarchical data. JTree objects are built from TreeNode objects, which are simple representations of a tree node. They have zero or one parent nodes and zero or more child nodes. There is a rich set of methods in the DefaultMutableTreeNode class for viewing and manipulating nodes in a tree. This class implements the MutableTreeNode interface, which extends the TreeNode interface. This is a partial listing of some of the more useful methods:
Optionally, a MutableTreeNode can hold a handle to an arbitrary object with the userObject property and the setUserObject access method. The getUserObject method is a part of DefaultMutableTreeNode. This way, a JTree can hold any objects. The toString method of TreeNode returns the result of the userObject.toString method or null if userObject is null. Three interfaces work in conjunction to allow developers to customize the model and view of a tree, TreeModel, TreeSelectionModel, and TreeCellRenderer. TreeModelThe TreeModel interface describes a JTree's underlying data model. JTree
contains a property, Model, with access methods getModel and setModel, that
determine which TreeModel a JTree uses. The TreeModel interface specifies how
a tree is mapped over a data structure with the following methods:
These are similar to their analogous JTree methods, except that they deal with Objects rather than TreeNodes. Three additional methods, addTreeModelListener, removeTreeModelListener, and valueForPathChanged deal with adding, removing, and notifying listeners respectively. These listeners are notified of changes in the TreeModel by receiving TreeModelEvent messages. An object that defines these methods can operate as a model for a JTree. DefaultTreeModel is a simple implementation of TreeModel that explicitly uses TreeNode and MutableTreeNode objects. TreeSelectionModelTreeSelectionModel is an interface that specifies how the user may select a path of arbitrary objects. JTree uses it to set up selection rules. DefaultTreeSelectionModel is a simple implementation of TreeSelectionModel. It allows for the usual selection paradigm that one is accustomed to (i.e. selecting files in a directory). TreeCellRendererThe TreeCellRenderer interface is used by JTree to specify a component that will visually represent nodes in the tree. For instance, the default cell renderer is BasicTreeCellRenderer, which uses folders as root and internal nodes and filled circles as leaf nodes (see example below). Custom appearance can be defined by creating classes that implement this interface that contains only one methods:
Using the default model and view of a JTree, you can easily create a file directory style tree. You do have to make sure you place the tree in a JScrollPane in case there is insufficient display space.
public class JTreePanel extends JPanel {
JTreePanel() {
// Set the layout to hold only one component
setLayout(new BorderLayout());
// Create root node of tree
DefaultMutableTreeNode root =
new DefaultMutableTreeNode("Contacts");
// Create 1st level child
DefaultMutableTreeNode level1 =
new DefaultMutableTreeNode("Business");
// Add 1st level child under root node
root.add(level1);
// Create and add 2nd level child
DefaultMutableTreeNode level2 =
new DefaultMutableTreeNode("JavaSoft");
level1.add(level2);
// Create and add some 3rd level leaf nodes
level2.add(new DefaultMutableTreeNode(
"James Gosling"));
level2.add(new DefaultMutableTreeNode(
"Frank Yellin"));
level2.add(new DefaultMutableTreeNode(
"Tim Lindholm"));
// Create and add another 2nd level child
level2 = new DefaultMutableTreeNode(
"Disney");
level1.add(level2);
// Create and add some 3rd level leaf nodes
level2.add(new DefaultMutableTreeNode(
"Goofy"));
level2.add(new DefaultMutableTreeNode(
"Mickey Mouse"));
level2.add(new DefaultMutableTreeNode(
"Donald Duck"));
// Create and add another 1st level child
level1 = new DefaultMutableTreeNode(
"Personal");
root.add(level1);
// Create and add some 2nd level leaf nodes
level1.add(new DefaultMutableTreeNode(
"Justin"));
level1.add(new DefaultMutableTreeNode(
"Andrew"));
level1.add(new DefaultMutableTreeNode(
"Denice"));
// Create a tree from the root
JTree tree = new JTree(root);
// Place tree in JScrollPane
JScrollPane pane = new JScrollPane();
pane.getViewport().add (tree);
add(pane, BorderLayout.CENTER);
}
}
ExercisesJList/JComboBox RevisitedWith the introduction of MVC, you can do more with a JList or JComboBox, as well as just about every other JComponent. By associating a data model to the component, and a way to render a view of the model, you can create more complex display components. To demonstrate MVC within these two components, you can use the same data model for both a JList and JComboBox because the ComboBoxModel extends the ListModel. Also, they both have the same renderer interface: ListCellRenderer.
public class MVCListPanel extends JPanel {
MVCListPanel() {
ImageListModel ilm = new ImageListModel();
JComboBox combo = new JComboBox(ilm);
combo.setRenderer(new ImageCellRenderer());
combo.setSelectedIndex(0);
add(combo);
JList list = new JList (ilm);
list.setCellRenderer(new ImageCellRenderer());
list.setSelectedIndex(0);
list.setVisibleRowCount(4);
JScrollPane pane = new JScrollPane ();
pane.setBorder (
BorderFactory.createLoweredBevelBorder());
pane.getViewport().add (list);
add(pane);
}
class ImageListModel extends DefaultListModel
implements ComboBoxModel, Serializable {
Object currentValue;
Icon icon[];
Hashtable cache[];
final int SIZE = 6;
Color color[] = {Color.red, Color.orange,
Color.yellow, Color.green, Color.blue,
Color.magenta};
String label [] = {"Cranberry", "Orange",
"Banana", "Kiwi", "Blueberry", "Pomegranate"};
public ImageListModel () {
cache = new Hashtable[getSize()];
icon = new AnOvalIcon[SIZE];
for (int i=0;i<SIZE;i++)
icon[i] = new AnOvalIcon (color[i]);
}
// ListModel methods
public int getSize() {
// Use constant for performance reasons
return SIZE;
}
public Object getElementAt(int index) {
// Cache data items to avoid recreating
if (cache[index] != null) {
return cache[index];
} else {
Hashtable result = new Hashtable();
result.put ("label", label[index]);
result.put ("icon", icon[index ]);
cache[index] = result;
return result;
}
}
// ComboBoxModel methods
// DefaultComboBoxModel does this for you
public Object getSelectedItem() {
return currentValue;
}
public void setSelectedItem(Object anObject) {
currentValue = anObject;
fireContentsChanged(this, -1, -1);
}
}
class ImageCellRenderer extends JLabel
implements ListCellRenderer {
private boolean focused = false;
public ImageCellRenderer () {
setOpaque (true);
}
public Component getListCellRendererComponent(
JList list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
Hashtable h = (Hashtable) value;
if (value == null) {
setText("");
setIcon(null);
} else {
setText((String)h.get ("label"));
setIcon((Icon)h.get ("icon"));
}
setBackground (isSelected ?
SystemColor.textHighlight :
SystemColor.text);
setForeground (isSelected ?
SystemColor.textHighlightText :
SystemColor.textText);
return this;
}
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.width += 15; // widen
return dim;
}
}
class AnOvalIcon implements Icon {
Color color;
public AnOvalIcon (Color c) {
color = c;
}
public void paintIcon (Component c, Graphics g,
int x, int y) {
g.setColor(color);
g.fillOval (x, y,
getIconWidth(), getIconHeight());
}
public int getIconWidth() {
return 20;
}
public int getIconHeight() {
return 10;
}
}
}
As just demonstrated, the JList in Swing can be very different from its AWT equivalent when it participates in an MVC relationship. Three interfaces work in conjunction to allow developers to customize the model and view of a list box, ListModel, ListSelectionModel, and ListCellRenderer. ListModelThe ListModel interface is a general model for a list of objects. It specifies the data to be represented by the JList. It is a fairly simple interface, containing four methods: Object getElementAt(int index); int getSize(); void addListDataListener(ListDataListener listener); void removeListDataListener(ListDataListener listener); The getElementAt method returns a single data element representing a position in the ListBox at index. The getSize method returns the number of elements in the model, and therefore in the JList. The final two methods maintain a list of view objects that are interested in changes to the model. The DefaultListModel class manages the listener list for you, through its superclass AbstractListModel. Then, when a ListDataEvent happens, you notify the listeners with one of the following methods:
ListSelectionModelListSelectionModel is an interface that specifies how the user may select a set of arbitrary ranges of objects. JList uses it to set up selection rules. DefaultListSelectionModel is a simple implementation of ListSelectionModel. The selection model describes whether or not a JList is in single or multi-selection mode. ListCellRendererAnother interface, ListCellRenderer, specifies how to visually represent items in a list. Similar to other widgets with "renderer" interfaces, it contains one methods:
There is a default ListCellRenderer available, so you can just add a String[] (or Vector) to the JList and it will use the default renderer. Swing Text FrameworkWarning: The following describes the 0.6.1 version of Swing. There will be changes to the com.sun.java.swing.text package in 0.7. These changes should not affect the general functionality of the text components. However, until 0.7 is released, the following is subject to change. The way Swing treats text-based widgets is another example of the Complex Widget Architecture application of MVC above. Textual content (model) and its representation (view) are decoupled. In order for an object to play the role of a model, it must implement the Document interface or, more likely, extend one of its "canned" implementations that ship with Swing. Observers of a document extend the abstract View class or one of its subclasses. A View usually takes the form of a rendered component on the screen. The illustration below shows how documents and views interact. UI events are usually sent to the document. If a change occurs that a view is interested in, the system generates a DocumentEvent and passes it to it. This allows for the synchronization of the document and view. Events, such as selection of text with the mouse, send the document event directly to the view for processing.
Document InterfaceThe Document interface describes an implementation independent structure for holding text. It supports markup of styles, notification of changes and tracking of changes to allow for "undo" functionality. Text is marked up with structures called elements, a concept taken from SGML. Elements describe the state of a document with an arbitrary set of attributes. You build a view from a type of element structure. Documents also contain methods to describe the number of lines and paragraphs of text. In most cases, a single document structure can describe a text component's model. The Document interface however, does allow for multiple structural representations of the text data. To do this, you create a document that has multiple root elements, one for each structural representation. The Swing team gives the following examples of where such an arrangement might be useful:
Document ImplementedSeveral convenience implementations of Document ship with Swing. The simplest of them, AbstractDocument, is intended primarily as a superclass to extend and form models that are more complete. The primary contribution of AbstractDocument is its locking mechanism. It implements the readers/writers model to allow either one writer or multiple readers access to the content. Writers must wait for notification of all observers of a previous change before they can begin another mutation cycle. AbstractDocument is the abstract superclass of two full-featured document models, PlainDocument and DefaultStyledDocument. The purpose of PlainDocument is for fairly short and simple text. It manages textual content as a string, and does not support history or undo operations. DefaultStyledDocument allows for storage of formatted text similar to Rich Text Format (RTF). It relies on structure elements to mark up the text into styles. These style elements are associated with paragraph marker elements. Basic Swing Text WidgetsThis MVC based text framework is very powerful but also very complex. After all, most developers simply want to throw some text components into a container and accept their pre-defined behavior. Fortunately, Swing can hide the MVC mechanics from you. If you simply instantiate a text widget and add it to a container a default document is generated, initialized, and maintained for you. You can think of text widgets as JTextComponents that have a ready-to-use delegate (View) and a ready-to-use model (Document) operating behind the scenes. Using JTextPane and DefaultStyledDocumentThe JTextPane component provides support for multi-attributed text. No longer are you restricted to the single color or font limitations of TextArea. With the help of a DefaultStyledDocument for its model, and a good understanding of the com.sun.java.swing.text package, you are well on your way to creating the next word processor or language-sensitive editor. Creating a JTextPane for complex text display requires two simple steps:
Once you have a your document, you can make various AttributeSet objects to describe the content style: SimpleAttributeSet defaultStyle = new SimpleAttributeSet(); SimpleAttributeSet italicStyle = new SimpleAttributeSet(); StyleConstants.setItalic(attr, true); SimpleAttributeSet bigStyle = new SimpleAttributeSet(); StyleConstants.setFontSize(attr, 36); and fill up the JTextPane, associating an attribute set with each paragraph in the StyledDocument: doc.insertString (doc.getLength(), "Hello World\n", bigStyle); doc.insertString (doc.getLength(), "What's up Doc?\n", italicStyle); doc.insertString (doc.getLength(), "Boring...\n", defaultStyle);
Then, at the appropriate time, you can use the various methods of StyleConstants to change the style of the selected contents within the JTextPane or use StyledDocument methods like setCharacterAttributes, setParagraphAttributes, or just plain setLogicalStyle, to change the document characteristics. Just create a SimpleAttributeSet, and configure any attribute you would like:
For certain functionality, you just need to wrap one of the style changing methods into an ActionListener and make it available on a menu or a button. To make things easier, most of these adapters are already created for you. Just make an instance and add one as an ActionListener to a menu or button. With either of these methods, you won't have to worry about finding the selected text to figure out what to change. There is even a third way, so you don't want to have to worry about specific class names, just functionality. The StyledEditorKit class provides a minimal set of text actions as a series of inner classes that are covered next.
In addition to the StyledEditorKit inner classes, there are a whole slew of other ones. Most of these are useful when you want to provide alternative input mechanisms for traversal within the JTextPane. However, none of them are public.
TextActionsSince none of the inner classes outside of StyledEditorKit are public, you need to access this functionality in another way. To perform these operations you can ask a JTextComponent how to do some functionality and it passes back something that implements ActionListener. You usually just get back an inner class, but you never need to know that. What you get back is an Action that happens to implement the ActionListener interface. You then just add this listener to your MenuItem, Button, or other class. To demonstrate, the following program shows how to support Cut and Paste operations for a JTextArea.
public class CutPaste extends JPanel {
CutPaste() {
setLayout (new BorderLayout (5, 5));
JTextArea jt = new JTextArea();
JScrollPane pane = new JScrollPane();
pane.setBorder (
BorderFactory.createLoweredBevelBorder());
pane.getViewport().add(jt);
add(pane, BorderLayout.CENTER);
// get the command table
Hashtable commands = new Hashtable();
Action[] actions = jt.getActions();
for (int i = 0; i < actions.length; i++) {
Action a = actions[i];
commands.put(a.getText(Action.NAME), a);
}
JButton cut = new JButton("Cut");
cut.setBackground (SystemColor.control);
Action cutAction =
(Action)commands.get (DefaultEditorKit.cutAction);
if (cutAction == null) {
cut.setEnabled (false);
} else {
cut.setActionCommand (DefaultEditorKit.cutAction);
cut.addActionListener (cutAction);
}
JButton paste = new JButton("Paste");
paste.setBackground (SystemColor.control);
Action pasteAction =
(Action)commands.get (DefaultEditorKit.pasteAction);
if (pasteAction == null) {
paste.setEnabled (false);
} else {
paste.setActionCommand (DefaultEditorKit.pasteAction);
paste.addActionListener (pasteAction);
}
JPanel p = new JPanel();
p.add(cut);
p.add(paste);
add (p, BorderLayout.SOUTH);
}
}
TextAction TableThere are String constants available in the various classes to help in working with most of the text actions. The following table shows the commands with built-in support in the text components, along with where the constants are located:
The way Action objects work within Swing is you can programmatically enable or disable components when you enable or disable an Action. This involves associating the component to the Action as a JavaBeans PropertyChangeListener and reacting accordingly to the change. For instance, if boldAction is the Action to make selected text bold, you can toggle the functionality with the following code:
JButton b1 = new JButton ("Toggle Bold");
b1.addActionListener (new ActionListener() {
public void actionPerformed (ActionEvent e) {
boldAction.setEnabled(!boldAction.isEnabled());
}
});
...
class MyButton extends JButton implements PropertyChangeListener {
public void propertyChange (PropertyChangeEvent e) {
if (e.getPropertyName().equals ("enabled")) {
if (!(e.getNewValue() == e.getOldValue()))
setEnabled (((Boolean)e.getNewValue()).booleanValue());
}
}
};
MyButton b2 = new MyButton ();
b2.setText ("Do Bold1");
b2.addActionListener (boldAction);
boldAction.addPropertyChangeListener (b2);
MyButton b3 = new MyButton ();
b3.setText ("Do Bold2");
b3.addActionListener (boldAction);
boldAction.addPropertyChangeListener (b3);
Then, when the Toggle Bold button is selected, the components associated with boldAction are jointly enabled or disabled. View and ViewFactory InterfacesThe View interface specifies a representation based on part or all of a document. It contains a paint method for rendering and layout. ViewFactory describes how Views are mapped to structure elements. It contains a method called create that returns a View, given an Element. Often a ViewFactory is passed to a method with an accompanying Shape object, allowing for dynamic generation of views.
ExerciseSwing Table FrameworkWarning: The following describes the 0.6.1 version of Swing. There will be MAJOR changes to the com.sun.java.swing.table package in 0.7. These changes will affect the general functionality of the table components, rendering the following description useless. Read the README-JTable.txt file that comes with the Swing release for a preview of things to come. Basically, JTable is NOT a spreadsheet and shouldn't be used like one. Table support for Swing is found in the com.sun.java.swing.table package. The package consists of a series of classes and interfaces to handle the creation and display of columner data. The way Swing supports tables is another example of MVC. The table data model is found in the TableModel interface, while the View/Controller part is found in the JTable class.
TableModelThe TableModel interface specifies how to describe the data in the table cells, and requires the maintaining of a TableModelListener list. Because whenever you create the table's data model you need to maintain this list, there is a AbstractTableModel class which maintains the list for you. The methods of TableModel consist of the following:
As mentioned above, AbstractTableModel maintains the listener list for you. The list is available from the protected listenerList variable. Then, when you subclass the adapter, you only need to implement getColumnCount, getRowCount, getValueAt, and setValueAt. If you are creating a read-only table, the setValueAt method is stubbed out for you in AbstractTableModel, so you don't have to implement that either. However, when you do implement setValueAt, you have to remember to notify the listener about the change. Besides changing the data, use the fireTableChanged method of AbstractTableModel to notify those interested in cell-level changes:
class SomeDataModel extends AbstractTableModel {
...
public void setValueAt (Object aValue,
int row, int column) {
...
foo[row][column] = aValue;
fireTableChanged (new TableModelEvent (this, row));
...
}
JTableOnce you have the table's data model in something that implements the TableModel interface, you can actually create and display the table. There are actually two steps involved here:
To make life even easier, you don't even have to worry about TableModel. If you have your data in a set of Vector objects or Object arrays, you can pass that off to the JTable constructor. More About JTableThere are many more capabilities available for JTable, like support for editing, colorizing, simultaneous row-column selection, and getting information about the selected entries. Be sure to examine the API documentation for information on these additional capabilities. The source for the example used above is shown below:
public class TablePanel extends JPanel {
TablePanel() {
setLayout (new BorderLayout());
// Create data model
EmployeeDataModel employeeModel =
new EmployeeDataModel();
// Create/setup table
JTable table = new JTable (employeeModel);
// Resize columns
table.sizeColumnsToFit (false);
// Place table in JScrollPane
JScrollPane scrollPane =
JTable.createScrollPaneForTable(table);
// Add to Screen
add(scrollPane, BorderLayout.CENTER);
}
}
class EmployeeDataModel extends AbstractTableModel {
// By extending AbstractTableModel, instead of
// implementing TableModel yourself,
// AbstractTableModel takes care of
// TableModelListener list management
String columns[] = {"Employee ID", "First Name",
"Last Name", "Department"};
String rows[][] = {
{"0181", "Bill", "Cat", "Political Candidate"},
{"0915", "Opus", "Penguin", "Lost and Found"},
{"1912", "Milo", "Bloom", "Reporter"},
{"3182", "Steve", "Dallas", "Legal"},
{"4104", "Hodge", "Podge", "Style"},
{"5476", "Billy", "Boinger", "Entertainment"},
{"6289", "Oliver", "Jones", "Science"},
{"7268", "Cutter", "John", "Travel"},
{"8133", "W. A.", "Thornhump", "C.E.O"},
{"9923", "Berke", "Breathed", "Editor"}
};
private int numColumns = columns.length;
private int numRows = rows.length;
public int getColumnCount() {
return numColumns;
}
public int getRowCount() {
return numRows;
}
public Object getValueAt (int row, int column) {
return rows[row][column];
}
public void setValueAt (Object aValue,
int row, int column) {
String cellValue;
if (aValue instanceof String)
cellValue = (String)aValue;
else
cellValue = aValue.toString();
rows[row][column] = cellValue;
fireTableChanged (new TableModelEvent (this, row));
}
public String getColumnName (int columnIndex) {
return columns[columnIndex];
}
}
Instead of using the EmployeeDataModel, the JTable could have been created with: String columnNames[] = ... String data[][] = ... JTable table = new JTable (data, columnNames);
ExerciseCreating a New LookCreating a different look and feel is not for everyone. Most people will just work with what Swing already provides. For those interface designers who want total control of what the interface looks like, Swing provides that control. The AbstractLookAndFeel is where you start. However, what you will probably do is just extend an existing look-and-feel (BasicLookAndFeel) class to support some of your own components. That way, you won't have to provide everything at once. To demonstrate, you can create your own look and feel, MyLookAndFeel, that changes the look of the JButton to have multi-color triangular right and left borders.
The LookAndFeel ClassIf you extend BasicLookAndFeel, the only thing you need to do with your LookAndFeel class is map your UI classes to the appropriate UI names. These names can be found in the com.sun.java.swing.basic package, or you can ask a particular component with the getUIClassID method.
package my;
import java.awt.Color;
import com.sun.java.swing.UIDefaults;
import com.sun.java.swing.basic.BasicLookAndFeel;
public class MyLookAndFeel extends BasicLookAndFeel {
public String getName() {
return "My Look and Feel";
}
public String getDescription() {
return "The My Look and Feel";
}
public boolean isNativeLookAndFeel() {
return false;
}
public boolean isSupportedLookAndFeel() {
return true;
}
protected void initClassDefaults (UIDefaults table) {
super.initClassDefaults(table);
table.put ("ButtonUI", "my.MyButtonUI");
}
}
The ButtonUI ClassAfter defining what UI classes you are going to create, you need to create them. For the most part, the UI classes are identical, with the exception of the paint method. The rest is just general house-keeping routines that have minor changes between components. For the particular ButtonUI that you are creating, you will need two support classes: the MyButtonBorder class draws the borders around the button and the MyButtonListener class will describe how your UI component responds to internal mouse and keyboard input. Here is the code for the ButtonUI. The installUI, uninstallUI, and createUI methods should be the only thing new to you. The installation routines basically just install your listener and border that you want for your interface. Obviously, paint draws the object. However, the majority of the work is left for the border class.
package my;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
import com.sun.java.swing.plaf.ComponentUI;
import com.sun.java.swing.basic.*;
import java.io.Serializable;
import com.sun.java.swing.plaf.ButtonUI;
public class MyButtonUI extends ButtonUI
implements Serializable {
protected final static Insets defaultMargin =
new Insets (2, 5, 2, 5);
protected final static Font defaultFont =
new Font ("Serif", Font.BOLD, 10);
private final static Border defaultBorder =
new CompoundBorder(
MyButtonBorder.getButtonBorder(),
BasicMarginBorder.getMarginBorder());
protected static final int textIconGap = 3;
protected MyButtonListener listener;
protected static ButtonUI buttonUI;
public static ComponentUI createUI (JComponent c) {
if (buttonUI == null) {
buttonUI = new MyButtonUI();
}
return buttonUI;
}
public void installUI (JComponent c) {
listener = new MyButtonListener(c);
c.addMouseListener(listener);
c.addMouseMotionListener(listener);
c.setFont (defaultFont);
if (c.getBorder() == null)
c.setBorder (defaultBorder);
}
public void uninstallUI (JComponent c) {
c.removeMouseListener (listener);
c.removeMouseMotionListener (listener);
if (c.getBorder() == defaultBorder)
c.setBorder(null);
}
public void paint (Graphics g, JComponent c) {
AbstractButton ab = (AbstractButton) c;
ButtonModel bm = ab.getModel();
Dimension size = ab.getSize();
g.setFont (c.getFont());
FontMetrics fm = g.getFontMetrics();
int shiftOffset = 0;
Rectangle viewRect = new Rectangle(size);
Rectangle iconRect = new Rectangle();
Rectangle textRect = new Rectangle();
String text = SwingUtilities.layoutCompoundLabel (
fm, ab.getText(), ab.getIcon(),
ab.getVerticalAlignment(),
ab.getHorizontalAlignment(),
ab.getVerticalTextPosition(),
ab.getHorizontalTextPosition(),
viewRect, iconRect, textRect, textIconGap);
if (bm.isArmed() && bm.isPressed()) {
shiftOffset = 1;
}
// Paint background
if (c.isOpaque()) {
g.setColor (ab.getBackground());
g.fillRect (0, 0, size.width, size.height);
}
// Draw Icon
if (ab.getIcon() != null) {
Icon icon = null;
if (!bm.isEnabled()) {
icon = ab.getDisabledIcon();
} else if (bm.isPressed() && bm.isArmed()) {
icon = ab.getPressedIcon();
} else if (bm.isRollover()) {
icon = ab.getRolloverIcon();
}
if (icon == null) {
icon = ab.getIcon();
}
if (bm.isPressed() && bm.isArmed()) {
icon.paintIcon (c, g, iconRect.x + shiftOffset,
iconRect.y + shiftOffset);
} else {
icon.paintIcon (c, g, iconRect.x, iconRect.y);
}
}
// Draw Text
if ((text != null) && (text.length() != 0)) {
if (bm.isEnabled()) {
g.setColor (ab.getForeground());
BasicGraphicsUtils.drawString (g, text,
bm.getKeyAccelerator(),
textRect.x + shiftOffset,
textRect.y + fm.getAscent() + shiftOffset);
} else {
g.setColor (ab.getBackground().brighter());
BasicGraphicsUtils.drawString (g, text,
bm.getKeyAccelerator(),
textRect.x, textRect.y + fm.getAscent());
g.setColor (ab.getBackground().darker());
BasicGraphicsUtils.drawString (g, text,
bm.getKeyAccelerator(),
textRect.x - 1, textRect.y + fm.getAscent() - 1);
}
}
}
public Dimension getMinimumSize (JComponent c) {
return getPreferredSize (c);
}
public Dimension getMaximumSize (JComponent c) {
return getPreferredSize (c);
}
public Dimension getPreferredSize (JComponent c) {
if ((c.getComponentCount() > 0) ||
!(c instanceof AbstractButton)) {
return null;
}
AbstractButton ab = (AbstractButton) c;
Icon icon = ab.getIcon();
String text = ab.getText();
Font font = ab.getFont();
FontMetrics fm = ab.getToolkit().getFontMetrics (font);
Rectangle viewRect = new Rectangle (
Short.MAX_VALUE, Short.MAX_VALUE);
Rectangle iconRect = new Rectangle();
Rectangle textRect = new Rectangle();
SwingUtilities.layoutCompoundLabel (
fm, text, icon,
ab.getVerticalAlignment(),
ab.getHorizontalAlignment(),
ab.getVerticalTextPosition(),
ab.getHorizontalTextPosition(),
viewRect, iconRect, textRect, textIconGap);
// find union of icon and text rectangles
Rectangle rect = iconRect.union (textRect);
Insets insets = getInsets (c);
rect.width += insets.left + insets.right;
rect.height += insets.top + insets.bottom;
return rect.getSize();
}
public Insets getDefaultMargin (AbstractButton b) {
return defaultMargin;
}
public Insets getInsets (JComponent c) {
Border border = c.getBorder();
Insets insets = ((border != null) ?
border.getBorderInsets (c) :
new Insets (0,0,0,0));
return insets;
}
}
The Mouse ListenerUnless you are doing special events for keyboard events and/or drawing focus, your listener code can practically be shared across multiple objects. Basically, you have to say things like: the mouse press event causes the button to be armed and pressed. This in turn triggers the appropriate behavior of the JButton object listeners. You can have any activity you want trigger these behaviors. For instance, if you want a ButtonUI that requires your user to fly a plane between two buildings before the button is activated, you can do that. In this case here, the MyButtonUI supports the general mouse events for selection.
package my;
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import java.io.Serializable;
public class MyButtonListener implements MouseListener,
MouseMotionListener, Serializable {
AbstractButton ab;
public MyButtonListener (JComponent c) {
ab = (AbstractButton) c;
}
public void mouseMoved (MouseEvent e) {
}
public void mouseClicked (MouseEvent e) {
}
public void mouseDragged (MouseEvent e) {
ButtonModel bm = ab.getModel();
if (bm.isPressed()) {
Graphics g = ab.getGraphics();
if (g != null) {
Rectangle r = g.getClipBounds();
if (r.contains (e.getPoint()))
bm.setArmed(true);
else
bm.setArmed(false);
}
}
}
public void mousePressed (MouseEvent e) {
ButtonModel bm = ab.getModel();
bm.setArmed(true);
bm.setPressed(true);
}
public void mouseReleased (MouseEvent e) {
ButtonModel bm = ab.getModel();
bm.setPressed (false);
}
public void mouseEntered (MouseEvent e) {
if (ab.getRolloverIcon() != null)
ab.getModel().setRollover (true);
}
public void mouseExited (MouseEvent e) {
if (ab.getRolloverIcon() != null)
ab.getModel().setRollover (false);
}
}
The Button BorderThe MyButtonBorder class is the worker class of this new user interface object. Here, the border needs to draw triangles in the insets of the component. Depending upon the state of the component, determines the color actually drawn.
package my;
import java.awt.*;
import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
import com.sun.java.swing.basic.BasicGraphicsUtils;
public class MyButtonBorder extends AbstractBorder {
private static Border buttonBorder =
new MyButtonBorder();
public static Border getButtonBorder() {
return buttonBorder;
}
public void paintBorder (Component c, Graphics g,
int x, int y, int width, int height) {
boolean pressed = false;
boolean focused = false;
if (c instanceof AbstractButton) {
AbstractButton b = (AbstractButton)c;
ButtonModel bm = b.getModel();
pressed = bm.isPressed();
focused = (pressed && bm.isArmed()) ||
(b.isFocusPainted() && b.hasFocus());
}
Insets in = getBorderInsets(c);
Polygon p1 = new Polygon ();
p1.addPoint (x+in.left, y);
p1.addPoint (x, y+(height/2));
p1.addPoint (x+in.left, y+height);
Polygon p2 = new Polygon ();
p2.addPoint (x+width-in.right, y);
p2.addPoint (x+width, y+(height/2));
p2.addPoint (x+width-in.right, y+height);
if (pressed) {
g.setColor (c.getForeground());
} else if (focused) {
g.setColor (SystemColor.green);
} else {
g.setColor (SystemColor.red);
}
g.fillPolygon (p1);
g.fillPolygon (p2);
}
public Insets getBorderInsets (Component c) {
return new Insets (5, 10, 5, 10);
}
}
The ExampleAnd, that is all there is to it. Borrowing heavily from the Simple example that comes with Swing, hear is an example that demonstrates all the hard work. You can extend it as you add more user interfaces to MyLookAndFeel. Notice that the interaction with the JButton doesn't change.
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
public class MyExample extends JPanel {
public MyExample() {
// Create the buttons.
JButton button = new JButton ("Hello, world");
ActionListener myListener = new ActionListener() {
public void actionPerformed (ActionEvent e) {
String lnfName = null;
if (e.getActionCommand().equals ("My")) {
lnfName = "my.MyLookAndFeel";
} else {
lnfName =
"com.sun.java.swing.basic.BasicLookAndFeel";
}
try {
UIManager.setLookAndFeel(lnfName);
SwingUtilities.updateComponentTreeUI (
MyExample.this);
MyExample.this.validate();
} catch (Exception ex) {
System.err.println (
"Could not swap LookAndFeel: " + lnfName);
}
}
};
ButtonGroup group = new ButtonGroup();
JRadioButton basicButton =
new JRadioButton ("Basic");
basicButton.setSelected(true);
basicButton.addActionListener (myListener);
group.add (basicButton);
JRadioButton myButton =
new JRadioButton ("My");
myButton.addActionListener (myListener);
group.add (myButton);
add (button);
add (basicButton);
add (myButton);
}
public static void main (String args[]) {
JFrame f = new JFrame ("LnF Example");
JPanel j = new MyExample();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.getContentPane().add (j, BorderLayout.CENTER);
f.setSize (300, 100);
f.show();
}
}
ConclusionIn conclusion, remember that Swing is just a small part of the Java Foundation Classes (JFC). Other new parts of JFC include the Accessibility API, Java2D API, Drag and Drop (Glasgow/Beans), and various application services (Undo, Custom Cursors, Keyboard Navigation).
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Copyright © 1997 MageLang Institute. All Rights Reserved May-97 Copyright © 1996, 19 97 Sun Microsystems Inc. All Rights Reserved |