Chapter 13. JFC Fundamentals

CONTENTS
  •  Components and Containers
  •  JFrame
  •  Handling Events with JFrame
  •  The Python Way of Handling Events
  •  The Class Hierarchy for JFrame and Frame
  •  JPanel
  •  JLabel
  •  JComponent
  •  JButton
  •  JTextField
  •  JCheckBox
  •  JRadioButton
  •  List and JList
  •  Summary

Terms in This Chapter

  • Abstract Window Toolkit

  • Checkbox

  • Container

  • Data model

  • Dropdown list

  • Event object

  • File path

  • Frame

  • gif/jpeg

  • Java APIs

  • Java Foundation Classes/Swing

  • Layout management

  • Listener

  • Menu (menubar/popup)

  • Mnemonics

  • Model View Controller

  • Packing

  • Panel

  • Polymorphism

  • Radio/Toggle button

  • Single inheritance

  • Superclass

  • Titlebar

  • Toolbar

  • Vector

In this chapter, we're going to cover the construction of Java GUI applications, including listboxes, text fields, menus, buttons, windows, checkboxes, and panels. In previous chapters, we played with Java applications to work with events and Java classes. Events will be the focus here.

There are two ways to build GUIs in Java Abstract Window Toolkit (AWT) and Java Foundation Classes (JFC)/Swing, which are conceptually similar although very different in execution. JFC is the newer of the two and is supported only by current browsers. AWT is supported by newer and older browsers, but our AWT tour will include only its post-Swing incarnation.

AWT or JFC

If you can't control the client that is, what browser he's using you may want to use AWT. You'll probably also use it when you're working with hand-held devices. Even so, JFC has many features that have no AWT equivalent, so it's a better general-purpose tool.

Actually, JFC encompasses both AWT and Swing, but Swing is the newer, so-called lightweight version of GUI widgets (components). Basically, Swing widgets have no operating system counterpart.

Learning GUI programming in Java is a fairly tall order, especially when you factor in all of the advanced JFC controls such as trees and tables. Covering this topic fully will take several chapters, and even then we can only cover the basics. If you want more, there are plenty of good books out there. One I recommend is Graphic Java, volumes 1 and 2 (Geary, 1998 and 1999), which provides exhaustive references and scores of code examples. Geary documents components by their properties, which makes them easy for Jython users to read and work with. Other good sources are the Java tutorial on JFC and the abundant, and free, Java API documentation.

Like most of the chapters in this book, this one is hands on. For example, we don't go into detail about the Model View Controller (MVC) architecture that JFC uses, but we show examples that use it. If you want to understand the dynamic behavior of MVC, you have to follow along. Repeat after me: The only way to learn programming is to program.

Components and Containers

In Java, you organize components as nested elements. Typically you have a frame, which contains several panels and maybe a toolbar and a menubar. In fact, each panel can contain other components such as text fields and labels or even other panels, so a complete graphical user interface can consist of many nested elements. The JFC and AWT containers are components themselves and can be contained in other containers. You can see this in their class diagrams, which also show their points of similarity.

Design Patterns

AWT and JFC use the design patterns Publish and Subscribe, Observer/Observable, Decorator, and Strategy, among others. These are described in Design Patterns (Gamma et al., 1995) a must read for a deep understanding of the Java APIs.

JFrame

Frames are top-level interfaces that pop up as windows and can be resized and closed. As containers, they typically contain panels and other components, for which they initiate layout management. They can also have an associated menubar and icons.

Here's an example of how a simple frame works using JFrame.

Import JFrame from javax.swing. Create an instance of it, show it, size it, change its title.

>>> from javax.swing import JFrame >>> frame = JFrame() >>> frame.show() >>> frame.size = 200,200

Set the title to "Hello JFC".

>>> frame.title = "Hello JFC"

Setting the Frame's Mouse Cursor

With a frame, as with all components, you can set the mouse cursor. You'll probably want to make the cursor wait during a long operation so it shows a wristwatch or hourglass that lets the user know the application is busy. In the example that follows, we'll set the cursor to WAIT, TEXT, and HAND. After each command, position the mouse over the middle of the frame, and note the image the pointer becomes.

Import Cursor from java.awt, and change the cursor three times.

>>> from java.awt import Cursor >>> frame.setCursor(Cursor.WAIT_CURSOR) >>> frame.setCursor(Cursor.TEXT_CURSOR) >>> frame.setCursor = Cursor.HAND_CURSOR

Adding Components to the Frame

To illustrate adding components to the frame, we'll import some components from AWT. Then we'll create and add a button, a label, a checkbox, and a text field.

>>> from javax.swing import JButton,      JLabel >>> from javax.swing import JCheckBox, JTextField

Add a button component.

>>> frame.contentPane.add(JButton("Button"))

Add a label component.

>>> frame.contentPane.add(JLabel("Label"))

Add a checkbox component.

>>> frame.contentPane.add(JCheckBox("Checkbox"))

Add a text field component.

>>> frame.contentPane.add(TextField("TextField"))

You may think that our frame should be showing some components at this point, but it won't until we tell it to by calling the pack() method.

>>> frame.pack()

A Reminder

Doing this:

>>> frame.contentPane.add(Button("Button"))

is equivalent to doing this:

>>> button = Button("Button") >>> frame.contentPane.add(button)

The first way creates an anonymous instance of Button and adds it to the frame, which is okay since we don't need access to the button component for this example.

BorderLayout versus FrameLayout

Only the last component added to the frame shows up. Why? The answer lies in the default layout manager for Frame BorderLayout which lays out components along the borders. I won't go into detail about BorderLayout here (see Chapter 14 for that). Suffice it to say that we need to change to another manager, FlowLayout, which arranges the components from left to right as long as there's room.

I'll tell you more about FlowLayout later. For now, we'll import the FlowLayout class from the java.awt package and set the frame's layout property to a FlowLayout instance. Then we'll pack the frame, that is, invoke the layout manager.

>>> from java.awt import FlowLayout >>> frame.contentPane.layout = FlowLayout() >>> frame.pack()

Now all of the added components are visible.

JFrame's Lineage

JFC frames work much like AWT frames because they're both cut from the same cloth. Let's examine JFrame's lineage, which is easy to do from the interactive interpreter.

Import JFrame from javax.swing.

>>> from javax.swing import JFrame

Print out JFrame's superclass, which is java.awt.Frame.

>>> print `JFrame.superclass` <jclass java.awt.Frame at 227114466>

This tells us that Frame is an AWT frame. (Follow along.)

>>> import java.awt.Frame >>> JFrame.superclass is java.awt.Frame 1

There are some differences between Frame and JFrame. For example, if you try to add a component to JFrame you get an exception. That's because components can be added only to JFrame's content pane.

JFrame's Design Flaw

One of the key benefits of inheritance is polymorphism, so you might think we could replace Frame with JFrame whenever possible. That's not the case, however. According to the Java API Documentation, JFrame and java.awt.Frame are not fully compatible.

In my opinion, making JFrame's add() method throw an exception is a design flaw. Its correct behavior should be to add the component to the content pane and then have separate methods for adding it directly to JFrame. Instead, by redefining the semantics and function of add(), the developers broke the contract of Frame's interface.

The key to working with JFrame is not to do this:

jframe.add(component)

but to do this:

frame.contentPane.add(child);

Handling Events with JFrame

