![]() ![]() ![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
While most of the new events are related to the more complicated
Model/View/Controller architecture, to be described
later, the JMenu file = new JMenu ("File"); file.addMenuListener (new MenuListener() { public void menuSelected (MenuEvent e) { System.out.println ("Selected"); } public void menuDeselected (MenuEvent e) { System.out.println ("Deselected"); } public void menuCanceled (MenuEvent e) { System.out.println ("Canceled"); } }); Swing Event Listeners
Each listener interface is paired with one event type and (usually) contains
a method for each type of event the event class embodies. For instance, the
Unlike the AWT event listener classes, there are few adapter classes at
this time. If you have an interest in only one of the event sub-types,
you have to implement all the methods of the interface. The two adapters
that do exist are
Also, like the EventListenerList listenerList = new EventListenerList(); AEvent aEvent = null; public void addAListener(AListener l) { listenerList.add (AListener.class, l); } public void removeFooListener (AListenerl) { listenerList.remove (AListener.class, l); } protected void fireAEvent() { // Must return non-null array Object listeners[] = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i] == AListener.class) { // Lazily create the event: if (aEvent == null) aEvent = new AEvent(this); ((AListener)listeners[i+1]).someAHappened(aEvent); } } } Swing Event Sources
The Swing events originate from its various components. The table below
represents the list of event sources. Do keep in mind the class hierarchy.
For instance, when
This table means that for each class in the left column, there is an
Just as a refresher, the following table shows the inherited listeners from
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 right away how to properly create a GUI with Swing, skip ahead to the next section, "Designing a Swing GUI." Aside from a richer collection of widgets, the primary advantage of the Swing Component set over AWT is its use of MVC. MVC is a design pattern often used in building user interfaces (UI). 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
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 uses a checked box to represent the true state and an unchecked box to represent the false state. It toggles 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
![]()
As stated earlier, a Delegates and the ComponentUI Interface
All delegates, such as
To take the
Look and Feel
Common 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, heavyweight, peer class that translates between a
JavaTM component and a native operating system widget. For instance,
this means that the The basis of Swing components is the lightweight component architecture introduced in AWT 1.1. As such, components no longer have these heavyweight 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 the Swing component's look and feel (L&F).
![]() Look and Feel is "Pluggable"
Because of the modular nature of MVC, you can make Swing-based GUIs look
like Windows, the Macintosh, Motif, or other user-defined views with minimal
programming effort. Sun calls 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 a try { UIManager.setLookAndFeel ( "javax.swing.plaf.metal.MetalLookAndFeel"); } catch (java.lang.ClassNotFoundException e) { // Can't change look and feel } The Metal look and feel is a cross-platform Java-native look and feel provided with Swing. The Metal look and feel is an attempt to create a common appearance across different computing environments. The Metal look and feel is actually the default look for Swing applications, so you would more likely change the appearance to one of the others. For instance, the following would change the look and feel to the user's native platform look and feel: try { UIManager.setLookAndFeel ( UIManager.getCrossPlatformLookAndFeelClassName()); } catch (java.lang.ClassNotFoundException e) { // Can't change look and feel } Designing 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 in the following paragraphs. The third is discussed in the individual widget descriptions. 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 javax.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); // 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. These are described using two examples. In the first, you handle events in a very simple AWT 1.1 style, with adapters (similar to the previous example). This example is followed by discussion of problems with this model and its flexibility. Some alternatives are examined. The second example uses MVC to design the GUI. It is far more complex, but also more maintainable and flexible in the long run. GUI Event Handling - Simple AWT Example
![]() /* 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 AWT and Swing symbols import java.awt.*; import java.awt.event.*; import javax.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); /******* 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 <ENTER>"); textPanel.add(textTitle, BorderLayout.NORTH); // Instantiate JTextField and add it to the // textPanel textField = new JTextField(); textPanel.add(textField, BorderLayout.SOUTH); // Add a strut to the textPanel as a bottom margin textPanel.add(Box.createVerticalStrut(6)); /******* Create a container for the textArea ********/ // Instantiate a JPanel JPanel listPanel = new JPanel(); // Give it a border so it stands out listPanel.setBorder ( BorderFactory.createEtchedBorder()); // Set the layout of the textPanel to a BoxLayout // BoxLayouts are discussed below (ignore for now) listPanel.setLayout( new BoxLayout(listPanel,BoxLayout.Y_AXIS)); // Create a label and add it to the panel JLabel title = new JLabel("Text List"); listPanel.add(title); // Add a strut to the BoxLayout listPanel.add(Box.createVerticalStrut(10)); // Instantiate the JTextArea with no initial text, // 6 rows, 10 columns, and vertical scrollbars textList=new JTextArea("", 6, 10); // Make it read-only textList.setEditable(false); // Add the textList to the listPanel pane = new JScrollPane (textList); listPanel.add(pane); // Add a strut to the listPanel as a bottom margin listPanel.add(Box.createVerticalStrut(6)); /***** Add a listener to the textField ***********/ /* The listener will respond to user input by * copying the textField's text to the textList. * The ENTER key causes an ActionEvent to be * generated. Notice how the two widgets are * becoming intertwined. * Changes to one will likely affect the other */ textField.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Append the textField's text to textList textList.append(textField.getText()); textList.append("\n"); // Reset the textField textField.setText(""); } }); // Add two panels to frame, separated by a strut Container c = getContentPane(); c.setLayout (new FlowLayout()); c.add(textPanel); c.add(Box.createHorizontalStrut(30)); c.add(listPanel); } /* Create a main method for invoking as application **/ public static void main(String args[]) { // Instantiate instance of the SimpleEvents class // This is where constructor is executed, and the // GUI built - JFrame title is passed as parameter SimpleEvents frame = new SimpleEvents("Simple Events Example"); // This is a standard adapter that should be // in most applications. It closes the window frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // Set the size of the JFrame and show it frame.setSize(WIDTH, HEIGHT); frame.setVisible(true); } } Problems and AlternativeThis 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
This association of the GUI Event Handling - Using MVCThe solution to this problem calls for the MVC architecture. TheList 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
The
The
![]()
The problem with using this architecture is that the
Ideally, what you want to do is pass some model data to the
You can solve this problem using adapters to establish the model-view
relationship. The adapter acts as a
The following is an implementation of such an arrangement. It is similar to the
prior example with an additional view,
![]() There are three primary classes involved:
controller and avgView objects are
JTextField objects.)
![]() ListView
The import java.util.*; import javax.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"); } } } IntVectorModel
The import java.util.*; import javax.swing.*; import javax.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); } } } } FirstMVC
The /* 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 javax.swing.*; import javax.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(); 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); // 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 JTree
Swing has a very flexible set of classes for creating tree controls. The
Three interfaces work in conjunction to allow developers to customize the model
and view of a tree, TreeModel
The getChild (Object parent, int index) getChildCount (Object parent) getIndexOfChild (Object parent, Object child) getRoot() isLeaf (Object node)
Three additional methods,
An object that defines these methods can operate as a model for
a TreeNode
Optionally, a TreeSelectionModel
TreeCellRenderer
The
![]()
Using the default model and view of a 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("Java Software"); 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(jtree); add(pane, BorderLayout.CENTER); } }
Certain look-and-feel classes provide the ability to set client
properties to alter appearance. For example, the tree for the Metal
look and feel has a lineStyle property that allows you to show
lines connecting cells in a
![]() tree.putClientProperty ("JTree.lineStyle", "Angled");
The MagercisesJList and JComboBox Revisited
With the introduction of MVC, you can do more with a
![]() public class MVCListPanel extends JPanel { MVCListPanel() { final 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); ListSelectionModel lsm = list.getSelectionModel(); lsm.setSelectionMode (ListSelectionModel.SINGLE_SELECTION); JScrollPane pane = new JScrollPane (list); add(pane); JButton jb = new JButton ("Add to Model"); final Color color[] = {Color.red, Color.orange, Color.yellow, Color.green, Color.blue, Color.magenta}; jb.addActionListener (new ActionListener() { Hashtable elem; public void actionPerformed(ActionEvent e) { int i = (int)(Math.random()*color.length); elem = new Hashtable(); elem.put ("label", "Label-" + i); elem.put ("icon", new AnOvalIcon(color[i])); ilm.addElement (elem); } }); add (jb); } static class ImageListModel extends DefaultComboBoxModel { private static final Color color[] = {Color.red, Color.orange, Color.yellow, Color.green, Color.blue, Color.magenta}; private static final String label [] = {"Cranberry", "Orange", "Banana", "Kiwi", "Blueberry", "Pomegranate"}; public ImageListModel () { Icon icon; for (int i=0, n=label.length; i<n ;i++) { icon = new AnOvalIcon (color[i]); Hashtable result = new Hashtable(); result.put ("label", label[i]); result.put ("icon", icon); addElement(result); } } } static class ImageCellRenderer implements ListCellRenderer { private boolean focused = false; private JLabel renderer; public ImageCellRenderer () { renderer = new JLabel(); renderer.setOpaque (true); } public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (value == null) { renderer.setText(""); renderer.setIcon(null); } else { Hashtable h = (Hashtable) value; renderer.setText((String)h.get ("label")); renderer.setIcon((Icon)h.get ("icon")); } renderer.setBackground (isSelected ? SystemColor.textHighlight : SystemColor.text); renderer.setForeground (isSelected ? SystemColor.textHighlightText : SystemColor.textText); return renderer; } } static 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
Three interfaces work in conjunction to allow developers to customize the
model and view of a list box, ListModel
The Object getElementAt(int index); int getSize(); void addListDataListener(ListDataListener listener); void removeListDataListener( ListDataListener listener);
The
The
ListSelectionModel
To change modes: model.setSelectionMode (int mode) Available modes are:
ListCellRenderer
Another interface,
There is a default Swing Table Framework
Table support for Swing is found in the
![]() TableModel
The
As mentioned above, class SomeDataModel extends AbstractTableModel { ... public void setValueAt (Object aValue, int row, int column) { ... foo[row][column] = aValue; fireTableCellUpdated (row, column)); ... } JTable
Once you have the table's data model in something that implements the
JTable Example
To make life even easier, you don't even have to worry about
String columnNames[] = ... String data[][] = ... JTable table = new JTable (data, columnNames); JScrollPane scrollPane = new JScrollPane (table); More About JTable
There are many more capabilities available for The source for the JTable example is: public class TablePanel extends JPanel { TablePanel() { setLayout (new BorderLayout()); // Create data model EmployeeDataModel employeeModel = new EmployeeDataModel(); // Create/setup table JTable table = new JTable (employeeModel); // Place table in JScrollPane JScrollPane scrollPane = new JScrollPane (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 String getColumnName (int columnIndex) { return columns[columnIndex]; } 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; fireTableCellUpdated (row, column); } public boolean isCellEditable(int row, int column) { // first column is read-only return (column != 0); } }
Instead of using the String columnNames[] = ... String data[][] = ... JTable table = new JTable (data, columnNames); MagerciseSwing Text Framework
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.
For an object to play the role of a model, it must implement the
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
![]() Document Interface
The
In most cases, a single document structure can describe a text component's
model. The
Document Implemented
Several convenience implementations of
Basic Swing Text Widgets
This 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
JTextPane and DefaultStyledDocument
The
Creating a
Once you have created your document, you can make various
static final String NORMAL = "Normal"; static final String ITALIC = "Italic"; static final String BIG = "Big"; // Setup initial style set Hashtable paraStyles; paraStyles = new Hashtable(); SimpleAttributeSet attr = new SimpleAttributeSet(); paraStyles.put(NORMAL, attr); attr = new SimpleAttributeSet(); StyleConstants.setItalic(attr, true); paraStyles.put(ITALIC, attr); attr = new SimpleAttributeSet(); StyleConstants.setFontSize(attr, 36); paraStyles.put(BIG, attr);
You can also fill up the // Clear out current document pane.setStyledDocument( doc = new DefaultStyledDocument()); // Get the NORMAL Style AttributeSet defaultStyle = (AttributeSet) paraStyles.get(NORMAL); // Get the ITALIC Style AttributeSet italicStyle = (AttributeSet) paraStyles.get(ITALIC); // Get the BIG Style AttributeSet bigStyle = (AttributeSet) paraStyles.get(BIG); // Insert into pane 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);
![]() Changing StylesThen, at the appropriate time, you can use either the various methods shown in the following table ofStyleConstants to change
the style of the selected contents within the JTextPane
or StyledDocument methods such
as setCharacterAttributes() ,
setParagraphAttributes() , or just plain
setLogicalStyle() to change the document
characteristics. Just create a SimpleAttributeSet ,
and configure any attribute you would like.
In some cases, you may want to wrap one of the style changing methods into an
To make things easier, most of these adapters have already been created for you. With either of these methods, you won't have to worry about finding the selected text to figure out what to change.
A third method of changing styles allows you to not worry about specific class
names, just functionality. The
In addition to
TextActions
Since several of the inner classes outside of
The following program demonstrates how to support cut and paste
operations for a
![]() public class CutPaste extends JPanel { CutPaste() { setLayout (new BorderLayout (5, 5)); JTextArea jt = new JTextArea(); JScrollPane pane = new JScrollPane(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.getValue(Action.NAME), a); } JToolBar bar = new JToolBar(); AbstractAction cutAction = (AbstractAction) commands.get (DefaultEditorKit.cutAction); JButton button = bar.add(cutAction); button.setText("Cut"); AbstractAction pasteAction = (AbstractAction) commands.get (DefaultEditorKit.pasteAction); button = bar.add(pasteAction); button.setText("Paste"); add (bar, BorderLayout.SOUTH); } } TextAction Table
Various classes provide
The classes implementing the
When you add a component to a
To make this work with your own components, you have to manually associate a
To demonstrate with public class ActionSet extends JFrame { public ActionSet(String lab) { super (lab); JTextPane tp = new JTextPane (); Hashtable commands = new Hashtable(); Action actions[] = tp.getActions(); for (int i = 0; i < actions.length; i++) { Action a = actions[i]; commands.put(a.getValue(Action.NAME), a); } final Action boldAction = (Action)commands.get ("font-bold"); // Setup MenuBar JMenu menu = new JMenu("Edit"); JMenuItem menuitem = menu.add (boldAction); menuitem.setText("Bold"); JMenuBar menubar = new JMenuBar(); menubar.add(menu); // Setup ToolBar JToolBar toolbar = new JToolBar(); JButton button = toolbar.add(boldAction); button.setText("Bold"); // Setup toggle button JButton toggleButton = new JButton("Toggle Bold Action"); ActionListener toggleListener = new ActionListener() { public void actionPerformed (ActionEvent e) { boolean enabled = boldAction.isEnabled(); boldAction.setEnabled(!enabled); } }; toggleButton.addActionListener (toggleListener); // Setup screen Container contentPane = getContentPane(); JScrollPane scrollPane = new JScrollPane(tp); contentPane.add(menubar, BorderLayout.NORTH); contentPane.add(scrollPane, BorderLayout.CENTER); contentPane.add(toolbar, BorderLayout.EAST); contentPane.add(toggleButton, BorderLayout.SOUTH); } } View and ViewFactory Interfaces
The MagerciseCreating a New Look
Creating a different look and feel is not for everyone. Most people will just
work with the look-and-feels provided. For simple user interface changes, you
can install a new resource with the Icon icon = new ImageIcon(...); IconUIResource iconResource = new IconUIResource(icon); UIDefaults defaults = UIManager.getDefaults(); defaults.put( "Slider.horizontalThumbIcon", iconResource);
To discover the names (and current values) of the available properties,
just ask. These properties are different from the ones set with
import java.util.*; import javax.swing.*; public class ListProps { public static void main (String args[]) { Hashtable defaultProps = UIManager.getDefaults(); Enumeration enum = defaultProps.keys(); while (enum.hasMoreElements()) { Object key = enum.nextElement(); System.out.println( key + "\t" + defaultProps.get(key)); } System.exit(0); } }
For those interface designers who want total control of a user's experience,
support is available. The
![]() The LookAndFeel Class
If you extend
You also have to provide your look-and-feel with an ID, name, and description
via package my; import javax.swing.UIDefaults; import javax.swing.plaf.metal.MetalLookAndFeel; public class MyLookAndFeel extends MetalLookAndFeel { public String getID() { return "My"; } 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 Class
After defining which UI classes you are going to create, you need to actually
create them. In the case of
Here is the code for the package my; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.metal.*; public class MyButtonUI extends MetalButtonUI { private final static Border defaultBorder = MyButtonBorder.getButtonBorder(); private Border savedBorder; protected static MyButtonUI buttonUI; public static ComponentUI createUI (JComponent c) { if (buttonUI == null) { buttonUI = new MyButtonUI(); } return buttonUI; } public void installUI (JComponent c) { super.installUI (c); savedBorder = c.getBorder(); c.setBorder (defaultBorder); } public void uninstallUI (JComponent c) { if (c.getBorder() == defaultBorder) c.setBorder(savedBorder); super.uninstallUI (c); } protected void paintButtonPressed( Graphics g, AbstractButton b) { if (b.isOpaque()) { Dimension size = b.getSize(); Insets insets = b.getBorder().getBorderInsets(b); g.setColor (getSelectColor()); g.fillRect (insets.left, insets.top, size.width-insets.left-insets.right, size.height-insets.top-insets.bottom); } } } The Button Border
The package my; import java.awt.*; import javax.swing.*; import javax.swing.border.*; 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); } } Putting It All Together
And, that is all there is to it. Borrowing heavily from the Simple
example that comes with the Swing component set, the following demonstrates
all the hard work above. You can extend it as you add more customized
user interfaces to import java.awt.*; import java.awt.event.*; import javax.swing.*; public class MyExample extends JPanel { private static String feel = UIManager.getCrossPlatformLookAndFeelClassName(); 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 = feel; } try { UIManager.setLookAndFeel(lnfName); Container c = MyExample.this.getTopLevelAncestor(); SwingUtilities.updateComponentTreeUI (c); c.validate(); } catch (Exception ex) { System.err.println ( "Could not swap LookAndFeel: " + lnfName); } } }; ButtonGroup group = new ButtonGroup(); JRadioButton basicButton = new JRadioButton ("Default"); 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(); } } Copyright © 1998-1999 MageLang Institute. All Rights Reserved. |