Continuation From Last Time 0/106 A Third Swing App: ColorPanelApp JFrame ● Specification: An app with three sliders that the user can drag to change the RGB values of the background color ● Useful classes: JFrame, JPanel, JSlider, ChangeListener JPanel JSlider 1/102 DEMO: ColorPanelApp 2/102 Using JSliders ● A JSlider lets the user graphically select a value by sliding an arrow within a bounded interval ● One way to construct is by passing two ints (minimum and maximum values) as arguments: JSlider slider = new JSlider(0, 30); ● To get the value of the slider, we can use the getValue method ● A JSlider emits ChangeEvents when value changes-to listen for them, we use a ChangeListener 3/102 ChangeListeners ChangeListener ChangeEvent e JSlider public void stateChanged(ChangeEvent e) { // Respond to ChangeEvent here! // (print something to the console, tell // a JLabel to update its text, etc.) } 4/102 Process: ColorPanelApp JFrame 1. Create top-level class that contains a JFrame 2. Write a subclass of JPanel that contains three JSliders, add an instance of it to the JFrame 3. Write a ChangeListener that changes a JPanel’s color based on value of a JSlider 4. Add ChangeListeners to the JSliders JPanel JSlider 5/102 Top-level Class: ColorPanelApp public class ColorPanelApp { ● This top-level class is almost identical to those of the other examples-- we follow same pattern of setting up JFrame public ColorPanelApp() { JFrame frame = new JFrame(); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } ● Instantiate JFrame, set its close operation, call pack and setVisible public static void main(String[] args) { new ColorPanelApp(); } } 6/102 Process: ColorPanelApp JFrame 1. Create top-level class that contains a JFrame 2. Write a subclass of JPanel that contains three JSliders, add an instance of it to the JFrame 3. Write a ChangeListener that changes a JPanel’s color based on value of a JSlider 4. Add ChangeListeners to the JSliders JPanel JSlider 7/102 JPanel Subclass: ColorPanel public class ColorPanel extends JPanel { ● ColorPanel “is a” JPanel public ColorPanel() { Dimension panelSize = new Dimension(300, 150); this.setPreferredSize(panelSize); this.setBackground(Color.GRAY); JSlider sliderRed = new JSlider(0, 255); JSlider sliderGreen = new JSlider(0, 255); JSlider sliderBlue = new JSlider(0, 255); ● As in other examples, create a Dimension, give it desired width and height, then call setPreferredSize ● Want panel to start out gray, so call method setBackground this.add(sliderRed); this.add(sliderGreen); this.add(sliderBlue); } } 8/102 JPanel Subclass: ColorPanel ● Instantiate three JSliders: one to control each channel (red, green, and blue) public class ColorPanel extends JPanel { public ColorPanel() { Dimension panelSize = new Dimension(300, 150); this.setPreferredSize(panelSize); this.setBackground(Color.GRAY); JSlider sliderRed = new JSlider(0, 255); JSlider sliderGreen = new JSlider(0, 255); JSlider sliderBlue = new JSlider(0, 255); ● Arguments are beginning/end of slider’s range: we give each range 0-255 (one byte/RGB channel) ● Finally, add each slider to panel this.add(sliderRed); this.add(sliderGreen); this.add(sliderBlue); } } 9/102 Adding a ColorPanelApp to the JFrame public class ColorPanelApp { public ColorPanelApp() { JFrame frame = new JFrame(); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); ● Just as we did in previous examples, first instantiate a panel, then call add to add it to the JFrame ColorPanel panel = new ColorPanel(); frame.add(panel); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { new ColorPanelApp(); } } 10/102 Process: ColorPanelApp JFrame 1. Create top-level class that contains a JFrame 2. Write a subclass of JPanel that contains three JSliders, add an instance of it to the JFrame 3. Write a ChangeListener that changes a JPanel’s color based on value of a JSlider 4. Add ChangeListeners to the JSliders JPanel JSlider 11/102 Changing a JPanel’s Color ● Every JPanel has a pair of accessor and mutator methods, setBackground and getBackground ● We have three JSliders-- one to control each color channel of the Jpanel. Each Jslider has its own listener ● When red slider’s listener detects that its slider has been moved: o it calls getBackground to ask the panel for its current color o it creates a new instance of Color, using the “R” value from the slider and the “G” and “B” values from the panel’s current background color (since they haven’t changed) o it calls setBackground, passing in the new Color 12/102 Our ChangeListener: SliderListener ● A SliderListener’s job is to change the color of a JPanel based on the value of a JSlider public class SliderListener implements ChangeListener { ● Since JSliders emit ChangeEvents whenever their value changes, SliderListener implements ChangeListener interface ● Defines method stateChanged. SliderListener will be coded generically so it can be used for each slider private JPanel _panel; private int _channel; private JSlider _slider; public SliderListener(JPanel panel, int channel, JSlider slider){ _panel = panel; _channel = channel; _slider = slider; } @Override public void stateChanged(ChangeEvent e) { // implementation elided for now } } 13/102 Our ChangeListener: SliderListener ● To do its job, it needs to know three things: public class SliderListener implements ChangeListener { private JPanel _panel; private int _channel; private JSlider _slider; o The JPanel whose color it should change public SliderListener(JPanel panel, int channel, JSlider slider){ _panel = panel; _channel = channel; _slider = slider; } o Which channel (red, green, or blue) to modify o Which slider it listens to ● We’ll represent the channel as an int: 0 means red, 1 means green, 2 means blue @Override public void stateChanged(ChangeEvent e) { // implementation elided for now } } 14/102 The stateChanged method ● // rest of class SliderListener elided for now @Override public void stateChanged(ChangeEvent e) { Method is called whenever val = _slider.getValue(); value of the listener’s JSlider int Color bg = _panel.getBackground(); Color newbg; changes ● Should modify the appropriate color channel of the JPanel’s background color ● Must take in a ChangeEvent as a parameter, because it is required by the ChangeListener interface; doesn’t use it in this case } switch(_channel){ case 0: newbg = new Color(val, bg.getGreen(), bg.getBlue()); break; case 1: newbg = new Color(bg.getRed(), val, bg.getBlue()); break; default: newbg = new Color(bg.getRed(), bg.getGreen(), val); } _panel.setBackground(newbg); 15/106 The stateChanged method // rest of class SliderListener elided for now @Override public void stateChanged(ChangeEvent e) { int val = _slider.getValue(); Color bg = _panel.getBackground(); Color newbg; ● Need to determine new value of JSlider ● Since we know the source is the JSlider, we can retrieve value by calling getValue on _slider } switch(_channel){ case 0: newbg = new Color(val, bg.getGreen(), bg.getBlue()); break; case 1: newbg = new Color(bg.getRed(), val, bg.getBlue()); break; default: newbg = new Color(bg.getRed(), bg.getGreen(), val); } _panel.setBackground(newbg); 16/106 The stateChanged method // rest of class SliderListener elided for now @Override public void stateChanged(ChangeEvent e) { int val = _slider.getValue(); Color bg = _panel.getBackground(); Color newbg; ● Next, we get the JPanel’s current background color ● switch(_channel){ case 0: newbg = new Color(val, bg.getGreen(), We declare a new Color, newBg, bg.getBlue()); break; to which we’ll assign a different case 1: value based on which color newbg = new Color(bg.getRed(), val, bg.getBlue()); channel the listener is supposed break; to change default: newbg = new Color(bg.getRed(), bg.getGreen(), val); } _panel.setBackground(newbg); 17/106 } The stateChanged method ● Depending on the value of _channel, we set newBg to different color // rest of class SliderListener elided for now @Override public void stateChanged(ChangeEvent e) { int val = _slider.getValue(); Color bg = _panel.getBackground(); a Color newbg; ● In each case, only the specified color channel changes; we use the gets for the values of the unchanged channels ● Once we’ve changed the appropriate channel, we set the JPanel’s background color to newBg } switch(_channel){ case 0: newbg = new Color(val, bg.getGreen(), bg.getBlue()); break; case 1: newbg = new Color(bg.getRed(), val, bg.getBlue()); break; default: newbg = new Color(bg.getRed(), bg.getGreen(), val); } _panel.setBackground(newbg); 18/106 Process: ColorPanelApp JFrame 1. Create top-level class that contains a JFrame 2. Write a subclass of JPanel that contains three JSliders, add an instance of it to the JFrame 3. Write a ChangeListener that changes a JPanel’s color based on value of a JSlider 4. Add ChangeListeners to the JSliders JPanel JSlider 19/106 Setting up our SliderListeners public class ColorPanel extends JPanel { ● Back in ColorPanel class public ColorPanel() { Dimension panelSize = new Dimension(300, 150); this.setPreferredSize(panelSize); this.setBackground(Color.GRAY); JSlider sliderRed = new JSlider(0, 255); JSlider sliderGreen = new JSlider(0, 255); JSlider sliderBlue = new JSlider(0, 255); sliderRed.addChangeListener( new SliderListener(this, 0, sliderRed)); sliderGreen.addChangeListener( new SliderListener(this, 1, sliderGreen)); sliderBlue.addChangeListener( new SliderListener(this, 2, sliderBlue)); this.add(sliderRed); this.add(sliderGreen); this.add(sliderBlue); } ● Call method addChangeListener on each slider, passing in a new instance of SliderListener ● Pass in ColorPanel, appropriate channel number and slider as arguments to each SliderListener } 20/106 The Whole App (1/2) public class ColorPanel extends JPanel { public class ColorPanelApp { public ColorPanel() { Dimension panelSize = new Dimension(300, 150); this.setPreferredSize(panelSize); this.setBackground(Color.GRAY); JSlider sliderRed = new JSlider(0, 255); JSlider sliderGreen = new JSlider(0, 255); JSlider sliderBlue = new JSlider(0, 255); public ColorPanelApp() { JFrame frame = new JFrame(); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); ColorPanel panel = new ColorPanel(); frame.add(panel); sliderRed.addChangeListener( new SliderListener(this, 0, sliderRed)); sliderGreen.addChangeListener( new SliderListener(this, 1, sliderGreen)); sliderBlue.addChangeListener( new SliderListener(this, 2, sliderBlue)); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { new ColorPanelApp(); } this.add(sliderRed); this.add(sliderGreen); this.add(sliderBlue); } } } 21/106 The Whole App (2/2) @Override public void stateChanged(ChangeEvent e) { int val = _slider.getValue(); Color bg = _panel.getBackground(); Color newbg; switch(_channel){ case 0: newbg = new Color(val, bg.getGreen(), bg.getBlue()); break; case 1: newbg = new Color(bg.getRed(), val, bg.getBlue()); break; default: newbg = new Color(bg.getRed(), bg.getGreen(), val); } _panel.setBackground(newbg); } public class SliderListener implements ChangeListener { private JPanel _panel; private int _channel; public SliderListener(JPanel panel, int channel){ _panel = panel; _channel = channel; } } 22/106 Putting It All Together: ColorPanelApp ColorPanelApp JFrame ColorPanel JSlider SliderListener 23/106 Buttons in Swing Some buttons in Swing: ● javax.swing.JButton ● javax.swing.JToggleButton ● javax.swing.JCheckBox ● javax.swing.JRadioButton 24/106 Buttons in Swing 25/106 ButtonGroups ● How do we make options mutually exclusive (i.e. you can only click one button at a time)? o o put them in a button group javax.swing.ButtonGroup ● Buttons that can be added to ButtonGroup: JRadioButton JToggleButton JMenuItem (for advanced users) 26/106 JRadioButtons & ButtonGroups ● How to use a ButtonGroup: o create buttons JRadioButton rB1 = new JRadioButton(“Green, not Red”); JRadioButton rB2 = new JRadioButton(“Red, not Green”); o create ButtonGroup ButtonGroup bGroup = new ButtonGroup(); o add buttons to ButtonGroup bGroup.add(rB1); bGroup.add(rB2); 27/106 Modified ColorTextApp: RadioColorListener public class RadioColorListener implements ActionListener { private JLabel _label; private Color _currentColor public RadioColorListener(JLabel label, Color color) { _label = label; _currentColor = color; } @Override public void actionPerformed(ActionEvent e) { _label.setForeground(_currentColor); } } 28/106 Modified ColorTextApp: ColorTextPanel public class ColorTextPanel extends JPanel { public ColorTextPanel() { JLabel label = new JLabel("CS15 Rocks!"); Assume all the swing JRadioButton buttonForRed = new JRadioButton("Red"); components are JRadioButton buttonForGreen = new JRadioButton(“Green); imported! RadioColorListener redListener = new RadioColorListener(label, Color.RED); RadioColorListener greenListener = new RadioColorListener(label, Color.GREEN); //add listeners to their buttons buttonForRed.addActionListener(redListener); buttonForGreen.addActionListener(greenListener); //create button group and add buttons to it ButtonGroup buttonGroup = new ButtonGroup(); buttonGroup.add(buttonForRed); buttonGroup.add(buttonForGreen); Dimension panelSize = new Dimension(300, 100); this.setPreferredSize(panelSize); For more complex projects you might want to give the Buttons a separate JPanel and use LayoutManagers (stay tuned!) //add label and buttons to ColorTextPanel this.add(label); this.add(buttonForRed); this.add(buttonForGreen); } } 29/106 More Complicated Swing Apps ● The examples we’ve seen so far have been small and simple ● What if we want to construct a more realistic GUI? ● We can use LayoutManagers to arrange components nicely within JPanels 30/106 Using java.awt.LayoutManagers ● Each JPanel contains a LayoutManager to automatically arrange its sub-components ● LayoutManager interface implemented by classes: o java.awt.FlowLayout o java.awt.GridLayout o java.awt.BorderLayout ● To specify the LayoutManager for a JPanel, either pass an instance of one of these classes to the panel’s constructor or to its setLayout method later 31/106 FlowLayout ● Default layout used by all JPanels ● Arranges components from left to right, top to bottom ● When it reaches the edge of the panel, moves to next row 32/106 FlowLayout FlowLayout fl = new Flowlayout(); mainPanel.setLayout(fl); mainPanel.add(b1); mainPanel.add(b2); mainPanel.add(b3); mainPanel.add(b4); mainPanel.add(b5); mainPanel.add(b6); this.add(mainPanel); this.pack(); this.setVisible(true); public class FlowTest extends JFrame { public FlowTest() { super(); this.setSize(200, 300); JButton b1 = new JButton(“One”); JButton b2 = new JButton(“Two”); JButton b3 = new JButton(“Tree”); JButton b4 = new JButton(“Four”); JButton b5 = new JButton(“Five”); JButton b6 = new JButton(“Six”); JPanel mainPanel = new JPanel(); } } 33/106 GridLayout ● Arranges components in a grid ● Add components left to right, top to bottom ● Must specify grid size in constructor-- number of rows and columns ● Each cell in grid is same size, determined by largest element in grid 34/106 GridLayout p1.setBackground(Color.GREEN); p2.setBackground(Color.RED); p3.setBackground(Color.ORANGE); p4.setBackground(Color.CYAN); p5.setBackground(Color.BLUE); p6.setBackground(Color.YELLOW); public class GridTest extends JFrame { public GridTest() { super(); this.setSize(200, 300); JPanel p1 = new JPanel(); JPanel p2 = new JPanel(); JPanel p3 = new JPanel(); JPanel p4 = new JPanel(); JPanel p5 = new JPanel(); JPanel p6 = new JPanel(); JPanel mainPanel = new JPanel(); } GridLayout gl = new GridLayout(3, 2); mainPanel.setLayout(gl); mainPanel.add(p1); mainPanel.add(p2); mainPanel.add(p3); mainPanel.add(p4); mainPanel.add(p5); mainPanel.add(p6); this.add(mainPanel); this.pack(); this.setVisible(true); 35/106 BorderLayout ● Splits JPanel into 5 regions: NORTH, SOUTH, EAST, WEST, CENTER o These are all static constants ● Must specify region when adding component to its container 36/106 BorderLayout ● Don’t have to fill all regions-- default size of a region is (0, 0) ● BorderLayout will adjust dynamically depending on what has been added ● Typically add to CENTER first-- it should be largest 37/106 BorderLayout p1.setBackground(Color.RED); p2.setBackground(Color.GREEN); p3.setBackground(Color.ORANGE); p4.setBackground(Color.CYAN); p5.setBackground(Color.BLUE); public class BorderTest extends JFrame { public BorderTest() { super(); this.setSize(300, 200); JPanel p1 = new JPanel(); JPanel p2 = new JPanel(); JPanel p3 = new JPanel(); JPanel p4 = new JPanel(); JPanel p5 = new JPanel(); JPanel mainPanel = new JPanel(); BorderLayout bl = new BorderLayout(); mainPanel.setLayout(bl); mainPanel.add(p1, BorderLayout.CENTER); mainPanel.add(p2, BorderLayout.NORTH); mainPanel.add(p3, BorderLayout.SOUTH); mainPanel.add(p4, BorderLayout.EAST); mainPanel.add(p5, BorderLayout.WEST); this.add(mainPanel); this.pack(); this.setVisible(true); } } 38/106 LayoutManagers ● You’ll be using layouts a lot in CS15-- practice playing around with them! ● Try taking the previous example and calling setPreferredSize on each sub-panel to change their sizes and shapes ● If you want to test your understanding (and want to get a headstart on Cartoon), try making something like this: 39/106 LayoutManagers 40/106 Swing: Summary ● Swing has a variety of tools to help you make GUIs: o o o JFrames, JPanels, JButtons, and JSliders, arranged using LayoutManagers ActionListeners and ChangeListeners to respond to user input Timers to perform a task at regular intervals ● On future projects, instead of relying on support code for graphical elements, you will use these tools yourself! 41/106 Lecture 10 Build Your Own Custom Graphics 42/106 Creating Custom Graphics ● Last lecture, we introduced you to Swing ● Lots of handy widgets for making your own graphical applications! ● What if you want to create and display your own custom graphics? ● This lecture: build your own graphics using TA-written Shape package and work them into Swing applications 43/106 java.awt.geom.RectangularShapes ● Swing provides built-in classes to represent a small amount of geometric information for 2D shapes – they can’t even draw themselves! ● Subclasses of RectangularShape (Rectangle2D, Ellipse2D, etc.) know how to do a few basic things: o e.g., set and get their current size and graphical position 44/106 The Problem ● We need way more functionality than built-in RectangularShapes provide! ● Want all shapes to be able to: o Set and get current color o Have a border of user-specified width and color o Rotate o Know how to paint themselves on the screen! 45/106 The Solution: Shape ● The TAs have written the Shape package to provide you with “smart shapes” that have all these capabilities and more! ● You’ll be using Shape subclasses to create beautiful graphics for the rest of the semester 46/106 Shape Package • To use the TAs’ Shape package, import it using import cs015.prj.Shape.*; • Make your own subclass by extending the Shape class in the Shape package and passing a java.awt.geom.RectuangularShape into super’s constructor. 47/106 Shape Class Hierarchy Shape ● Shape package also contains specific shapes: e.g., RectangleShape (don’t confuse with RectangularShape) and RectangleShape EllipseShape EllipseShape ● Subclasses of abstract Shape class ● All Shapes keep track of a basic set of properties for which they have accessors and mutators 48/106 Accessors and mutators of Shapes Border Location public double getX(); public double getY(); public void setLocation(double x, double y); public int getBorderWidth(); public void setBorderWidth(int width); Size public double getWidth(); public double getHeight(); public void setSize(double width, double height); Visibility public boolean getVisible(); public void setVisible(boolean visible); Rotation public double getRotation(); public void setRotation(double degrees); public public public public // set public Color Color getBorderColor(); Color getFillColor(); void setBorderColor(Color c); void setFillColor(Color c); both border and fill to same color void setColor(Color c); 49/106 Pixels and Coordinate System (0, 0) ● The screen is a grid of pixels (tiny dots) X Y ● Cartesian plane with: o origin in upper-left corner o y-axis increasing downward o corresponds to writing order pixels 50/106 Pixels and Coordinate System location ● Set and get location of shapes using setLocation, getX, and getY ● Location of any shape is upper-left corner of its bounding box location bounding box location location 51/106 paintComponent: Drawing on JPanels ● How are components drawn on the screen? ● JComponents (like JPanels and JButtons) can paint themselves with Swing’s paintComponent method o you won’t have to call this method yourself - Swing does this automatically ● Also have other methods that automatically take care of painting any Swing components that were added to them ● Remember using the add method to add JButtons to a JPanel? 52/106 paintComponent: Drawing on JPanels ● Chain of command: 1. Swing automatically tells frame to paint itself 2. frame automatically tells contained panel to paint itself 3. panel paints itself (e.g., its background color) with paintComponent, then automatically tells contained button to paint itself public class PaintingExample { public PaintingExample() { JFrame frame = new JFrame(); JPanel panel = new JPanel(); JButton button = new JButton(); // setting sizes, etc. elided panel.add(button); frame.add(panel); frame.pack(); frame.setVisible(true); } } 53/106 Painting Custom Shapes ● Whenever a JPanel is told to paint itself, paintComponent is called first-- this takes care of painting the panel itself ● Next, another method automatically paints all Swing components that have been added to the panel using the add method ● But the custom shapes we want to paint aren’t Swing components-- they won’t be painted automatically for us, alas 54/106 Overriding paintComponent ● To paint custom shapes on a JPanel, create a subclass of JPanel that partially overrides paintComponent ● This way, we can add to the paintComponent method so it does more than just painting the background of the JPanel ● After painting the JPanel as normal, can add code to the method to specifically tell shapes to paint themselves 55/106 Example: MovingShape ● Spec: App with central panel that displays a shape, and a slider that moves the shape back and forth on the panel 56/106 DEMO: MovingShape 57/106 Process: MovingShape 1. Write top-level class that contains a JFrame and write a constants class to keep track of important values 2. Create MainPanel, a JPanel subclass, to serve as top-level panel, and add an instance to the JFrame 3. Create another JPanel subclass to serve as the ShapePanel, and add an instance to the MainPanel 4. Write a ChangeListener that tells the associated ShapePanel to move the shape, and add an instance to the Jslider (an implicit association) MovingShapeApp MainPanel JSlider JFrame ShapePanel ChangeListener Shape Replace this with subclass of Shape ShapePanel MainPanel JSlider 58/106 Top-level Class: MovingShapeApp ● Starts out exactly like examples from last lecture ● Instantiate JFrame public class MovingShapeApp { public MovingShapeApp() { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ); frame.pack(); frame.setVisible(true); ● Specify its closing behavior } ● Tell it to resize to fit contents public static void main(String[] args) { new MovingShapeApp(); } ● Make sure it’s visible } 59/106 Constants Class ● Constants class keeps track of a few important numbers ● For this app, width and height of the shape and of the panel it sits in public class Constants { public static final int public static final int public static final int public static final int } PANEL_W PANEL_H SHAPE_W SHAPE_H = = = = 500; 500; 100; 100; ● Units: pixels 60/106 Process: MovingShape 1. Write top-level class that contains a JFrame and write a constants class to keep track of important values 2. Create JPanel subclass to serve as top-level panel, and add an instance to the JFrame 3. Create another JPanel subclass to serve as the shape panel, and add an instance to the top-level panel 4. Write a ChangeListener that tells the shape panel to move the shape, and add an instance to the Jslider MovingShapeApp MainPanel JSlider ShapePanel JFrame ChangeListener Shape ShapePanel MainPanel JSlider 61/106 JPanel Subclass: MainPanel public class MainPanel extends JPanel { ● We know we want main panel to contain a special JPanel with a shape in it, and a JSlider to move the shape public MainPanel() { this.add(shapePanel, BorderLayout.CENTER); } } ● First, we’ll set up the slider 62/106 JPanel Subclass: MainPanel ● Want slider’s value to represent x-coordinate of shape on panel public class MainPanel extends JPanel { ● Thus, max value (of left top corner) is panel width - shape width PANEL_W public MainPanel() { ShapePanel shapePanel = new ShapePanel(); int sliderMax = Constants.PANEL_W - Constants.SHAPE_W; JSlider slider = new JSlider(0, sliderMax); SHAPE_W PANEL_W - SHAPE_W } } ● Instantiate new slider and pass in min/max values as arguments 63/106 ShapePanel public class ShapePanel extends JPanel { /* we’ll fill this class in later, in particular adding the ellipse */ ● We’re not adding any functionality to the ShapePanel until later public ShapePanel(){ ● Adding incomplete, placeholder components like this (to the MainPanel, on next slide) is a form of incremental development } o working incrementally can be very useful for debugging! } 64/106 JPanel Subclass: MainPanel ● Give MainPanel a BorderLayout ● ShapePanel will be added to CENTER position ● Place our JSlider in SOUTH position public class MainPanel extends JPanel { public MainPanel() { ShapePanel shapePanel = new ShapePanel(); int sliderMax = Constants.PANEL_W-Constants.SHAPE_W; JSlider slider = new JSlider(0, sliderMax); ShapePanel slider.addChangeListener(listener); (CENTER) this.setLayout(new BorderLayout()); this.add(shapePanel, BorderLayout.CENTER); this.add(slider, BorderLayout.SOUTH); MainPanel } } JSlider (SOUTH) 65/106 Adding a MainPanel to our JFrame public class MovingShapeApp { ● Just like in examples from last lecture, first instantiate a MainPanel, then call add to add it to the JFrame public MovingShapeApp() { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); MainPanel mainPanel = new MainPanel(); frame.add(mainPanel); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { new MovingShapeApp(); } } 66/106 Process: MovingShape 1. Write top-level class that contains a JFrame and write a constants class to keep track of important values 2. Create JPanel subclass to serve as top-level panel, and add an instance to the JFrame 3. Create another JPanel subclass to serve as the shape panel, and add an instance to the top-level panel 4. Write a ChangeListener that tells the shape panel to move the shape, and add an instance to the JSlider MovingShapeApp MainPanel JSlider ShapePanel JFrame ChangeListener Shape ShapePanel MainPanel JSlider 67/106 Finishing ShapePanel ● Now we’ll fill in the class we created earlier as a placeholder public class ShapePanel extends JPanel { private Shape _shape; public ShapePanel() { Dimension dim = new Dimension(Constants.PANEL_W, Constants.PANEL_H); this.setPreferredSize(dim); this.setBackground(Color.WHITE); _shape = new EllipseShape(); _shape.setSize(Constants.SHAPE_W, Constants.SHAPE_H); _shape.setColor(Color.RED); int startX = (Constants.PANEL_W - Constants.SHAPE_W) / 2; int startY = (Constants.PANEL_H - Constants.SHAPE_H) / 2; _shape.setLocation(startX, startY); } ● A ShapePanel “is a” JPanel ● Will keep track of a single instance variable: a Shape named _shape /* Other methods elided for now */ } 68/106 ShapePanel ● First, specify preferred size of panel using a Dimension as we saw in last lecture’s examples ● Set background color of panel to WHITE ● Initialize _shape and set its size and color (using constants we defined for size) public class ShapePanel extends JPanel { private Shape _shape; public ShapePanel() { Dimension dim = new Dimension(Constants.PANEL_W, Constants.PANEL_H); this.setPreferredSize(dim); this.setBackground(Color.WHITE); _shape = new EllipseShape(); _shape.setSize(Constants.SHAPE_W, Constants.SHAPE_H); _shape.setSolor(Color.RED); int startX = (Constants.PANEL_W - Constants.SHAPE_W) / 2; int startY = (Constants.PANEL_H - Constants.SHAPE_H) / 2; _shape.setLocation(startX, startY); } } /* Other methods elided for now */ 69/106 ShapePanel ● Calculate proper starting location for _shape (want its center to start in center of panel) and call setLocation public class ShapePanel extends JPanel { private Shape _shape; public ShapePanel() { Dimension dim = new Dimension(Constants.PANEL_W, Constants.PANEL_H); this.setPreferredSize(dim); this.setBackground(Color.WHITE); _shape = new EllipseShape(); _shape.setSize(Constants.SHAPE_W, Constants.SHAPE_H); _shape.setSolor(Color.RED); int startX = (Constants.PANEL_W - Constants.SHAPE_W) / 2; int startY = (Constants.PANEL_H - Constants.SHAPE_H) / 2; _shape.setLocation(startX, startY); } (PANEL_H - SHAPE_H) / 2 /* Other methods elided for now */ (PANEL_W - SHAPE_W) / 2 } 70/106 ShapePanel public class ShapePanel extends JPanel { ● Constructor is done-- still need to take care of painting and moving shape private Shape _shape; /* Constructor elided for now */ ● Override Jpanel’s paintComponent method to make our Shape show up ● It takes in a parameter of type java.awt.Graphics ● Think of this parameter as a paintbrush-- it’s used to draw things on the panel @Override public void paintComponent(Graphics g) { } } 71/106 ShapePanel public class ShapePanel extends JPanel { ● First, we call the superclass’s paintComponent method, passing in the instance of Graphics we received from Swing -- the “paintbrush” private Shape _shape; ● This takes care of painting any Swing components that may have been added to the panel with the add method @Override public void paintComponent(Graphics g) { super.paintComponent(g); /* Constructor elided for now */ } } 72/106 ShapePanel public class ShapePanel extends JPanel { private Shape _shape; ● Now need to tell _shape to paint itself /* Constructor elided for now */ ● A Shape has all necessary information to paint itself (color, size, etc.) ● To tell _shape to paint itself, we call its paint method, passing in a Graphics, the same “paintbrush” that paintComponent took in @Override public void paintComponent(Graphics g) { super.paintComponent(g); _shape.paint(g); } } 73/106 ShapePanel public class ShapePanel extends JPanel { ● We’ve taken care of making sure the shape shows up on the panel private Shape _shape; /* Constructor elided for now */ ● Now, panel needs to be able to move its shape to a different location when given an int value (x coordinate) ● This method will be called by the slider’s ChangeListener whenever the slider is moved public void moveShape(int xPos) { } @Override public void paintComponent(Graphics g) { super.paintComponent(g); _shape.paint(g); } } 74/106 ShapePanel ● Whenever moveShape is called, first move the shape by calling its setLocation method, which takes in the x and y coordinates of the new location public class ShapePanel extends JPanel { private Shape _shape; /* Constructor elided for now */ public void moveShape(int xPos) { _shape.setLocation(xPos, _shape.getY()); this.repaint(); } ● Change x-coordinate to value passed in, keep ycoordinate the same ● Next, make sure panel visually updates by calling repaint @Override public void paintComponent(Graphics g) { super.paintComponent(g); _shape.paint(g); } } 75/106 Aside: repaint and paintComponent ● Why do we call repaint and not paintComponent? public class ShapePanel extends JPanel { private Shape _shape; ● paintComponent is only the first step in the painting process of a JPanel-- other painting methods take care of painting the border and any Swing components added to the panel ● repaint will call all of these methods in the proper order, to ensure that everything visually updates correctly – see next slide /* Constructor elided for now */ public void moveShape(int xPos) { _shape.setLocation(xPos, _shape.getY()); this.repaint(); } Why not paintComponent? @Override public void paintComponent(Graphics g) { super.paintComponent(g); _shape.paint(g); } } 76/106 Painting Custom Graphics: Summary 1. Someone (e.g., moveShape) calls Swing’s repaint on the JPanel _panel.repaint(); 2. repaint creates an instance of Graphics (the paintbrush) and calls paintComponent, passing in the paintbrush as an argument _panel 3. paintComponent, // in JPanel subclass public void paintComponent(Graphics g) { which has been super.paintComponent(g); partially overridden, _shape.paint(g); calls the paint } method on the Shape(s) we want to display // in Shape class public void paint(Graphics g) { // TA code to display shape here } _shape 4. Shape’s paint uses information about the shape’s size, color, and location to draw shape on screen 77/106 Adding a ShapePanel to the MainPanel ● Instantiate a ShapePanel in MainPanel’s constructor ● Add ShapePanel to MainPanel in CENTER layout position public class MainPanel extends JPanel { public MainPanel() { ShapePanel shapePanel = new ShapePanel(); int sliderMax = Constants.PANEL_W-Constants.SHAPE_W; JSlider slider = new JSlider(0, sliderMax); this.setLayout(new BorderLayout()); this.add(shapePanel, BorderLayout.CENTER); this.add(slider, BorderLayout.SOUTH); } } 78/106 Process: MovingShape 1. Write top-level class that contains a JFrame and constants class to keep track of important values 2. Create JPanel subclass to serve as top-level panel, and add an instance to the JFrame 3. Create another JPanel subclass to serve as the shape panel, and add an instance to the top-level panel 4. Write a ChangeListener that tells the shape panel to move the shape, and add an instance to the JSlider MovingShapeApp MainPanel JSlider ShapePanel JFrame ChangeListener Shape ShapePanel MainPanel JSlider 79/106 Our ChangeListener: SliderListener ● Our SliderListener’s job is to tell the ShapePanel to do something whenever its JSlider’s value changes public class SliderListener implements ChangeListener { private ShapePanel _panel; private JSlider _slider; public SliderListener(ShapePanel panel, JSlider slider) { _panel = panel; _slider = slider; } ● In our case, want ShapePanel to move its shape ● First thing we do is set up an association with a ShapePanel and a JSlider } 80/106 Our ChangeListener: SliderListener ● stateChanged method is called whenever slider’s value changes public class SliderListener implements ChangeListener { ● Next, call the ShapePanel’s moveShape method, passing in the JSlider’s current value as an argument ● Whenever the slider is moved, the ChangeListener calls a method on the ShapePanel, which in turn calls a method } on its Shape private ShapePanel _panel; private JSlider _slider; public SliderListener(ShapePanel panel, JSlider slider) { _panel = panel; _slider = slider; } @Override public void stateChanged(ChangeEvent e) { _panel.moveShape(_slider.getValue()); } 81/106 Setting up a SliderListener public class MainPanel extends JPanel { ● In MainPanel’s constructor, instantiate a SliderListener, passing in the ShapePanel and the Slider as an argument ● Add the SliderListener to the JSlider public MainPanel() { ShapePanel shapePanel = new ShapePanel(); int sliderMax = Constants.PANEL_W-Constants.SHAPE_W; JSlider slider = new JSlider(0, sliderMax); SliderListener listener = new SliderListener(shapePanel, slider); slider.addChangeListener(listener); this.setLayout(new BorderLayout()); this.add(shapePanel, BorderLayout.CENTER); this.add(slider, BorderLayout.SOUTH); } } 82/106 Aside: Private Inner Classes ● So far, we’ve operated under the rule that every class we create belongs in its own .java file ● What we didn’t tell you: o Java actually allows you to define a class within another class! o called inner class or nested class ● Often write ActionListeners, ChangeListeners and other listener classes as inner classes, rather than in their own separate files 83/106 public class MainPanel { public MainPanel() { // rest of constructor elided... SliderListener listener = new SliderListener(shapePanel); // rest of constructor elided... } private class SliderListener implements ChangeListener { private ShapePanel _panel; private JSlider _slider; public SliderListener(ShapePanel panel, JSlider slider) { _panel = panel; _slider = slider; } outer class inner class @Override public void stateChanged(ChangeEvent e) { _panel.moveShape(_slider.getValue()); } } } 84/106 Why Use a Private Inner Class? ● Readability o If a class is only useful to one other class, using a private inner class keeps the code closer to where it is used ● Encapsulation o A private inner class can directly access the outer class’s private members-- stay tuned! o Inner class can be hidden from outside world 85/106 Private Inner Classes (Partial Example) ● Here, we demonstrate a private inner class directly accessing a private instance variable of its outer class ● Since _colorPanel is accessible from anywhere within ColorChangingApp, inner class Listener can call methods on it public class ColorChangingApp { private JPanel _colorPanel; public ColorChangingApp() { _colorPanel = new JPanel(); // rest of constructor elided } private class Listener implements ChangeListener { private JSlider _slider; public Listener(JSlider slider) { _slider=slider; } public void stateChanged(ChangeEvent e) { int value = _slider.getValue(); Color c = new Color(value, value, value); _colorPanel.setBackground(c); } } } 86/106 Creating Composite Shapes ● What if we want our DrawingPanel to display something more elaborate? ● Can make a composite shape by combining two or more shapes 87/106 Spec: MovingAlien ● Transform MovingShape into MovingAlien ● An alien should be displayed on the central panel, and should be moved back and forth by the JSlider 88/106 DEMO: MovingAlien 89/106 MovingAlien: Design ● We can create a class, Alien, to model our composite shape ● Define composite shape’s capabilities in Alien class Alien EllipseShape ● Alien could have method setLocation that positions each component (face, left eye, right eye) o delegation to the max EllipseShapes 90/106 Turning MovingShape into MovingAlien 1. Create the Alien class to model our composite shape, making sure it has all the capabilities we need it to have (like setLocation) 2. Modify ShapePanel to contain an Alien instead of a Shape 91/106 Alien public class private private private ● The Alien class is our composite shape Alien { EllipseShape _face; EllipseShape _leftEye; EllipseShape _rightEye; // constructor elided- should instantiate the // three ellipses, set their colors/sizes ● It contains three EllipseShapes-- one for the face, and one for each eye ● Constructor (elided) instantiates these ellipses and sets initial sizes/colors } 92/106 Alien public class private private private // constructor elided- should instantiate the // three ellipses, set their colors/sizes ● An Alien’s location is determined by top left corner of its face’s bounding box ● When setLocation is called, it sets _face to that location, and positions each eye at an appropriate offset Alien { EllipseShape _face; EllipseShape _leftEye; EllipseShape _rightEye; public void setLocation(double x, double y) { _face.setLocation(x, y); _leftEye.setLocation(x + 30, y + 20); _rightEye.setLocation(x + 55, y + 20); } } 93/106 Alien public class private private private // constructor elided- should instantiate the // three ellipses, set their colors/sizes ● Aliens need to know how to paint themselves so they can show up on JPanels public void setLocation(double x, double y) { _face.setLocation(x, y); _leftEye.setLocation(x + 30, y + 20); _rightEye.setLocation(x + 55, y + 20); } ● Alien’s paint method simply calls paint on each component EllipseShape ● Since our ShapePanel needs Alien’s current Y coordinate when calling setLocation on it, we define method getY, which returns _face’s Y coordinate Alien { EllipseShape _face; EllipseShape _leftEye; EllipseShape _rightEye; public void paint(Graphics g) { _face.paint(g); _leftEye.paint(g); _rightEye.paint(g); } public double getY() { return _face.getY(); } } 94/106 Turning MovingShape into MovingAlien 1. Create the Alien class to model our composite shape, making sure it has all the capabilities we need it to have (like setLocation) 2. Modify ShapePanel to contain an Alien instead of a Shape 95/106 ShapePanel public class ShapePanel extends JPanel { private Alien _alien; ● Only have to make a few basic changes to ShapePanel! ● Instead of knowing about a Shape called _shape, knows about an Alien called _alien ● Since the ShapePanel is the only object that deals directly with the Shape/Alien, don’t need to change any other classes! public ShapePanel() { /* set preferred size and color elided */ _alien = new Alien(); int startX = (Constants.PANEL_W-Constants.SHAPE_W)/2; int startY = (Constants.PANEL_H-Constants.SHAPE_H)/2; _alien.setLocation(startX, startY); } public void moveShape(int xPos) { _alien.setLocation(xPos, _alien.getY()); this.repaint(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); _alien.paint(g); } } 96/106 ShapePanel public class ShapePanel extends JPanel { private Alien _alien; ● Can easily swap in an Alien for the Shape-- already made sure Aliens can respond to all messages ShapePanel needs to send them ● We implemented setLocation, getY, and paint methods in the Alien class because we knew the ShapePanel would call them public ShapePanel() { /* set preferred size and color elided */ _alien = new Alien(); int startX = (Constants.PANEL_W-Constants.SHAPE_W)/2; int startY = (Constants.PANEL_H-Constants.SHAPE_H)/2; _alien.setLocation(startX, startY); } public void moveShape(int xPos) { _alien.setLocation(xPos, _alien.getY()); this.repaint(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); _alien.paint(g); } } 97/106 Building a Complex GUI ● What if we want to make a more powerful GUI that looks like this? ● Requires a few layers of Swing components and layouts 98/106 Graphical Containment ● Graphical containment describes which components visually contain other components ● Not always the same as logical containment 99/106 The Big Picture ● JFrame is at the top level ● JPanels, used with LayoutManagers, arrange graphical components nicely ● Add other components (JButtons, JSliders, etc.) to JPanels ● Can subclass JPanel and override paintComponent to display custom graphics 100/106 The Big Picture ● Certain Swing components (like JButtons, JSliders) and Timers emit events ● To respond to these events, we use listeners ● A listener always needs to know about the object it’s going to change (for example, a JLabel or a ShapePanel) 101/106 Your Next Project: Cartoon! ● You’ll be building a Swing application that displays your own custom “cartoon”, much like the examples in this lecture ● But your cartoon will be animated! 102/106 Your Next Project: Cartoon! ● How can we animate our cartoon (e.g. make him move across the screen)? ● As in film and video animation, can create apparent motion with many small changes in position ● If we move fast enough and in small enough increments, we get smooth motion ● Same goes for smoothly changing size, orientation, shape, etc. 103/106 Animation in Cartoon Tick! ● Use a Timer to create incremental change ● It’ll be up to you to figure out the details… but every time the Timer ticks, your cartoon should move a small amount 104/106 Cartoon Demos 105/106 Announcements • Cartoon is released today o DQs are due this Sunday (Oct 12) at 2:00 PM o Cartoon help session this Sunday at 6:00 PM, Barus & Holley 166 o Cartoon early hand in is Thursday Oct 16th (11:59 PM), on time hand in is Saturday Oct 18th (10:00 PM), late hand in is Monday Oct 20th (11:59 PM) 106/106