Since Frame is a subclass of java.awt.Frame, which is a subclass of java.awt.Window, you can listen for window events with its event mechanism. Import the jinfo class to inspect the event properties of Frame (JFrame's superclass).

>>> import jinfo >>> from java.awt import Frame >>> jinfo.getEventsInfo(Frame) []

As you can see, Frame has no event properties, because its events are defined from its base class, java.awt.Window.

>>> Frame.superclass <jclass java.awt.Window at 228949474>

Therefore, we need to inspect Window's event properties.

>>> jinfo.getEventsInfo(Window) [<beanEventProperty windowIconified for event interface java.awt.event.WindowListener at 861240813>, <beanEventProperty WindowActivated for event interface java.awt.event.WindowListener at 863600109>, <beanEventProperty windowClosing for event interface java.awt.event.WindowListener at 862551533>, <beanEventProperty windowClosed for event interface java.awt.event.WindowListener at 862813677>, <beanEventProperty windowDeiconified for event interface java.awt.event.WindowListener at 861502957>, <beanEventProperty windowOpened for event interface java.awt.event.WindowListener at 860978669>, <beanEventProperty windowDeactivated for event interface java.awt.event.WindowListener at 862289389>]

There's a lot of them. I've done a little formatting to make them more readable.

[<... windowIconified for event interface java.awt.event.WindowListener ...>, <... WindowActivated for event interface java.awt.event.WindowListener ...>, <... windowClosing for event interface java.awt.event.WindowListener ...>, <... windowClosed for event interface java.awt.event.WindowListener ...>, <... windowDeiconified for event interface java.awt.event.WindowListener ...>, <... windowOpened for event interface java.awt.event.WindowListener ...>, <... windowDeactivated for event interface java.awt.event.WindowListener ...>]

Notice that the getEventsInfo() function extracts all of the information needed to work with event properties, but it's a little hard to read. Here's an easier way.

Import all of jinfo and import java.awt.Window.

>>> from jinfo import * >>> from java.awt import Window

Use getEventsInfo() to get the sequence of event properties from Window.

>>> eProps = getEventsInfo(Window)

Use len() to determine how many properties there are.

>>> print `len(eProps)` 7

Once you know how many properties there are, invoke printEventProperty() to print out details about the event.

>>> printEventProperty(eProps[0]) Event Property:            windowOpened Defined in:                java.awt.event.WindowListener Event:                            java.awt.event.WindowEvent Event properties for java.awt.event.WindowEvent:     window              Type: org.python.core.PyBeanProperty Public Event fields for java.awt.event.WindowEvent:      static final WINDOW_FIRST          Type: int      static final WINDOW_LAST           Type: int      static final WINDOW_OPENED         Type: int      static final WINDOW_CLOSING        Type: int      static final WINDOW_CLOSED         Type: int      static final WINDOW_ICONIFIED      Type: int      static final WINDOW_DEICONIFIED    Type: int      static final WINDOW_ACTIVATED      Type: int

printEventProperty() tells you three important things:

  • The name of the interface that defines the listener java.awt.event.WindowListener

  • The name of the class that defines the event object passed to the listener java.awt.event.WindowEvent

  • The properties and fields associated with the event object

Let's look at windowDeactivated:

>>> printEventProperty(eProps[6]) Event Property:            windowDeactivated Defined in:                java.awt.event.WindowListener Event:                     java.awt.event.WindowEvent Event properties for java.awt.event.WindowEvent:      window             Type: org.python.core.PyBeanProperty Public Event fields for java.awt.event.WindowEvent:      static final WINDOW_FIRST          Type: int      static final WINDOW_LAST           Type: int      static final WINDOW_OPENED         Type: int      static final WINDOW_CLOSING        Type: int      static final WINDOW_CLOSED         Type: int      static final WINDOW_ICONIFIED      Type: int      static final WINDOW_DEICONIFIED    Type: int      static final WINDOW_ACTIVATED      Type: int      static final WINDOW_DEACTIVATED    Type: int

You might want to look up the rest of the event properties and traverse Frame's lineage until you reach a superclass called Object.

Two Ways to Look Up Events with One Method

This one works best on Windows 98 because it pauses after each event:

>>> import jinfo >>> from java.awt import Window >>> jinfo.getEventsInfo(Window, 1, 1)

This one works best on Windows NT because you can scroll the DOS box to see the results:

>>> import jinfo >>> from java.awt import Window >>> jinfo.getEventsInfo(Window, 1)

 

Table 13-1. Frame, JFrame, and Window Events
windowOpened(WindowEvent e) The window is first opened.
windowClosed(WindowEvent e) The window is closed.
windowClosing(WindowEvent e) The user is requesting to close the window a good time to see if the user wants to save any unsaved data.
windowActivated(WindowEvent e) The window is the active window; the window or one of its contained components have the keyboard focus.
windowDeactivated(WindowEvent e) The window doesn't have keyboard focus.
windowIconified(WindowEvent e) Invoked when the window is minimized.
windowDeiconified(WindowEvent e) The window has been restored to its size before it was iconified.

Table 13-1 lists and describes the events supported by Frame, JFrame, and Window. All of the event properties have a one-to-one correspondence to a method in their listener interface.

Event Handling for Frame and JFrame

The following example demonstrates Java event handling for both JFrame and Frame. It also shows how to treat the two classes polymorphically.

Create a Listener class by extending the java.awt.event.WindowListener interface.

from java.awt.event import WindowListener class Listener (WindowListener):      def windowOpened(self, windowEvent):           self.handleEvent("windowOpened",windowEvent)      def windowClosed(self, windowEvent):           self.handleEvent("windowClosed", windowEvent)      def windowClosing(self, windowEvent):           self.handleEvent("windowClosing",windowEvent)      def WindowActivated(self, windowEvent):           self.handleEvent("WindowActivated", windowEvent)      def windowDeactivated(self, windowEvent):           self.handleEvent("windowDeactivated", windowEvent)      def windowIconified(self, windowEvent):           self.handleEvent("windowIconified", windowEvent)      def windowDeiconified(self, windowEvent):           self.handleEvent("windowDeiconified", windowEvent)      def handleEvent(self, event_name, event):           print "Event: " + event_name           print "Event info: " + `event.class`           print "Source: " + `event.source.title`           print "---------------- "

Adapters

In the Listener code example, we were interested in all methods, so we used WindowListener. To save time you can derive the listener from WindowAdapter and only override the events that interest you.

All listener classes have corresponding adapter classes. The best way to know if a particular event listener interface has a corresponding event listener class is to check the Java API documentation.

Essentially we've created a class that implements java.awt.event.WindowListener. We handle every event by calling the handleEvent() method, which prints out the event name, a string denoting which event occurred, the name of the event class that contains information regarding the event, and the source's title property.

Now we'll create two frames, javax.swing.JFrame and java.awt.Frame. Then we'll pass an instance of the listener class (listener) with addWindowListener to register the event listener with the event source. To create and set up the frames, I've created a function called createFrame(), which takes a class and a title as arguments. CreateFrame() uses the class to create an instance of Frame and then sets its size, title, and visibility. The reason we can use createFrame() is that, JFrame being derived from Frame, both implement the same interface.

Here's the createFrame() method (from WindowEventListener.py).

def createFrame(clazz, title, listener):      frame = clazz()      frame.addWindowListener(listener)      frame.setSize(200, 200)      frame.setTitle(title)      frame.setVisible(1)      return frame

Note that the call to addWindowListener registers an instance of the Listener class with a frame.

Now we'll use the createFrame() function to create both a Frame and a JFrame.

from java.awt import Frame from javax.swing import JFrame ... ...      listener = Listener()      raw_input ("Press Enter to create a JFC Frame")      jfc_frame = createFrame(JFrame, "JFC Frame", listener)      raw_input ("Press Enter to create an AWT Frame")      awt_frame = createFrame(Frame, "AWT Frame", listener)

Since the same listener instance is registered with both Frame and JFrame, it will get events from both.

Run the WindowEventListener class and, when it asks you to "Press Enter to create a JFC Frame," do so. You should see the following in the DOS box:

Event: WindowActivated Event info: <jclass java.awt.event.WindowEvent at 1384027101> Source: 'JFC Frame' ---------------- Event: windowOpened Event info: <jclass java.awt.event.WindowEvent at 1384027101> Source: 'JFC Frame' ---------------- 

When the window is first created, it sends the WindowActivated and windowOpened events.

Now return to where you ran the script; you should get the following message:

Event info: <jclass java.awt.event.WindowEvent at 1384027101> Source: 'JFC Frame' ---------------- 

As you can see, the JFC frame is no longer active, so it sends out a windowDeactivated event.

Now that the DOS box is the active window, hit Return. You should get the following messages:

Event: WindowActivated Event info: <jclass java.awt.event.WindowEvent at 1408756942> Source: 'AWT Frame' ---------------- Event: windowOpened Event info: <jclass java.awt.event.WindowEvent at 1408756942> Source: 'AWT Frame' ---------------- 

Move the AWT frame so that you can see the JFC frame as well. Select each a few times (by clicking once on their individual captions). As you go from one to the other, you should get the following messages:

Event: windowDeactivated Event info: <jclass java.awt.event.WindowEvent Source: 'JFC Frame' ---------------- Event: WindowActivated Event info: <jclass java.awt.event.WindowEvent Source: 'AWT Frame' ---------------- Event: windowDeactivated Event info: <jclass java.awt.event.WindowEvent Source: 'AWT Frame' ---------------- Event: WindowActivated Event info: <jclass java.awt.event.WindowEvent Source: 'JFC Frame' ---------------- 

If you minimize the JFC frame, you should get these messages in the DOS box:

Event: windowIconified Event info: <jclass java.awt.event.WindowEvent Source: 'JFC Frame' ---------------- Event: windowDeactivated Event info: <jclass java.awt.event.WindowEvent Source: 'JFC Frame' ---------------- Event: WindowActivated Event info: <jclass java.awt.event.WindowEvent Source: 'AWT Frame' ---------------- 

When the JFC frame is minimized, it sends out a windowIconified event followed by a windowDeactivated event. Since the AWT frame was the last active window, it becomes the active window again and sends out a WindowActivated event. If you can maximize the JFC frame, you'll get the following messages:

Event: windowDeactivated Event info: <jclass java.awt.event.WindowEvent Source: 'AWT Frame' ---------------- Event: WindowActivated Event info: <jclass java.awt.event.WindowEvent Source: 'JFC Frame' ---------------- Event: windowDeiconified Event info: <jclass java.awt.event.WindowEvent Source: 'JFC Frame' ---------------- 

First the AWT frame and then the JFC frame are deactivated; then the JFC frame sends a windowDeactivated event. Close the JFC frame by clicking on its close control (on Windows 95/98/NT, it's an X in the top right corner next to the caption). You should get the following messages:

Event: windowClosing Event info: <jclass java.awt.event.WindowEvent Source: 'JFC Frame' ---------------- Event: windowDeactivated Event info: <jclass java.awt.event.WindowEvent Source: 'JFC Frame' ---------------- Event: WindowActivated Event info: <jclass java.awt.event.WindowEvent Source: 'AWT Frame' ---------------- 

Now try to close the AWT frame using the same technique. What happens? Why?

The JFC frame's default method of closing a window is hiding the frame. Since the AWT frame doesn't handle the windowClosing event, you have to add a handler and do it yourself.

Unlike java.awt.Frame, javax.swing.JFrame responds when a user attempts to close a window. You can change the default behavior (hiding the frame) by invoking the setDefaultCloseOperation() method to make the frame exit the application. Look this up, as well as other default operation-closing methods, in the Java API documentation.

The Complete Java Event-Handling Code

Here's the complete listing (WindowEventListener.py) for the Java way of handling events:

from java.awt import Frame from javax.swing import JFrame from java.awt.event import WindowListener class Listener (WindowListener):      def windowOpened(self, windowEvent):           self.handleEvent("windowOpened",windowEvent)      def windowClosed(self, windowEvent):           self.handleEvent("windowClosed", windowEvent)      def windowClosing(self, windowEvent):           self.handleEvent("windowClosing",windowEvent)      def WindowActivated(self, windowEvent):           self.handleEvent("WindowActivated", windowEvent)      def windowDeactivated(self, windowEvent):           self.handleEvent("windowDeactivated", windowEvent)      def windowIconified(self, windowEvent):           self.handleEvent("windowIconified", windowEvent)      def windowDeiconified(self, windowEvent):           self.handleEvent("windowDeiconified", windowEvent)      def handleEvent(self, event_name, event):           print "Event: " + event_name           print "Event info: " + `event.class`           print "Source: " + `event.source.title`           #print "Event Properties: " + `dir(event)`           print "---------------- "      def createFrame(clazz, title, listener):           frame = clazz()           frame.addWindowListener(listener)           frame.setSize(200, 200)           frame.setTitle(title)           frame.setVisible(1)           return frame      def main():           listener = Listener()           raw_input ("Press Enter to create a JFC Frame")           jfc_frame = createFrame(JFrame, "JFC Frame", listener)           raw_input ("Press Enter to create an AWT Frame")           awt_frame = createFrame(Frame, "AWT Frame", listener)      if __name__ == "__main__":           main()

The Python Way of Handling Events

The following brief interactive session highlights the Python way of handling events:

>>> def closing(event): ...     print "windowClosing" ... >>> def closed(event): ...     print "windowClosed" ... >>> def open(event): ...     print "windowOpened" ...

Import a JFrame, create an instance of it, and configure the instance.

>>> from javax.swing import JFrame >>> frame = JFrame() >>> frame.title = "JFrame frame" >>> frame.size = 200,200

Set the event properties of the frame to the functions created.

>>> frame.windowClosing = closing >>> frame.windowClosed = closed >>> frame.windowOpened = open

Notice that the event properties windowClosing, windowClosed, and windowOpened correspond to the methods in the java.awt.WindowListener interface. Show the window for the first time to get the windowOpened event.

>>> frame.visible = 1 windowOpened

Close the frame using the Close button in the upper right corner to get the DOS box message.

windowClosing

Make the frame visible again. (You don't get another windowOpened event; that happens only when the frame becomes visible for the first time.)

>>> frame.visible = 1

Call the frame's dispose() method to actually close the window. You'll get a windowClosed event.

>>> frame.dispose() windowClosed

As you can see, for just a few events you can define just a few event handlers and assign them to the event property of the frame instance. The event properties always correspond to the event listener's methods.

Python's WindowEventListener

The following example illustrates the same functionality as in the WindowEventListener.py module, except that everything is written using Python event properties and handlers instead of the more Java-centric way of doing things. Read the code, and see if you can pick out the differences.

from java.awt import Frame from javax.swing import JFrame def windowOpened(windowEvent):      handleEvent("windowOpened",windowEvent) def windowClosed(windowEvent):      handleEvent("windowClosed", windowEvent) def windowClosing(windowEvent):      handleEvent("windowClosing",windowEvent) def WindowActivated(windowEvent):      handleEvent("WindowActivated", windowEvent) def windowDeactivated(windowEvent):      handleEvent("windowDeactivated", windowEvent) def windowIconified(windowEvent):      handleEvent("windowIconified", windowEvent) def windowDeiconified(windowEvent):      handleEvent("windowDeiconified", windowEvent) def handleEvent(event_name, event):      print "Event: " + event_name      print "Event info: " + `event.class`      print "Source: " + `event.source.title`      #print "Event Properties: " + `dir(event)`      print "---------------- " def createFrame(clazz, title):      frame = clazz()      frame.windowClosing = windowClosing      frame.windowOpened = windowOpened      frame.windowClosed = windowClosed      frame.windowIconified =    windowIconified      frame.windowDeiconified = windowDeiconified      frame.WindowActivated = WindowActivated      frame.windowDeactivated = windowDeactivated      frame.size = 200, 200      frame.title = title      frame.visible = 1      return frame def main():      raw_input ("Press Enter to create a JFC Frame")      jfc_frame = createFrame(JFrame, "JFC Frame")      raw_input ("Press Enter to create an AWT Frame")      awt_frame = createFrame(Frame, "AWT Frame") if __name__ == "__main__":      main()

I find myself using the Python way more often than the Java way, mainly because I have to import fewer classes and have to remember only the name of the event handler, not every method in the listener.

The Class Hierarchy for JFrame and Frame

For review, let's cover the class hierarchy of JFrame and then Frame. Start up the interactive interpreter and follow along. To get the superclass of JFrame, import JFrame, and then inspect its superclass property.

>>> from javax.swing import JFrame >>> JFrame.superclass <jclass java.awt.Frame at 214168159>

To find out Frame's superclass, append another superclass to it. Since superclass is an instance of jclass and jclass has the property superclass, we can get the superclass of JFrame's superclass like this:

>>> JFrame.superclass.superclass <jclass java.awt.Window at 217313887>

We can continue to do this until we hit Object.

>>> JFrame.superclass.superclass.superclass <jclass java.awt.Container at 219411039> >>> JFrame.superclass.superclass.superclass.superclass <jclass java.awt.Component at 221770335> >>> JFrame.superclass.superclass.superclass.superclass.superclass <jclass java.lang.Object at -1247546800>

Unlike Python classes, Java programming language classes can only singly inherit from a superclass, so they have a narrow chain of superclasses. JFrame's class hierarchy looks like this:

Object Component Container Window Frame JFrame

Frame's class hierarchy looks like this:

Object Component Container Window Frame

What's revealed is that JFrame and Frame inherit functionality from Container, Component, and Window. To find out more about these base classes and how they relate to JFrame and Frame, look them up in the Java API documentation.

Component Functionality Inherited from JFrame and Frame

Now let's look at some of the functionality that JFrame and Frame get from Component. As always, follow along. Create a frame.

>>> from javax.swing import JFrame >>> frame = JFrame() >>> frame = JFrame(visible=1, size=(200,200))

From Component comes the ability to move the frame around the screen.

>>> from javax.swing import JFrame >>> frame = JFrame() >>> frame = JFrame(visible=1, size=(200,200))

The bounds() method takes four arguments: the x and y positions of the upper left corner of the frame and the frame's width and height. If you want to change the location of a frame without changing its width and height, use setLocation().

>>> frame.setLocation(50,50) >>> frame.setLocation(0,0)

The background property from Component allows you to change the RGB (red-green-blue) value of the frame's background.

>>> frame.background = (255, 0 , 0) >>> frame.background = (0, 255, 0) >>> frame.background = (0, 0, 255) >>> frame.background = (255, 255, 0)

Here are some other familiar properties that come from Component:

>>> frame.size = 150, 150 >>> frame.visible = 1 >>> frame.visible 1

JPanel

Panels allow you to organize portions of a larger user interface within a frame, a dialog, or a window. Like frames (java.awt.Frame and javax.swing.JFrame), panels extend the java.awt.Container class, which means that they can contain other components. However, they can't exist on their own but must be in the context of a window, dialog, or frame, or in the context of another panel contained in a top-level window (like Dialog, Frame, or Window).

Here's a JFC/Swing example of adding components with JPanel, in which every time the Add button is pressed another button is added to a panel. Import JPanel and JFrame from the javax.swing package.

>>> from javax.swing import JPanel, JFrame, JButton

Create a frame variable in the global space of the main module. Also, create an instance of JPanel called pane, and initialize the count to 0.

>>> frame = None >>> pane = JPanel() >>> count = 0

Define an add() function that takes the global variables pane, count, and frame into its local namespace and adds a button to the panel called Pane. add() also increments the count variable, which keeps track of how many buttons are added.

>>> def add(event): ...     global pane ...     global count ...     global frame ...     count = count + 1 ...     pane.add(JButton('Button' + `count`)) ...     frame.pack() ...

Define a remove() function that removes the last button. This method uses the count variable (which is in the module namespace) to determine the last button removed.

>>> def remove(event): ...     global pane ...     global count ...     global frame ...     pane.remove(count-1) ...     count = count  1 ...     frame.pack()

Supply an Add button (addB) to add a button to the pane and a Remove button (removeB) to remove a button. The event handler for the Add button is set to the add() function.

>>> addB = JButton ("Add", actionPerformed = add) >>> removeB = JButton("Remove", actionPerformed = remove) >>> pane.add(addB); pane.add(removeB) >>> count = 2

Create a frame, and set its title to "Panel Demo." Set the content pane to the pane instance defined earlier, and make the frame visible.

>>> frame=JFrame(title="Panel Demo", contentPane=pane, visible=1) >>> frame.pack()

Hit the Add button twice to add a third and a fourth button to the panel. Hit it two more times to add a fifth and a sixth button. Hit the Remove button twice to remove the fifth and sixth buttons. Hit the Add button a good 10 to 20 times, and then you can resize the frame with the mouse.

JLabel

Labels are components for placing text in a container. From a user perspective, they're read-only, although their text can be changed programmatically. Let's have a short interactive session that demonstrates using javax.swing.JLabel.

Import the classes needed.

>>> from javax.swing import JLabel, JFrame

Create a frame to hold the label.

>>> frame = JFrame(visible=1, title="Label Demo")

Create the label, and add it to the frame; then pack the frame to make the label visible.

>>> label = JLabel("Hello World")#Create a label >>> frame.contentPane.add(label) #add the label to the frame >>> frame.pack()

For this next exercise we need to resize the window with the mouse so we can read all of the text in the frame caption (sometimes referred to as the titlebar). You can set the alignment of the label text for center, right, or left.

>>> label.horizontalAlignment = JLabel.CENTER >>> label.horizontalAlignment = JLabel.RIGHT >>> label.horizontalAlignment = JLabel.LEFT

JComponent

JLabel is derived from JComponent, which is full of functionality such as the ability to set an icon or tooltip to a label. The following example illustrates some of the things you can do. Import the JLabel and JFrame classes.

>>> from javax.swing import JLabel, JFrame

Create a frame and a label; put the label in the frame's content pane.

>>> frame = JFrame(visible=1, title="JComponent/JLabel Demo") >>> label = JLabel("JComponent/JLabel demo") >>> frame.contentPane.add(label); frame.pack()

We'll use the label just created for the following exercises and examples.

Working with Tooltips

The first JLabel we'll look at is toolTip, which provides help text about what a component does.

>>> label.toolTipText = "Tooltips are cool"

If you go to the frame and hover the mouse over the JLabel for a few seconds, a little window pops up with the message "Tooltips are cool."

Color and the Opaque Property

The opaque property allows a component to set its own background color rather than use the color of the container's background (the default).

>>> label.opaque 0

The 0 (false) value tells us that the default is set, so the code above will change if we change the background.

>>> frame.contentPane.background = (255,0,0)

However, just changing the label background won't do anything. When we execute the following code setting the panel to green, the label stays the same:

>>> label.background = (0, 255, 0)

Instead, we have to set the opaque property to true (1). Once we do that, we'll get the right label color when the label background changes.

>>> label.opaque = 1

You can also set the foreground of a component.

>>> label.foreground = (255,255,255)

An Easier Way to Work with Colors

You don't have to remember the RGB values for your common colors. The java.awt.Color class makes working with colors pretty simple by defining the following constants:

Color.black Color.blue Color.cyan Color.darkGray Color.gray Color.green Color.lightGray Color.magenta Color.orange Color.pink Color.red Color.white Color.yellow

Try this short exercise:

>>> from java.awt import Color     #import the color class >>> label.background = Color.black     #set the background black >>> label.foreground = Color.white     #set the foreground white

Now take a look at the frame that contains your label.

>>> label.foreground = Color.black     #set the foreground black >>> label.background = Color.white     #set the background white

Unlike javax.swing.JLabel, java.awt.Label is pretty much always opaque and so has no opaque property. However, the foreground and background properties work the same for both.

Fonts

JComponent has a font property that allows you to see what font the label is using. It works essentially the same in both java.awt.Label and javax.swing.JLabel.

>>> label.font javax.swing.plaf.FontUIResource[family=dialog.bold,name=Dialog,style=bold,     size=12]

label.font also allows you to set the label font.

>>> font = Font("Times New Roman", Font.BOLD, 12) >>> label.font = font

Finding Fonts

Import GraphicsEnvironment.

>>> from java.awt import GraphicsEnvironment >>> ge = GraphicsEnvironment.getLocalGraphicsEnvironment()

Invoke len().

>>> fontlist = ge.getAllFonts() >>> len(fontlist) 98 >>> for x in range (0, 10): ...     print fontlist[x].name ...

Print the output.

Abadi MT Condensed Light Arial Arial Black Arial Cursiva Arial Narrow Arial Narrow fed Arial Narrow fed kursiv Arial Narrow kursiv Arial Negreta Arial Negreta cursiva

To save space only the first ten fonts appear. To deal with large font lists we can start with the font families, which group fonts that have similar characteristics.

>>> families = ge.getAvailableFontFamilyNames() >>> len(families) 62 >>> for x in range (0, 10): ...     print families[x] ... Abadi MT Condensed Light Arial Arial Black Arial Narrow Book Antiqua Bookman Old Style Calisto MT Century Gothic Comic Sans MS Copperplate Gothic Bold

To find out more about fonts, look up java.awt.Font; then try to create an italicized font.

Icons

JLabel has an icon property that you can use to set an image in the label component.

Import ImageIcon from javax.swing.

>>> from javax.swing import ImageIcon

Create the image icon, and assign it to the icon property. (In this example, I used one of the images that ships with the JDK. You may have to adjust the file path if you installed the JDK somewhere else or if you're using a different JDK version.)

>>> label.icon = ImageIcon("c:\\jdk1.2.1\\demo\\jfc\\java2d\\images\\duke.gif") >>> frame.pack()

Note that the ImageIcon constructor we're using takes a file path to an image. You can use any gif or jpeg image. You also can change the position of the text relative to the icon.

>>> label.horizontalTextPosition = JLabel.RIGHT >>> label.verticalTextPosition = JLabel.TOP

Look up setVerticalTextPosition and setHorizontalTextPosition for JLabel in the Java API documentation; then move the label text to the bottom, to the right of the Duke icon.

Images make graphical user interfaces graphical. JLabel can easily display images as icons, but for java.awt.Label image display is not so simple.

Mnemonics

Mnemonics allow users to select a component for input with the keyboard and can be displayed with labels. However, since labels can't do anything other than display text and images, they need the help of another component to receive the input, such as a container or text field. Here's how to set a mnemonic:

>>> label.setDisplayedMnemonic('J')

The first J is underlined, which signifies it as the mnemonic. JLabel's labelFor property allows you to set the component that will receive the input focus when the mnemonic is pressed. On Windows, you activate the mnemonic by pressing Alt-J; on most UNIX boxes, you press meta-J.

Using labelFor, let's set up a few components and associate one of them with the label's mnemonic. Add two buttons to the frame, and set the layout so that the frame displays all of the components, that is, the two buttons and the label.

>>> from javax.swing import JButton     #import the JButton class >>> j = JButton ("J button")            #create two buttons >>> b2 = JButton ("button 2") >>> frame.contentPane.add(j)            #add the buttons to the frame >>> frame.contentPane.add(b2) >>> from java.awt import FlowLayout     #import the FlowLayout class >>> frame.contentPane.layout = FlowLayout() #set the layout >>> frame.pack()                        #layout the components

Associate the label with the j button.

>>> label.labelFor = j

When you press Alt-J, the j button will receive the input focus. You can tell because its text becomes outlined in light gray. Now use the Tab key to put the focus on the b2 button (the one with "button 2"). Most often, labelFor is assigned a text field or some other component that is incapable of displaying a mnemonic.

JButton

We touched on buttons in Chapter 11. They're pretty simple, so we'll cover them here. Import the JFrame, JButton, Frame, and Button classes.

>>> from javax.swing import JFrame, JButton >>> button = JButton("J Button") >>> frame = JFrame(visible=1) >>> frame.title = "JFC" >>> frame.contentPane.add(button) >>> frame.pack()

Java Event Handling and JButton

To demonstrate Java event handling we'll create a class that implements ActionListener. Then, using addActionListener, we'll register an instance of the Listener class to the button.

Define the Listener class.

>>> from java.awt.event import ActionListener >>> class ButtonListener(ActionListener): ...     def actionPerformed(self, e): ...             print e.getSource().getText() + " was clicked" ...

Create an instance of Listener, and register it with the button.

>>> listen = ButtonListener() >>> button.addActionListener(listen)

Click the J button a few times to see the following message in the DOS box:

J button was clicked J button was clicked J button was clicked

Python Event Handling and JButton

Again, since we covered Java event handling in Chapter 11, we'll just quickly review it here, continuing with the Java example. Define the event handler, which is just a function with an argument.

>>> def actionPerformed(e): >>>   print "The Python event handler" >>>   source = e.source >>>   if source.class is JButton: >>>          print source.text + " was clicked"

Assign the handler by setting the button's actionPerformed event property to the actionPerformed event.

>>> button.actionPerformed = actionPerformed >>> def actionPerformed(e): >>>   print "The Python event handler" >>>   source = e.source >>>   if source.class is JButton: >>>          print source.text + " was clicked" >>> button.actionPerformed = actionPerformed

Try the event handler by pressing the button with the mouse pointer. If you haven't been following along as you should have been read and run buttons.py in Chapter 12.

Button and JButton Shared Properties

Using the enabled(Boolean b) property, disable the AWT and JFC buttons.

>>> button.enabled = 0

You'll notice that the buttons' text is grayed out. If you click on them, you won't get any action event. To enable the buttons again use:

>>> button.enabled = 1

There are some things you can do with java.swing.JButton that you can't do with java.awt.Button. For one, with the mnemonic (char mnemonic) property you can set the button so that it receives input focus when you press a shortcut key.

You can set up the frame to show more than one component and then add an extra component to it. Import FlowLayout (we'll get into layout managers in Chapter 14).

>>> from java.awt import FlowLayout

Add a second button to each of the frames.

>>> frame.contentPane.add(JButton("second button"))

Set the layout to FlowLayout so that the extra components are visible.

>>> frame.contentPane.layout = FlowLayout

Pack the frames so that the layout takes effect.

>>> frame.pack()

To see how the mnemonic property works, set it with JButton.

>>> button.setMnemonic('J') >>> button.mnemonic = ord('J')   #Does the same as above

Go to the JFC frame, and use the Tab key to select the second button. Now type in Alt-J (meta-J on UNIX or apple-J on Mac). The result is that JButton will have input focus.

Want to try this with java.awt.Button? You can't. Mnemonics are a JFC-only feature. Another JFC exclusive is associating an image icon with a button. The icon property works just like its Jlabel counterpart.

icon (Icon defaultIcon)

Here's how you set an image in a button:

>>> from javax.swing import ImageIcon >>> button.icon = ImageIcon("c:\\jdk1.2.1\\demo\\jfc\\java2d\\images\\duke.gif") >>> frame.pack()

You can also move the button's text in relation to its icon. Again, this works just like it does in JLabel, as we saw in an earlier example.

As an exercise, look up the properties for the JButton class; then create a button whose image changes when the mouse rolls over it. Hint: Check out javax.swing.AbstractButton, the superclass of JButton, particularly its setRolloverEnabled() and setRolloverIcon() methods. AbstractButton contains much of the functionality for JButton. It's also a base class for JCheckBox, JRadioButton, JMenuItem, and JToggleButton, which means that all of those classes can be used interchangeably (that is, polymorphically). Many of the methods and properties we use with JButton we can also use with any of AbstractButton's subclasses.

JTextField

JTextField allows you to edit text in text fields. It's pretty basic and can be explained with a simple example. Import JTextField and JFrame from the javax.swing package.

>>> from javax.swing import JTextField

Create a frame and a text field, and add it to the frame. We'll use this text field to demonstrate how to set and read text. Create the frame.

>>> frame = JFrame()

Create the TextField instance.

>>> textField = JTextField(20)

Add the instance to the center of the frame.

>>> from java.awt import BorderLayout >>> frame.add(textField, BorderLayout.CENTER)

Default Layout Manager

The default layout manager for the frame is BorderLayout, which essentially allows you to add components to a frame's north, south, east, and west borders and its center region. We'll cover it and the other layout managers in Chapter 14.

Now we'll create a toolbar, using a panel and two buttons, that will allow us to work with the text field's properties. Import Panel, and create a panel for a toolbar.

>>> from java.awt import Panel >>> toolbar = Panel()

Create two buttons for setting and reading text field text.

>>> readText = JButton("Read Text") >>> setText = JButton("Set Text")

Add the buttons to the toolbar.

>>> toolbar.add(readText) >>> toolbar.add(setText)

Add the buttons to the north border of the frame.

>>> frame.add(toolbar, BorderLayout.NORTH)

Pack the frame to make the component layout visible.

>>> frame.pack() >>> frame.visible = 1

Set up the button event handlers to manipulate the text field. This one reads the text and prints it out to the console window:

>>> def readTextClicked(e): ...     print textField.text ...

This one sets the text field text:

>>> def setTextClicked(e): ...     textField.text = "I really like Java and Python" ...

Associate the handlers with the actionPerformed event by assigning them to the button's actionPerformed property.

>>> readText.actionPerformed = readTextClicked >>> setText.actionPerformed = setTextClicked

Enter in some text in the text field, and hit the Read Text button a few times

Hello how are you Hello how are you Hello how are you

Hit the Set Text, and then the Read Text button.

I really like Java and Python I really like Java and Python I really like Java and Python

Work with the buttons and the text field to verify that the field is working like java.awt.TextField.

Most of the functionality for JTextField is in JTextComponent. This is an abstract class and the superclass of JTextField and JTextArea (the latter represents a multi line text field). JEditorPane and JTextPane are subclasses of JTextComponent; they add the ability to view HTML and RTF (Rich Text Format) text and to display in-text components and icons.

As an exercise, look up JTextField and JTextComponent in the Java API documentation. Try creating an application that uses JEditorPane to view an HTML page.

JCheckBox

Checkboxes represent a true or false condition. Let's illustrate them with an example application for choosing pizza toppings (jfc_checkbox.py). Import java.awt.Checkbox, and create checkboxes that represent pizza toppings.

>>> from javax.swing import JCheckBox >>> pepperoni = JCheckBox("pepperoni") >>> peppers = JCheckBox("peppers") >>> olives = JCheckBox("olives") >>> anchovies = JCheckBox("anchovies") >>> onions = JCheckBox ("onions") >>> sausage = JCheckBox ("sausage")

Create a frame to put the checkboxes in.

>>> from java.swing import JFrame >>> frame = JFrame(title="Pick your topping", visible = 1)

Add the checkboxes to the frame.

>>> frame.contentPane.add(pepperoni) >>> frame.contentPane.add(peppers) >>> frame.contentPane.add(olives) >>> frame.contentPane.add(anchovies) >>> frame.contentPane.add(onions) >>> frame.contentPane.add(sausage)

Set pepperoni as the default.

>>> pepperoni.selected = 1

Remember that the default layout for a frame is BorderLayout. If we want to show all of the checkboxes created, we need to reset the layout manager to FlowLayout.

>>> from java.awt import FlowLayout >>> frame.contentPane.layout = FlowLayout() >>> frame.pack()

Set the event handler for closing the frame to inspect the checkboxes' state.

>>> def frameClosing(event): ...     for checkbox in frame.components: ...             if(checkbox.selected == 1): print checkbox.label ... >>> frame.windowClosing = frameClosing

Work with the checkboxes to select toppings; then press the Close button on the frame's top left border (for Windows NT/9X).

pepperoni onions sausage

Putting Things Together with JCheck Box

Here's the pizza topping application we just wrote. Follow along, and make sure to read the comments.

     # Import JCheckBox and create checkboxes that represent pizza toppings. from javax.swing import JCheckBox cb = JCheckBox #cb is the JCheckBox class. Remember classes are first class objects in Python. pepperoni = cb("pepperoni") checkboxes=(pepperoni,cb("peppers"),cb("olives"),cb("anchovies"),cb("onions")            ,cb("sausage"))      # Import JFrame and create a frame to hold our checkboxes. from javax.swing import JFrame frame = JFrame(title="Pick your topping", visible = 1)      # Import JPanel and set the frame's contentPane to a JPanel. from javax.swing import JPanel panel = JPanel() frame.contentPane = panel      # Now add the checkboxes to the Panel for a_checkbox in checkboxes:     panel.add(a_checkbox)      # Set the pepperoni's state to selected. pepperoni.selected = 1      # Pack the frame frame.pack() def frameClosing(event):      for checkbox in checkboxes:           if(checkbox.selected == 1): print checkbox.text frame.windowClosing = frameClosing

Now we want to offer a one-topping special, which means that we have to change the program so that the customer can select only one checkbox. (The changes are highlighted in bold.) Add each checkbox to a button group.

frame.title = "Choose your one Topping"      #Import the ButtonGroup from javax.swing import ButtonGroup group = ButtonGroup()      #Add each check box to the group. for a_checkbox in checkboxes: group.add(a_checkbox)      # Now let's assume that most people want pepperoni. pepperoni.selected = 1

It doesn't work, does it? Read on.

JRadioButton

With JFC, to make the buttons look and behave like radio buttons, you have to use JRadioButton. Let's continue our one-topping example, using JRadioButton in a button group to allow only one checkbox to be selected.

for a_checkbox in checkboxes:      panel.remove(a_checkbox) group = ButtonGroup() from javax.swing import JRadioButton rb = JRadioButton   #rb is JRadioButton class.      #Remember classes are first class objects in Python. pepperoni = rb("pepperoni") radiobuttons=(pepperoni,rb("peppers"),rb("olives"),rb("anchovies"),              rb("onions"),rb("sausage")) for a_radiobutton in radiobuttons:      group.add(a_radiobutton)      panel.add(a_radiobutton) pepperoni.selected = 1 frame.pack()

Try these exercises:

  • Change the event handler to work with JRadioButton, and then look up AbstractButton, the superclass of JButton, JCheckBox, and JRadioButton. You might also try adding picture icons to represent the different toppings.

  • With the technique we used to replace checkboxes with radio buttons, replace radio buttons with toggle buttons (javax.swing.JToggleButton). Can you think of ways to switch between all three? Hint: JToggleButton has the same properties that JCheckBox and JRadioButton have. Remember that JButton gets much of its functionality from AbstractButton, so all of its properties icon, text, mnemonics, and so forth work for these classes as well. If you want to add icons or change text, you do it in the same way.

List and JList

List components represent choices. They can be in single-selection mode (one item) or multi-selection mode (more than one item). Let's create an example list in the interactive interpreter.

>>> from javax.swing import JList >>> list_items = ["Bruce", "Darrell", "Tony", "Debbie", "Karen"] >>> list = JList(list_items)

Here's the interactive session to create a frame:

>>> from javax.swing import JFrame >>> frame = JFrame("JList example") >>> frame.contentPane.add(list) >>> frame.pack() >>> frame.visible=1

List Events

Now we want to do something with our list that is, handle the list events so we need to inspect JList's event properties (or look them up in the Java API documentation).

Here's the interactive session to inspect the list properties, with the output formatted to make it a little more readable.

>>> getEventsInfo(JList) [<beanEventProperty valueChanged for event interface javax.swing.event.ListSelectionListener>]

We can inspect the events more closely using getEventsInfo to print and pause.

>>> getEventsInfo(JList, 1, 1) Event Property:         valueChanged Defined in:             javax.swing.event.ListSelectionListener Event:                  javax.swing.event.ListSelectionEvent Event properties for javax.swing.event.ListSelectionEvent:      lastIndex          Type: org.python.core.PyBeanProperty      valueIsAdjusting   Type: org.python.core.PyBeanProperty      firstIndex         Type: org.python.core.PyBeanProperty

If you look up the list properties in the Java API documentation, these are the ones you'll find:

  • LastIndex last row that may have changed

  • ValueIsAdjusting true if this is multiple change events

  • FirstIndex first row that may have changed

ListSelectionEvent allows handling of an item selection event, either the Java way or the Python way. Here's the Python way:

>>> def eventListener(event): ...     list = event.source # get a reference to the list, ...                            # the event's source ...     index = event.firstIndex ...     print list.model.getElementAt(index) ...

Now try selecting some list items.

I'm going to leave the Java way as an exercise. I'll give you two hints to help you, but don't read them unless you get stuck. Hint 1: Create a class that subclasses java.awt.event.ItemListener; then create an instance of that class, and pass it to the list.addItemListener() method. Hint 2: The subclass should override the itemStateChanged() method with the arguments self and event.

JComboBox

JComboBox is a combination dropdown list and text field that comes in handy when you have limited real estate on a panel. It's something of a cross between java.swing.JList and javax.swing.JTextField. In fact, JComboBox looks a lot like javax.swing.JList, its only real difference being that it uses the addItem() method to add items to the list.

Here's the JComboBox code:

# Import List, create a list instance and populate the list instance. from javax.swing import JComboBox list = JComboBox() list_items = ("Bruce", "Darrell", "Tony", "Satesh", "Debbie", "Karen") for item in list_items:      list.addItem()()(item)      # Create a frame and add the list to the frame. from javax.swing import JFrame frame = JFrame("Combobox Example") frame.contentPane.add(list); frame.visible=1; frame.pack()      # Handle item event. from java.awt.event import ItemEvent def eventListener(event):      list = event.itemSelectable      item = event.item      print "Current item :" + `item`      if (event.stateChange == ItemEvent.SELECTED):           print " --selected items--"           for item in list.selectedObjects:                print " " + `item` list.itemStateChanged = eventListener

It's a little different from the JList version, but close enough.

As an exercise, try changing the previous example to make the combo box editable. Hint: Look up the JComboBox properties and methods, particularly setEditable.

Working with JList

The Model View Controller (MVC) architecture splits the logic for the model, view, and controller into different class sets. For example, the JList component constructor takes a reference to ListModel. Here's an example of JList (JList.py) that's similar to our JComboBox example:

     # Import List, create a list instance and populate the list instance. from javax.swing import JList list_items = ("Bruce", "Darrell", "Tony", "Debbie", "Karen")      # Create a list with a default model that uses the above list items list = JList(list_items)      # Create a frame and add the list to the frame. from javax.swing import JFrame frame = JFrame("JList Example") frame.contentPane.add(list); frame.visible=1; frame.pack()      # Import this for selection constants from javax.swing import ListSelectionModel list.selectionMode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION      # Handle item event. from java.awt.event import ItemEvent def eventListener(event):      list = event.source      print "Current item :" + list.selectedValue      if (list.selectionMode == ListSelectionModel.MULTIPLE_INTERVAL_SELECTION):           print " --selected items--"           for item in list.selectedValues:                print " " + `item` list.valueChanged = eventListener

I said that JList was similar to JComboBox, but there are differences (highlighted in bold). First, JList has no addItem() (or equivalent) method to add items to the list, so we have to pass list_item to the constructor (we could have passed it to the java.awt.List constructor). Second, we have to import javax.swing.ListSelectionModel to get access to the constants to set selectionMode. selectionMode allows three types of selection: single (one item), single contiguous range (a set of items next to each other), and multiple interval (multiple items).Third, the Jlist and JComboBox event properties are different.

As an exercise, look up javax.swing.ListSelectionModel in the Java API documentation, and inspect the event properties of JList with jinfo. Then look up the listener and event classes associated with them. Change the last example to use Java event handling.

The output for our JList example is as expected.

Current item :Karen   ----selected items----     'Karen' Current item :Tony   ----selected items----     'Tony'     'Karen' Current item :Bruce   ----selected items----     'Bruce'     'Tony'     'Karen'

Try to map the output to the code.

Don't you think it's a little strange not to have an addItem()method? I do. Remember that JList uses the MVC architecture, which keeps the data from view by putting it in the model (the M in MVC). The view (the V in MVC) is the JList itself. If you pass a vector (a sequence in Python) or an array, JList will create a model based on AbstractListModel.

If you want to create an easy-to-use list similar to java.awt.List (which is much easier than JList), you can pass JList an instance of DefaultListModel. DefaultListModel is mutable, so you can add items to it that will show up in the list.

Here's an example (JList_Model) of creating a mutable list based on our java.swing.JComboBox example. I've highlighted the differences in bold and omitted the last part, which doesn't change.

     # Import List, create a list instance and populate the list instance. from javax.swing import JList, DefaultListModel list_items = ("Bruce", "Darrell", "Tony", "Satesh", "Debbie", "Karen")      # Create a list with an empty default model list = JList(DefaultListModel) model = list.getModel() for item in list_items:      model.addElement(item) ... ...

An Easier Way with DefaultListModel

Since model is a property of Jlist, an easier way to write the above would have been

list = JList(DefaultListModel()) for item in list_items:      list.model.addElement(item)

I wrote it the way I did to highlight where the model was coming from.

As an exercise try the JList_Model example again, without an instance of DefaultListModel, and pass it to the JList constructor. What happens? Why? Hint: Look up AbstractDefaultListModel, and inspect the class hierarchy returned from the getModel() method.

Advanced MVC and JList

The real advantage to using MVC is that you can have multiple views of the same data model. When the data changes, the model notifies all of the views so that they're updated.

Remember our address book application? We can create a list data model that uses it, so that when an address changes in the list we can automatically display the change in all open views. To create a data model that's compatible with JList, we need to implement the ListModel interface.

For brevity, we're going to create a very small subset of the Address and AddressBook classes that highlights the MVC list.

What we'll do is define an address class that implements the toString() method from java.lang.Object so that it can be displayed in the listbox. Next we'll define an AddressBook class that contains instances of Address and acts as a data model. It will have to extend ListModel to work with javax.swing.JList. Finally, we'll populate an address book the data model with sample addresses.

MVC in JTree and JTable

The techniques I'm showing you in the MVC example are the same ones you use with JTree and JTable. You have to master them if you want to use advanced Swing components.

MVC, along with the related design patterns (e.g., Observer/Observable), is explained in Design Patterns (Gamma et al., 1995). Reading this book will make you appreciate why MVC was picked for JList, JTable, and JTree.

JList_AddressModel.py

Here's the complete code for JList_AddressModel.py.

from string import * from javax.swing import JList, ListModel from javax.swing.event import ListDataEvent from java.lang import Object      # Define an address class to hold an address class Address(Object):      def __init__(self, name, phone):           self.name = name           self.phone = phone      def __str__(self):           return ljust(self.name,35) + ": " + self.phone      def toString(self):           return self.__str__()      def __repr__(self):           return self.__str__()      # Define an address book to hold addresses class AddressBook(ListModel):      def __init__(self):                # Fill in some initial data           self.list_items = [Address("Darrell", "555-1212")]           self.list_items.append(Address("Bruce", "555-1212"))           self.list_items.append(Address("Karen", "555-1212"))           self.list_items.append(Address("Tony", "555-1212"))           self.list_items.append(Address("Chris", "555-1212"))           self.listeners = []     # To hold listeners           # public int getSize()           # Returns the length of the list.      def getSize(self):           return len(self.list_items)           #     public Object getElementAt(int index)           # Returns the value at the specified index.      def getElementAt(self, index):           return self.list_items[index]           # public void addListDataListener(ListDataListener l)           # Add a listener that gets notified when the list data           # model gets changed           # Parameters:           # l - the ListDataListener      def addListDataListener(self, listener):           print "somebody is listening: " + `listener.class`           self.listeners.append(listener)           # public void RemoveListDataListener(ListDataListener l)           # Removes a listener           # Parameters:           # l - the ListDataListener      def RemoveListDataListener(listener):           print "somebody is ignoring: " + `listener.class`           self.listeners.remove(listener)           # AddAddress()           # Adds an address to the address book      def AddAddress (self, name, number):           self.list_items.append(Address(name, number))                # Notify each listener that our data has changed.                # Being aware that our data changed, they can                # update their view to reflect the changes           for each_listener in self.listeners:                start = len(self.list_items)                end = len(self.list_items)                type = ListDataEvent.INTERVAL_ADDED                event = ListDataEvent(self, type, start, end)                each_listener.intervalAdded(event) AddressBook = AddressBook()      # Create 2 lists using our address book list model list = JList(AddressBook) list2 = JList(AddressBook)      # Create a frame and add the lists to the frame. from javax.swing import JFrame from java.awt import FlowLayout frame = JFrame("JList Custom Data Model Example") frame.contentPane.layout = FlowLayout() #Added to show both lists frame.contentPane.add(list); frame.contentPane.add(list2) frame.visible=1; frame.pack()      #Import this for selection constants from javax.swing import ListSelectionModel list.selectionMode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION      #Handle item event. from java.awt.event import ItemEvent def eventListener(event):      list = event.source      print "Current item :" + `list.selectedValue`      if (list.selectionMode == ListSelectionModel.MULTIPLE_INTERVAL_SELECTION):           print " --selected items--"           for item in list.selectedValues:                print " " + `item` list.valueChanged = eventListener def addNames():      AddressBook.AddAddress("Geoff", "555-1234")      AddressBook.AddAddress("Bill", "555-1235")      AddressBook.AddAddress("Robert", "555-1257")

Run the following in the interactive interpreter:

>>> addNames() >>> frame.pack()

First, we define the Address class.

     # Define an address class to hold an address class Address(Object):      def __init__(self, name, phone):           self.name = name           self.phone = phone      def __str__(self):           return ljust(self.name,35) + ": " + self.phone      def toString()(self):           return self.__str__()      def __repr__(self):           return self.__str__()

Address defines three methods: __str__, __repr__, and toString(). __str__creates the string representation of Address for Python; toString() is its Java equivalent. We need to make Address a string because the listbox uses toString() to display the method. __repr__displays the string in Python when the object is referenced with back quotes. To override toString(), Address uses java.lang.Object as its base class.

Next we define the custom list model, which presents an interesting opportunity to demonstrate events. In previous event examples, our code was geared to listening for (handling) events. In this example, it publishes events (AddressBook is an event source). To turn things inside out, JList will be registering a listener with AddressBook to listen for events that tell it that data has changed.

AddressBook implements ListMode; AddressBook is thus a subclass (in Python speak) of ListModel.

     # Define an address book to hold addresses class AddressBook(ListModel):

AddressBook has to override all of ListModel's methods, which are

  • int getSize()

  • Object getElementAt(int index)

  • void addListDataListener(ListDataListener l)

  • void RemoveListDataListener(ListDataListener l)

In the code that follows, be sure to read all of the comments before each AddressBook method, since they show which ListModel method is being overridden and what it does.

     # public int getSize()      # Returns the length of the list. def getSize(self):      return len(self.list_items)      # public Object getElementAt(int index)      # Returns the value at the specified index. def getElementAt(self, index):      return self.list_items[index]      # public void addListDataListener(ListDataListener l)      # Add a listener that gets notified when the list      # data model gets changed      # Parameters:      # l - the ListDataListener def addListDataListener(self, listener):      print "somebody is listening: " + `listener.class`      self.listeners.append(listener)      # public void RemoveListDataListener(ListDataListener l)      # Removes a listener      # Parameters:      # l - the ListDataListener def removeListDataListener(listener):      print "somebody is ignoring: " + `listener.class`      self.listeners.remove(listener)

addListDataListener and removeListDataListener register and unregister listeners for ListDataEvents from AddressBook (which, you'll remember, is an event source, which means that it publishes events when they occur). addListDataListener prints out a listener's registration. When you set a model, JList registers listeners to it; when you remove a model, JList unregisters those listeners.

The AddressBook class has the AddAddress()method, which adds a new address to list_items and then notifies each listener by calling its intervalAdded() method. AddAddress() first creates a ListDataEvent instance to pass to the listener's intervalAdded() method.

     Rest of 85, 86, top 87# AddAddress()      # Adds an address to the address book def AddAddress(self, name, number):      self.list_items.append(Address(name, number))      # Notify each listener that our data has changed.      # Being aware that our data changed, they can      # update their view to reflect the changes for each_listener in self.listeners:      start = len(self.list_items)      end = len(self.list_items)      type = ListDataEvent.INTERVAL_ADDED      event = ListDataEvent(self, type, start, end)      each_listener.intervalAdded()(event)

Now we'll create an instance of AddressBook. Then we'll create two JFC lists, passing the instance to them. At this point, the JList instance will call addDataListener to register listeners with the model. You can verify this with the console output.

AddressBook = AddressBook()      #Create 2 lists using our address book list model list = JList(AddressBook) list2 = JList(AddressBook)

The rest of the code for this example is pretty much the same as that for the earlier ones. As a final step, we'll define a function called addNames() that adds three addresses to the AddressBook list model.

def addNames():      AddressBook.AddAddress("Geoff", "555-1234")      AddressBook.AddAddress("Bill", "555-1235")      AddressBook.AddAddress("Robert", "555-1257")

AddAddress()notifies all the listeners that an address has been added. (The listeners are the two JList components.)

To run this example from the interactive interpreter, start JList_AddressModel.py with the i option. Then call AddAddress() from the interactive interpreter.

>>> addNames() >>> frame.pack()

Both of the lists are updated because they use the same example. (You have to call the frame.pack() method to resize JList so that all of the items appear. We'll fix this in the next example).

As an exercise, look up javax.swing.ListModel, javax.ListDataEvent, and javax.ListDataListener.

Essentially, JList registers for (that is, subscribes to) events in ListModel. When the model data changes (that is, when addresses are added to the address book), all registered views are notified and thus updated.

In the previous example, both views are JList components; however, in a real MVC application the same model may have more than one view type. For example, a spreadsheet application may have data mapped to a spreadsheet view and a graph view, both of which change when the model for the spreadsheet view changes.

An Easier Way to Create the Address Book

An easier way to create the address book is to use the AbstractListModel class, which implements all of the methods for registering event listeners and notifying them of changes. Here's the changed JList_AddressModel (JList_AddressModel2.py), which uses AbstractListModel for the superclass of AddressBook.

class AddressBook(AbstractListModel):      def __init__(self):                # Fill in some sample data           self.list_items = [Address("Darrell", "555-1212")]           ...           ...           self.list_items.append(Address("Chris", "555-1212"))           # public int getSize()           # Returns the length of the list.      def getSize(self):           return len(self.list_items)           # public Object getElementAt(int index)           # Returns the value at the specified index.      def getElementAt(self, index):           return self.list_items[index]      def AddAddress(self, name, number):           self.list_items.append(Address(name, number))           start = len(self.list_items)           end = len(self.list_items)           fireIntervalAdded(self, type, start, end)

Notice that the code to implement AddressBook (ListModel) is now significantly shorter. I recommend using the AbstractListModel class instead of the ListModel interface, which I used simply to explain MVC.

Working with JScrollPane and the Decorator Design Pattern

The JList class uses Gamma's Decorator design pattern to add scrolling, which essentially adds a JFC frame to a scroll pane by passing a JList instance to JScrollPane's constructor. Thus, the code changes from this:

list = JList(AddressBook) list2 = JList(AddressBook)

to this:

list = JScrollPane(JList(AddressBook)) list2 = JScrollPane(JList(AddressBook)

That's all. The same technique works for adding scrolling to JTree and JTable. When you run this in the interactive interpreter, you don't have to call frame.pack() after you call addNames(). The names show up automatically, and so do the scrollbars.

Now start JList_AddressModel2.py with the i option. Then call the AddAddress() method from the interactive interpreter.

>>> addNames() >>> frame.pack()

Summary

In this chapter, we covered the basics of AWT and JFC components, some of which are polymorphically related. We also covered MVC, which, although advanced, is essential for working with some of the more interesting components of JFC. Learning MVC is good for learning about events, since the architecture publishes events to views.

There's a lot more to cover on JFC and AWT, but we've gleaned enough information to get started. We'll learn more in coming chapters.

CONTENTS


Python Programming with the JavaT Class Libraries. A Tutorial for Building Web and Enterprise Applications with Jython
Python Programming with the Javaв„ў Class Libraries: A Tutorial for Building Web and Enterprise Applications with Jython
ISBN: 0201616165
EAN: 2147483647
Year: 2001
Pages: 25

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net