meeting -*- outline -*- objectives: give an examples of subclassing, subtyping and polymorphism, show how to use the Eclipse editor/IDE, show basics of building a GUI in Java using Swing. * User Interface Example One of the historically most important uses of inheritance and polymorphism is in the user interface frameworks (GUIs, window systems). The first (and most widely used) practical way to build GUIs was with these techniques. This was done first in Smalltalk-80, and then in Flavors (which became CLOS), and then there were frameworks like ET++ for C++, Motif, etc. Java's (AWT and) Swing UI frameworks continue this tradition. ** Writing GUIs in Java Java has two frameworks for GUIs: the AWT and Swing. Swing looks the same on all platforms, and is probably better debugged (same bugs everywhere at least). So: use Swing (instead of the AWT) unless you know why you shouldn't (Beware older books that use the AWT as opposed to Swing) As a reference for these notes, I used the book: "Core Java 2 Volume 1 -- Fundamentals" by Cay S. Horstmann and Gary Cornell (Sun Microsystems Press, 2001). This has the advantage that it really covers Swing well. Also recommended is Kathy Walrath and Mary Campione's book "The JFC Swing Tutorial: A Guide to Constructing GUIs" (Addison-Wesley, Reading, MA, 1999). This is available online from: http://java.sun.com/docs/books/tutorial/uiswing/index.html See also the on-line training center at java.sun.com: http://developer.java.sun.com/developer/onlineTraining/GUI/ See also the demos that ship with the J2SDK *** plan We're going to evolve a Swing GUI for the calculator We want it to look like the following (good to sketch it like this before coding!): See the ui-use-cases.txt for user-interface oriented use cases. ------------------------------------------ INITIAL GUI SKETCH |-------------------------------------| | Formula-Register Calculator | |-------------------------------------| | [ 0.0 ] | |-------------------------------------| | Named Formulas | | | | [ add0 ][\/] [ r1 + r2] (Eval) | | | |-------------------------------------| | Registers | | | | r0 [ 0.0] r1 [ 0.1] | | r2 [ 2.2] r3 [ 0.3] | | r4 [ 0.4] r5 [ 0.5] | | r6 [ 60.6] r7 [ 0.7] | | r8 [ 0.8] r9 [ 0.9] | | | |-------------------------------------| | Formula Composition | | | | First Second | | Register Operator Register | | [ r0][\/] [ +][\/] [ r1][\/] | | | | Name [ ] ( Save ) | | ( Reset ) | |-------------------------------------| ------------------------------------------ where (Eval) is a button, [...][\/] is an item choice, and [ ] is a text display or text box. The rest are labels. *** Infrastructure, building the Frame In Eclipse, make a package gui, and add a class Calculator to be the main program. **** main program ------------------------------------------ package gui; import javax.swing.JFrame; /** The main program for the calculator. * @author Gary T. Leavens */ public class Calculator { /** * Create a calculator frame and show it. */ public static void main(String[] args) { CalculatorFrame frame = new CalculatorFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } } ------------------------------------------ Note that you have to (a) create a frame, and then (b) call it's show method. Otherwise it won't appear. The closing stuff might be changed in a real program (ask to save state, etc.) **** JFrame Now we have to create the most basic part of the application the JFrame that holds the rest of it. It's recommended to not have the main class itself be the JFrame, ------------------------------------------ package gui; import javax.swing.*; /** A frame for the Calculator. * @author Gary T. Leavens */ public class CalculatorFrame extends JFrame { /** * Initialize this frame. */ public CalculatorFrame() { setTitle("Formula-Register Calculator"); setSize(WIDTH, HEIGHT); } /** The width of this frame, in pixels. */ public static final int WIDTH = 500; /** The height of this frame, in pixels. */ public static final int HEIGHT = 600; } ------------------------------------------ Try this out, by running it in Eclipse. Note: the rest of the code (below) goes inside the constructor of CalculatorFrame **** Layout The most flexible layout is a GridBagLayout. It's like a spreadsheet. ------------------------------------------ GRIDBAG LAYOUT RECIPE (From "Core Java 2, Volume 1", p. 519) 1. Sketch the layout on paper 2. Find a grid such that the smallest components fit inside single cells. 3. Label rows 0, 1, 2,..., and columns 0, 1, 2, ... from top left. The gridx, gridy are coordinates gridwidth, gridheight sizes of components (>1 if span cells) 4. Decide on fill (horizontal?, vertical? both?), and anchor (alignment) if not filled 5. Set weights to 100, unless you want components in a row or column to stay at their default size (use 0 for them). 6. Write code and double check constraints 7. Compile, run, and enjoy. ------------------------------------------ We'll use a grid of width 7, and we'll count the rows as we go. weights tell how much a component can grow or shrink relative to others Note: rows and columns can have different sizes. A weight of 0 says it can't grow or shrink. ------------------------------------------ STARTING A GRIDBAG LAYOUT Container contentPane = getContentPane(); GridBagLayout layout = new GridBagLayout(); contentPane.setLayout(layout); Then - create the components, and - for each component: a. set its constraints b. add it to the contentPane A sample: // textBox for output JTextField outputText = new JTextField(25); outputText.setEditable(false); outputText.setHorizontalAlignment(JTextField.RIGHT); // add components to the grid GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.CENTER; constraints.gridx = 0; constraints.gridy = 0; constraints.gridwidth = 7; constraints.gridheight = 1; contentPane.add(outputText, constraints); ------------------------------------------ *** Other stuff you can do it, just look it up Java provides a ton of stuff in Swing, it just has to be learned... For example, I wanted to do all this, and didn't know how... **** borders ------------------------------------------ MOTTO: YOU CAN DO IT YOU JUST HAVE TO LOOK UP HOW Example: - Make the output text border blue - give it a title Border lined = BorderFactory .createLineBorder(Color.BLUE); Border outputTitle = BorderFactory .createTitledBorder(lined, "Output"); outputText.setBorder(outputTitle); ------------------------------------------ Note this is an example of polymorphism (and Factory pattern) **** a helper method for adding components: ------------------------------------------ A HELPER METHOD FOR ADDING COMPONENTS /** * Add the given component to the given grid bag layout. * This simplifies otherwise tedious code. See Core Java 2, * Volume 1, pp. 521-522. * @param c the component to add * @param constraints the grid bag constraints object to use * @param x the gird x position * @param y the grid y position * @param w the grid width (columns spanned) * @param h the grid height (rows spanned) */ private void add(Component c, GridBagConstraints constraints, int x, int y, int w, int h) { constraints.gridx = x; constraints.gridy = y; constraints.gridwidth = w; constraints.gridheight = h; getContentPane().add(c, constraints); } ------------------------------------------ Use of the type Component is another example of polymorphism *** Adding the main labels: ------------------------------------------ JLabel nfLabel = new JLabel("Named Formulas"); JLabel regsLabel = new JLabel("Registers"); JLabel fcLabel = new JLabel("Formula Composition"); // add components to the grid add(nfLabel, constraints, 0, 1, 7, 1); add(regsLabel, constraints, 0, 3, 7, 1); add(fcLabel, constraints, 0, 9, 7, 1); ------------------------------------------ Try this. *** Adding text fields and labels for registers ------------------------------------------ for (int i = 0; i < 10; i++) { regLabels[i] = new JLabel("r" + i); regTexts[i] = new JTextField(25); regTexts[i].setEditable(true); regTexts[i].setHorizontalAlignment(JTextField.RIGHT); Border rb = BorderFactory.createLineBorder(Color.BLACK); regTexts[i].setBorder(rb); } for (int i = 0; i < 10; i = i + 2) { constraints.anchor = GridBagConstraints.EAST; int row = 4+i/2; add(regLabels[i], constraints, 0, row, 1, 1); add(regLabels[i+1], constraints, 4, row, 1, 1); constraints.anchor = GridBagConstraints.WEST; constraints.fill = GridBagConstraints.NONE; add(regTexts[i], constraints, 1, row, 2, 1); add(regTexts[i+1], constraints, 5, row, 2, 1); } ------------------------------------------ try this, I had to increase the horizontal size... Try screwing up this by changing the last i+1 to i... *** Adding listeners for the text fields Action events can be generated when user presses enter, but if they forget, want to do something when focus is lost (See Core Java, p. 434). So need to make the listener respond to both focus lost and action events. This means we need to define a class that implements these interfaces ------------------------------------------ package gui; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import javax.swing.JTextField; import calc.Registers; /** Listener for the registers. * These keep track of a textfield and a register number, * and they put the contents of the text field into the register. * @author Gary T. Leavens */ public class RegisterInputListener implements FocusListener, ActionListener { /** The text field we listen to. */ private JTextField tf; /** The register we put the input into. */ private int regNum; /** * Initialize this RegisterInputListener to listen to events * on the given JTextField and output to the given register. * @param tf the text field * @param regNum the number of the register */ //@ requires tf != null && 0 <= regNum; public RegisterInputListener(JTextField tf, int regNum) { this.tf = tf; this.regNum = regNum; } /** Done when the user moves focus away. */ public void focusLost(FocusEvent e) { Registers.put(regNum, parseDoubleFrom(tf)); } /** Done when the user types the enter key. */ public void actionPerformed(ActionEvent e) { Registers.put(regNum, parseDoubleFrom(tf)); } /** Parse a Double from the given JTextField */ private double parseDoubleFrom(JTextField t) { try { return Double.parseDouble(t.getText().trim()); } catch (NumberFormatException e) { return Double.NaN; } } /** What is done when focus is gained. */ public void focusGained(FocusEvent e) { } } ------------------------------------------ Note this is an example of polymorphism. Then we can change the loop in CalculatorFrame to add a listener to each text field: ------------------------------------------ for (int i = 0; i < 10; i++) { /* ... */ regTexts[i] = new JTextField(25); /* ... */ // Add Listener for events on this text field RegisterInputListener ril = new RegisterInputListener(regTexts[i], i); regTexts[i].addActionListener(ril); regTexts[i].addFocusListener(ril); } ------------------------------------------ Explain what happens now when you press enter or change focus. Change it so that when focus is put on, the color of the border changes (to green, say), and then when focus is lost it changes to black. *** Code cleanup Look for duplications, e.g., the numbers 10 and 25, repeated code, etc. Use import ...*; forms