JSlider sliderRed = new JSlider(0, 255)

advertisement
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
Download