CS102 – GUI Based on : David Davenport’s slides Spring 2002 References: http://java.sun.com/docs/books/tutorial/uiswing http://www.aduni.org/courses/java Core Java Volume 1 http://www.javaworld.com/javaworld/jw-04-1998/jw-04-howto.html AWT abstract window toolkit AWT components were provided by the JDK 1.0 and 1.1 platforms. delegates into native windowing system therefore, looks different in different platforms AWT Since platforms vary, AWT had to choose a lowest common denominator write once, debug everywhere … JFC & Swing JFC is short for JavaTM Foundation Classes, which encompass a group of features to help people build graphical user interfaces (GUIs). The JFC was first announced at the 1997 JavaOne developer conference and has the following features: The Swing Components Include everything from buttons to split panes to tables. You can see mugshots of all the components in http://java.sun.com/docs/books/tutorial/uiswing/components/components.html . Pluggable Look and Feel Support Gives any program that uses Swing components a choice of looks and feels. For example, the same program can use either the JavaTM look and feel or the Windows look and feel. We expect many more look-and-feel packages to become available from various sources. Accessibility API Enables assistive technologies such as screen readers and Braille displays to get information from the user interface. Java 2DTM API (Java 2 Platform only) Enables developers to easily incorporate high-quality 2D graphics, text, and images in applications and in applets. Drag and Drop Support (Java 2 Platform only) Provides the ability to drag and drop between a Java application and a native application. Swing is implemented without making use of native window elements components are painted by java on blank windows. more flexibility, not the lowest common denominator same look, same bugs in all platforms The Swing components will continue to be enhanced in the future, AWT is dying Swing, other differences Swing buttons and labels can display images instead of, or in addition to, text. You can easily add or change the borders drawn around most Swing components. For example, it's easy to put a box around the outside of a container or label. You can change the behavior or appearance of a Swing component by either invoking methods on it or creating a subclass of it. Swing components don't have to be rectangular. Buttons, for example, can be round. Assistive technologies such as screen readers can easily get information from Swing components. For example, a tool can easily get the text that's displayed on a button or label. Look & Feel Java look and feel CDE/Motif look and feel Windows look and feel Swing’s Handicaps? performance familiarity Programming forms Procedural programming code is executed in sequential order. statement statement statement --------------statement Event-driven programming code is executed upon activation of events. Do method 1 then method 3 method 1 method 2 method 3 --------------method n Do method 2 then method 1 then method 3 1 2 3 n procedural programming Up to now, our control flow model has been pretty straight-forward. Execution starts at main() and executes statement sequentially branching with if,for,and while statement, and occasional method calls. When we need user input we call read() on the console stream which waits (blocks) until the user types something, then returns. One problem with this model is: How do we wait for and respond to input from more than one source (eg keyboard and mouse). If we block on either waiting for input, we may miss input from the other. Event-driven programming the system waits for user input events, and these events trigger program methods Event-based programming addresses the two problems: How to wait on multiple input sources (input events) How to decide what to do on each type of input event General form of eventdriven programming The operating system and window system process input event from devices (mouse movement, mouse clicks, key presses etc) The window system decides which window, and hence which frame and program, each input event belongs to. A data structure describing the event is produced and placed on an 'event queue'. Events are added to one end of the queue by the window system . Events are removed from the queue and processed by the program. These event data structures hold information including: Which of the program's windows this event belongs to. The type of event (mouse click, key press, etc.) Any event-specific data (mouse position, key code, etc.) Event loop main(){ ...set up application data structures... ...set up GUI.... // enter event loop while(true){ Event e = get_event(); process_event(e); // event dispatch routine } } the event loop is usually hidden from programmer, he/she provides just a process_event() method Java Event Management java system has more control over event system, it directs events based on their type you only override behavior you are interested in. more details later… AWT Applications - Frame Frame is a container for components Optional Menu Three containers in Frame with Border Layout UI-components inside containers each with own layout Frame with normal window controls AWT classes AWTEvent Font FontMetrics Object Color Container Panel Applet Button Window Frame Label Dialog TextField TextComponent TextArea Graphics Component List Choice CheckBox LayoutManager CheckBoxGroup Canvas MenuComponent Scrollbar MenuItem MenuBar Menu FileDialog AWT & Swing classes AWTEvent Font Classes in the java.awt package LayoutManager Heavyweight 1 FontMetrics Object Color Panel Applet JApplet Window Frame JFrame Dialog JDialog Graphics Component Container * JComponent Swing Components in the javax.swing package Lightweight Swing - JComponents . JCheckBoxMenuItem AbstractButton JMenuItem JMenu JButton .JRadioButtonMenuItem .JToggleButton JCheckBox JRadioButton .JEditorPane JComponent .JTextComponent .JTextField .JPasswordField .JTextArea .JLabel .JList .JComboBox .JMenuBar .JPanel .JOptionPane .JScrollBar .JScrollPane .JPopupMenu .JSeparator .JSlider .JTabbedPane .JRootPane .JPane .JProgressBar .JToolBar .JSplitPane .JTable .JTree .JColorChooser .JInternalFrame .JToolTip .JLayeredPane .JTableHeader .JFileChooser Understanding the GUI UI-containers {label} Each UI-component {Frame} components {textfield} {button} have list of UI-components is a class with paint method & lists of Event listeners Setting up the GUI Extend Frame class In constructor • Create instances of containers & add them to Frame • Create instances of components & add them to containers or Frame Hint: Employ layout managers to arrange components in containers Possibly override paint method UI-components Painting added to components list Frame 1.paints Frame borders 2.calls Frame paint method 3.calls paint method of each object in component list A simple example import javax.swing.*; class FirstFrame extends JFrame { public FirstFrame() { setTitle("FirstFrame"); setSize(300, 200); } } public class FirstTest { public static void main(String[] args) { JFrame frame = new FirstFrame(); frame.show(); } } Simple Example Remarks we have two classes, one for the frame, the other for the main method that creates the frame we could have put the main method inside class FirstFrame if we don’t set the size, the default is a window of size 0x0 java runtime creates a new thread for our frame it doesn’t terminate, just hides Hello World Program import javax.swing.*; public class HelloWorldSwing { public static void main(String[] args) { JFrame frame = new JFrame("HelloWorld"); JLabel label = new JLabel("Hello World"); frame.getContentPane().add(label); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); frame.setBounds(200, 200, 200, 50); frame.setVisible(true); } } Containers Swing provides three generally useful top-level container classes: JFrame, JDialog, and JApplet. To appear onscreen, every GUI component must be part of a containment hierarchy. Each containment hierarchy has a top-level container as its root. Each top-level container has a content pane that, generally speaking, contains the visible components in that top-level container's GUI. You can optionally add a menu bar to a top-level container. The menu bar is positioned within the top-level container, but outside the content pane. Container Tree • a frame, or main window (JFrame) • a panel, sometimes called a pane (JPanel) •a button (JButton) •a label (JLabel) Containers .. The frame is a top-level container. It exists mainly to provide a place for other Swing components to paint themselves. The panel is an intermediate container. Its only purpose is to simplify the positioning of the button and label. Other intermediate Swing containers, such as scroll panes (JScrollPane) and tabbed panes (JTabbedPane), typically play a more visible, interactive role in a program's GUI. The button and label are atomic components -components that exist not to hold random Swing components, but as self-sufficient entities that present bits of information to the user. Often, atomic components also get input from the user. Layouts Using Layout Managers By default, every container has a layout manager. All JPanel objects use a FlowLayout by default, whereas content panes (the main containers in JApplet, JDialog, and JFrame objects) use BorderLayout by default. You can change the layout as: JPanel pane = new JPanel(); pane.setLayout(new BorderLayout()); When you add components to a panel or a content pane, the arguments you specify to the add method depend on the layout manager that the panel or content pane is using. Border Layout A border layout lays out a container, arranging and resizing its components to fit in five regions: north, south, east, west, and center. Each region may contain no more than one component, and is identified by a corresponding constant: NORTH, SOUTH, EAST, WEST, and CENTER. When adding a component to a container with a border layout, use one of these five constants, for example: Panel p = new Panel(); p.setLayout(new BorderLayout()); p.add(new Button("Okay"), BorderLayout.SOUTH); Border Layout Example Container contentPane = getContentPane(); //Use the content pane's default BorderLayout. //contentPane.setLayout(new BorderLayout()); contentPane.add(new JButton("Button 1 (NORTH)"), BorderLayout.NORTH); contentPane.add(new JButton("2 (CENTER)"), BorderLayout.CENTER); contentPane.add(new JButton("Button 3 (WEST)"), BorderLayout.WEST); contentPane.add(new JButton("Long-Named Button 4 (SOUTH)"), BorderLayout.SOUTH); contentPane.add(new JButton("Button 5 (EAST)"), BorderLayout.EAST); Border Layout Output Border Layout cont. We specified the component as the first argument to the add method. For example: add(component, BorderLayout.CENTER) //preferred However, you might see code in other programs that specifies the component second. For example, the following are alternate ways of writing the preceding code: add(BorderLayout.CENTER, component) add("Center", component) //valid but error prone By default, a BorderLayout puts no gap between the components it manages. You can specify gaps (in pixels) using the following constructor: BorderLayout(int horizontalGap, int verticalGap) You can also use the following methods to set the horizontal and vertical gaps, respectively: void setHgap(int) void setVgap(int) BorderLayout(20, 20) Border Layout Resizing First North and South are drawn with their normal sizes, then east & west, finally all the rest of the space is reserved for center. center is the default location by default anything in center fill resize to fill the space Border Layout Resized Flow Layout FlowLayout puts components in a row, sized at their preferred size. If the horizontal space in the container is too small to put all the components in one row, FlowLayout uses multiple rows. Within each row, components are centered (the default), left-aligned, or right-aligned as specified when the FlowLayout is created. Flow Layout Example Container contentPane = getContentPane(); contentPane.setLayout(new FlowLayout()); contentPane.add(new contentPane.add(new contentPane.add(new contentPane.add(new Button 4")); contentPane.add(new JButton("Button 1")); JButton("2")); JButton("Button 3")); JButton("Long-Named JButton("Button 5")); Flow Layout Example Output The FlowLayout API The FlowLayout class has three constructors: public FlowLayout() public FlowLayout(int alignment) public FlowLayout(int alignment, int horizontalGap, int verticalGap) The alignment argument must have the value FlowLayout.LEFT, FlowLayout.CENTER, or FlowLayout.RIGHT. The horizontalGap and verticalGap arguments specify the number of pixels to put between components. If you don't specify a gap value, FlowLayout uses 5 for the default gap value. Grid Layout A GridLayout places components in a grid of cells. Each component takes all the available space within its cell, and each cell is exactly the same size. If you resize the GridLayout window, it changes the cell size so that the cells are as large as possible, given the space available to the container. Grid Layout Example Container contentPane = getContentPane(); contentPane.setLayout(new GridLayout(0,2)); contentPane.add(new JButton("Button 1")); contentPane.add(new JButton("2")); contentPane.add(new JButton("Button 3")); contentPane.add(new JButton("Long-Named Button 4")); contentPane.add(new JButton("Button 5")); The constructor tells the GridLayout class to create an instance that has two columns and as many rows as necessary. Grid Output The GridLayout API The GridLayout class has two constructors: public GridLayout(int rows, int columns) public GridLayout(int rows, int columns, int horizontalGap, int verticalGap) At least one of the rows and columns arguments must be nonzero. The horizontalGap and verticalGap arguments to the second constructor allow you to specify the number of pixels between cells. If you don't specify gaps, their values default to zero. GridBagLayout GridBagLayout is the most flexible and complex one A GridBagLayout places components in a grid of rows and columns, allowing specified components to span multiple rows or columns. Not all rows necessarily have the same height. Similarly, not all columns necessarily have the same width. Essentially, GridBagLayout places components in rectangles (cells) in a grid, and then uses the components' preferred sizes to determine how big the cells should be. see http://www.interex.org/pubcontent/enterprise/jul00/08chew.html for a nice description Setting Up … GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); JPanel pane = new JPanel(); pane.setLayout(gridbag); //For each component to be added to this container: //...Create the component... //...Set instance variables in the GridBagConstraints instance... gridbag.setConstraints(theComponent, c); pane.add(theComponent); GridBagConstraints gridx, gridy Specify the row and column at the upper left of the component. gridwidth, gridheight Specify the number of columns (for gridwidth) or rows (for gridheight) in the component's display area. fill Used when the component's display area is larger than the component's requested size to determine whether and how to resize the component. Valid values are NONE (the default), HORIZONTAL (make the component wide enough to fill its display area horizontally, but don't change its height), VERTICAL and BOTH. ipadx, ipady Specifies the internal padding: how much to add to the minimum size of the component. The default value is zero. The width of the component will be at least its minimum width plus ipadx*2 pixels, the height of the component will be at least its minimum height plus ipady*2 pixels. insets Specifies the external padding of the component -- the minimum amount of space between the component and the edges of its display area. By default, each component has no external padding. anchor Used when the component is smaller than its display area to determine where (within the area) to place the component. Valid values (defined as GridBagConstraints constants) are CENTER (the default), NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, and NORTHWEST. weightx, weighty Weights are used to determine how to distribute space among columns (weightx) and among rows (weighty); this is important for specifying resizing behavior. GridBagLayout Example All ipadx = 0 fill = HORIZONTAL Button 1 ipady = 0 weightx = 0.5 weighty = 0.0 gridwidth = 1 anchor = CENTER insets = new Insets(0,0,0,0) gridx = 0 gridy = 0 Button 2 weightx = 0.5 gridx = 1 gridy = 0 Button 3 weightx = 0.5 gridx = 2 gridy = 0 Button 4 ipady = 40 weightx = 0.0 gridwidth = 3 gridx = 0 gridy = 1 Button 5 ipady = 0 weightx = 0.0 weighty = 1.0 anchor = SOUTH insets = new Insets(10,0,0,0) gridwidth = 2 gridx = 1 gridy = 2 Horizontal Fill fill = HORIZONTAL expands components to maximum horizontally, but normal vertically fill=NONE fill=HORIZONTAL Weights all the components expand equally horizontally, since their weightx are equal only button 5’s area expand vertically, since other’s weighty’s are 0 Ipad & inset ipad values grow the component itself, whereas insets grow the area while keeping the component same size fill = HORIZONTAL makes buttons wider than they should be, but only button 4 with a nonzero ipady value is higher than normal. Why Layouts? Can use Absolute Layout Layouts provide flexibility in different environments with different screen resolutions. Font sizes, component contents may change (internationalization) Using Netbeans IDE Events & Event Handling Event cause (mouse, keyboard, timer, …) Event Source object Event Object Event listener object (executes method) Example… User clicks on a button Button is source of event object Event object passed to associated listener object Listener object executes associated method to perform desired task (save file, quit program, …) Setting up Event Handling Create listener class Using new or existing class, simply Implement desired event listener interface Putting code for desired action in its methods In application (e.g. Frame) Create instance of listener class Add as listener of source object • can have any number of listeners for each event • Source & listener can be same object! Understanding Events When button is pressed {label} {Frame} components WindowListeners {textfield} windowClosing actionPerformed ActionListeners actionPerformed method of every item in button’s actionListeners list called Similarly for textfield When Frame close button is pressed windowClosing method of every item in Frame’s windowListeners list called {MyListener} {button} ActionListeners actionPerformed Event Classes EventObject AWTEvent ListSelectionEvent ActionEvent ContainerEvent AdjustmentEvent FocusEvent ComponentEvent InputEvent ItemEvent PaintEvent TextEvent WindowEvent MouseEvent KeyEvent Event Examples User clicks a button, presses Return while typing in a text field, or chooses a menu item ActionListener User closes a frame (main window) WindowListener User presses a mouse button while the cursor is over a component MouseListener User moves the mouse over a component MouseMotionListener Component becomes visible ComponentListener Component gets the keyboard focus FocusListener Table or list selection changes ListSelectionListener How to Implement an Event Handler In the declaration for the event handler class, specify that the class either implements a listener interface or extends a class that implements a listener interface. For example: public class MyClass implements ActionListener { Register an instance of the event handler class as a listener upon one or more components. For example: someComponent.addActionListener(instanceOfMyClass) Implement the methods in the listener interface. For example: public void actionPerformed(ActionEvent e) { ...//code that reacts to the action... } public class MC extends JFrame implements ActionListener { private int noClicks = 0; private JButton button = new JButton("click me !"); private JTextArea textArea = new JTextArea(5, 40); private JLabel label = new JLabel("Here you can see the number of button clicks") ; private JProgressBar progressBar = new JProgressBar(JProgressBar.HORIZONTAL); public MC(String title) { super(title); Container cp = getContentPane(); cp.setLayout(new BoxLayout(cp, BoxLayout.Y_AXIS)); cp.add(button); JScrollPane scrollPane = new JScrollPane(textArea); cp.add(scrollPane);cp.add(label); cp.add(progressBar); button.addActionListener(this); } Action Example Cont. public void actionPerformed(ActionEvent e) { noClicks++; textArea.append("Button clicked " + noClicks + " times so far \n"); label.setText("You clicked " + noClicks + " times"); progressBar.setValue(noClicks); } } public static void main(String[] args) { MyClass frame = new MyClass("Simple Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } Listener Class Flavors public class MyClass implements MouseListener { ... someObject.addMouseListener(this); ... public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseClicked(MouseEvent e) { ...//Event handler implementation goes here... }} Adapter Classes Java comes with a variety of adapter classes that implement Listener interfaces but have empty implementations. One can extend an adapter class and only override events that are of interest Adapter Example An example of extending an adapter class instead of directly implementing a listener interface. public class MyClass extends MouseAdapter { ... x.addMouseListener(this); ... public void mouseClicked(MouseEvent e) { //Event handler implementation // goes here... }} Using an Inner class … What if your class has to extend another one? public class MyClass extends Applet { ... x.addMouseListener(new MyAdapter()); ... class MyAdapter extends MouseAdapter { public void mouseClicked(MouseEvent e) { ...//Event handler implementation // goes here... } } } Using an Anonymous Inner class public class MyClass extends Applet { ... x.addMouseListener( new MouseAdapter() { public void mouseClicked(MouseEvent e) { ...//Event handler impl. } }); ... } } an inner class can refer to instance variables and methods just as if its code is in the containing class Example class PanelJ2 extends JPanel { public PanelJ2() { JButton b = new JButton( "OK"); setLayout( new BorderLayout() ); setBackground( Color.red); add( b, BorderLayout.SOUTH); b.addActionListener( new ExampleActionListener()); } } // restores screen after window has been covered, minimized, public void paintComponent( Graphics g) { super.paintComponent( g); g.drawRect( 50, 50, 200, 100); g.drawString( "Hello", 125, 100 ); } Action Listener class ExampleActionListener implements ActionListener { public void actionPerformed( ActionEvent e) { System.out.println( "Button Pressed"); } } Putting 3 of PanelJ2 … class MyJFrame extends JFrame { } public MyJFrame() { Container c = getContentPane(); c.setLayout( new FlowLayout() ); c.add( new PanelJ2()); c.add( new PanelJ2()); c.add( new PanelJ2()); setTitle( "ExampleJ3 - Hi there"); setBounds( 100, 150, 300, 300); setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); setVisible(true); } Outcome … Put them inside an applet public class AppletExample extends Applet { public void init() { setLayout( new FlowLayout() ); add( new PanelJ2()); add( new PanelJ2()); add( new PanelJ2()); } } Applet outcome Which button ? Let’s say you have three buttons on your application. How can you tell which button was pressed? We need something distinct about the event object to decide what to do We can use getSource() method ButtonPanel class ButtonPanel extends JPanel implemnts ActionListener{ public ButtonPanel() { yellowButton = new JButton("Yellow"); blueButton = new JButton("Blue"); redButton = new JButton("Red"); add(yellowButton); add(blueButton); add(redButton); } } yellowButton.addActionListener(this); blueButton.addActionListener(this); redButton.addActionListener(this); private JButton yellowButton, blueButton, redButton; actionPerformed method public void actionPerformed(ActionEvent evt){ Object source = evt.getSource(); Color color = getBackground(); if (source == yellowButton) color = Color.yellow; else if (source == blueButton) color = Color.blue; else if (source == redButton) color = Color.red; setBackground(color); repaint(); } An alternative? We used the same listener object for all the buttons (ButtonPanel itself) Another approach would be to use different listeners who knows about what they should do when the event happens ColorAction inner class private class ColorAction implements ActionListener { public ColorAction(Color c) { backgroundColor = c; } public void actionPerformed(ActionEvent event) { setBackground(backgroundColor); } private Color backgroundColor; } New constructor … public ButtonPanel() { // create buttons JButton yellowButton = new JButton("Yellow"); . . . // add buttons to panel add(yellowButton); add(blueButton); add(redButton); // create button actions ColorAction yellowAction = new ColorAction(Color.YELLOW); ColorAction blueAction = new ColorAction(Color.BLUE); ColorAction redAction = new ColorAction(Color.RED); } // associate actions with buttons yellowButton.addActionListener(yellowAction); blueButton.addActionListener(blueAction); redButton.addActionListener(redAction); ColorAction, another aproach class ColorAction implements ActionListener { public ColorAction(Color c, JPanel p) { backgroundColor = c; buttonPanel = p; } public void actionPerformed(ActionEvent event) { buttonPanel.setBackground(backgroundColor); } private Color backgroundColor; private JPanel buttonPanel; } The program .. Mouse Listener Example public class MouseMove extends JFrame { private Rectangle box; private int x,y; } public static void main(String[] args) { MouseMove m = new MouseMove(); } public MouseMove() { super("Mouse Demonstration"); Container cp=getContentPane(); JPanel mousePanel = new MousePanel(); mousePanel.setPreferredSize(new Dimension(300, 300)); cp.add(mousePanel); pack(); setVisible(true); } class MousePanel extends JPanel { int x,y; public MousePanel() { addMouseListener( new MouseAdapter() { public void mousePressed(MouseEvent event) { x=event.getX(); y=event.getY(); System.out.println(event); repaint(); } } ); } } public void paintComponent(Graphics g) { super.paintComponent(g); g.drawString("You clicked here", x, y); } Design Tips GUI code can get very messy Do not put everything in one class (as many Visual IDE’s do) Quick & dirty = impossible to change! Employ design patterns, e.g. MVC Think Design first... MVC - Design Pattern View View Multiple Views View 14:30 Half past two notify read model hours: 14 mins: 30 secs: 25 visual update content update controller receives user interface events 1 sec. timer Reset MVC . . . Model : The core of the application. This maintains the state and data that the application represents. When significant changes occur in the model, it updates all of its views Controller : The user interface presented to the user to manipulate the application. View : The user interface which displays information about the model to the user. Any object that needs information about the model needs to be a registered view with the model. Observer/Observable Java contains a framework to implement a system where a model is observed by a set of observers. The Observable class represents an observable object, or "data" in the model-view paradigm. It can be subclassed to represent an object that the application wants to have observed. An observable object can have one or more observers. An observer may be any object that implements interface Observer. After an observable instance changes, an application calling the Observable's notifyObservers method causes all of its observers to be notified of the change by a call to their update method. Observable class void addObserver(Observer o) Adds an observer to the set of observers for this object, provided that it is not the same as some observer already in the set. protected void setChanged() Marks this Observable object as having been changed; the hasChanged method will now return true. void notifyObservers() If this object has changed, as indicated by the hasChanged method, then notify all of its observers and then call the clearChanged method to indicate that this object has no longer changed. interface Observer public void update(Observable o, Object arg) This method is called whenever the observed object is changed. An application calls an Observable object's notifyObservers method to have all the object's observers notified of the change. Parameters: • o - the observable object. • arg - an argument passed to the notifyObservers method. Clock Example, Time Model public class Time3 extends Observable { private int hour, minute, second; //setters, getters, constructors omitted public void tick() { second = ( second + 1 ) % 60; if ( second == 0 ) { minute = (minute + 1 ) % 60; if ( minute == 0 ) hour = ( hour + 1 ) % 24; } setChanged(); notifyObservers(); } Clock View (partial code) public class ClockView extends JPanel implements Observer { Time3 t; public ClockView( Time3 time) { t = time; t.addObserver( this); … } public void update( Observable o, Object arg) { repaint(); } public void paintComponent( Graphics g) { super.paintComponent(g); g.drawOval(2, 2, 95, 95); double hoursAngle = 2 * Math.PI * ( t.getHour() - 3 ) / 12; g.drawLine( 50, 50, 50 + (int) (25 * Math.cos( hoursAngle)), 50 + (int) (25 * Math.sin( hoursAngle)) ); } Digital View public class DigitalView extends JLabel implements Observer { Time3 t; public DigitalView( Time3 t) { … this.t = t; t.addObserver( this); } } public void update(Observable o, Object arg) { setText( t.toUniversalString()); repaint(); } Clock Panel (partial) public class Clock extends JPanel { public Clock() { theTime = new Time3(); clockView1 = new ClockView( theTime); clockView2 = new DigitalView( theTime); add( clockView1, CENTER); add( clockView2, SOUTH); timer = new Timer( 1000, new TimerActionListener() ); timer.start(); } Time3 theTime; Timer timer; ClockView clockView1; DigitalView clockView2; } private class TimerActionListener implements ActionListener { public void actionPerformed ( ActionEvent e) { theTime.tick(); } } An Application public class Example1 extends JFrame { public Example1() { Container c = getContentPane(); c.setLayout( new FlowLayout() ); Clock tc1 = new Clock(); c.add( tc1); Clock tc2 = new Clock(); c.add( tc2); } } setBounds( 100, 150, 300, 250); setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); setVisible(true); Output … MVC in Swing ButtonModel model = button.getModel(); ButtonUI ui = button.getUI(); model and ui The model The behavior of the model is captured by the ButtonModel interface. A button model instance encapsulates the internal state of a single button and defines how the button behaves. Its methods can be grouped into four categories -- those that: Query internal state Manipulate internal state Add and remove event listeners Fire events The view and controller The behavior of the view and controller are captured by the ButtonUI interface. Classes that implement this interface are responsible for both creating a button's visual representation and handling user input provided via the keyboard and mouse. Its methods can be grouped into three categories -- those that: Paint Return geometric information Handle AWT events interface ButtonModel This model is used for check boxes and radio buttons, as well as for normal buttons. For check boxes and radio buttons, pressing the mouse selects the button. For normal buttons, pressing the mouse "arms" the button. Releasing the mouse over the button then initiates a button press, firing its action event. Releasing the mouse elsewhere disarms the button. In use, a UI will invoke setSelected(boolean) when a mouse click occurs over a check box or radio button. It will invoke setArmed(boolean) when the mouse is pressed over a regular button and invoke setPressed(boolean) when the mouse is released. If the mouse travels outside the button in the meantime, setArmed(false) will tell the button not to fire when it sees setPressed. (If the mouse travels back in, the button will be rearmed.) A button is triggered when it is both "armed" and "pressed". Plugging in Custom model Let’s say we have a button model that just toggles the state, called ToggleButtonModel. We can designate this new type of model to our ordinary JButton like: button.setModel(new toggleButtonModel(button)); Changing UI class Given a new UI class FancyButtonUI, we can change the look of our button as: button.setUI(new FancyButtonUI(button)); Results MVC Example class Circles extends JFrame implements ActionListener { public Circles() { radius = new JTextField( 5); radius.addActionListener( this); } // convert given radius to circumference or vice versa public void actionPerformed( ActionEvent e) { DecimalFormat fmt = new DecimalFormat( "0.##"); } } if ( e.getSource() == radius) { double newRadius = Double.parseDouble( radius.getText() ); double newCircumference = 2 * 3.142 * newRadius; circumference.setText( fmt.format( newCircumference) ); } else { double newCircumference = Double.parseDouble( circumference.getText() ); double newRadius = newCircumference / 2 / 3.142; radius.setText( fmt.format( newRadius) ); } Separate Circle logic class Circle { double radius, circumference; public Circle( double r){ setRadius( r); } public void setRadius( double r) { radius = r; circumference = 2 * Math.PI * r; } public double getRadius() { return radius; } public void setCircumference( double c) { circumference = c; radius = circumference / 2 / 3.142; } public double getCircumference() { return circumference; } } Circle logic separated class Circles extends JFrame implements ActionListener { Circle aCircle; public Circles( Circle circleObject) { aCircle = circleObject; } } // convert given radius to circumference or vice versa public void actionPerformed( ActionEvent e) { if ( e.getSource() == radius) { double newRadius = Double.parseDouble( radius.getText() ); aCircle.setRadius( newRadius); circumference.setText( fmt.format( aCircle.getCircumference() ) ); } else … } Model aware of view class Circle { double radius, circumference; Circles panel; } public Circle( double r){ setRadius( r); } public void setUI( Circles panel) { this.panel = panel; } public void setRadius( double r) { radius = r; circumference = 2 * Math.PI * r; if (panel != null) panel.update(); } public double getRadius() { return radius;} public void setCircumference( double c) { …} public double getCircumference() {return circumference;} class Circles extends JFrame implements ActionListener { Circle aCircle; public Circles( Circle circleObject) { aCircle = circleObject; aCircle.setUI( this); } // convert given radius to circumference or vice versa public void actionPerformed( ActionEvent e) { } } if ( e.getSource() == radius) { double newRadius = Double.parseDouble( radius.getText() ); aCircle.setRadius( newRadius); } else . . . public void update() { DecimalFormat fmt = new DecimalFormat( "0.##"); circumference.setText( fmt.format( aCircle.getCircumference() ) ); radius.setText( fmt.format( aCircle.getRadius() )); } Using Observer framework class Circle extends Observable { double radius, circumference; public Circle( double r){ setRadius( r); } public void setRadius( double r) { radius = r; circumference = 2 * Math.PI * r; setChanged(); notifyObservers(); } public double getRadius() {return radius;} public void setCircumference( double c) {… } public double getCircumference() {return circumference;} } Using Observable class Circles extends JFrame implements ActionListener, Observer { Circle aCircle; public Circles( Circle circleObject) { aCircle = circleObject; aCircle.addObserver( this); } // convert given radius to circumference or vice versa public void actionPerformed( ActionEvent e) { } } if ( e.getSource() == radius) { double newRadius = Double.parseDouble( radius.getText() ); aCircle.setRadius( newRadius); } else . . . public void update( Observable o, Object arg) { Circle oCircle = (Circle) o; DecimalFormat fmt = new DecimalFormat( "0.##"); circumference.setText( fmt.format( oCircle.getCircumference() ) ); radius.setText( fmt.format( oCircle.getRadius() )); }