Last modified: Mar 21, 2014
The StringArt Program
The Process
The model is the underlying data that is being manipulated and portrayed by the program.
// The Model
private Color[] colors;
private int stepSize = 5;
Select the GUI elements to portray the model
There are two common approaches to laying out GUIs
Programmed Approach in Java
Uses a combination of
Container/nesting relationships
Layout managers
GUI Container Elements
GUI Elements in StringArt
Structural Summary
Where Did the Control Panel Come From?
Programming the Containment
GUI container elements are handled much like utility containers
public void createAndShowGUI() {
window = new JFrame(); ➊
//...
canvas = new JPanel () { ➋
//...
}
};
canvas.setBackground(Color.white);
window.getContentPane().add (canvas, BorderLayout.CENTER); ➌
canvas.setPreferredSize(new Dimension(400, 400));
JPanel controls = new JPanel(); ➍
colorChooser1 = new JButton("Color 1"); ➎
controls.add (colorChooser1);
//...
colorChooser2 = new JButton("Color 2");
controls.add (colorChooser2);
//...
stepSizeIn = new JTextField (""+stepSize, 5);
controls.add (stepSizeIn);
//...
window.getContentPane().add (controls, BorderLayout.SOUTH); ➏
//...
window.pack(); ➐
window.setVisible(true);
}
➊ Create the window
➋ Create the canvas
➌ Add the canvas to the window’s content pane
➍ Create the control panel
➎ Create the buttons and text field and add to the control panel
➏ Add the control panel to the window’s content pane
➐ Arrange the components of the GUI and display the window.
Laying out the components
Java has several layout managers.
These give us several options for how to arrange our components
Need to choose one for each container
Using Border Layout
public void createAndShowGUI() {
window = new JFrame();
// set up the components
window.getContentPane().setLayout (
new BorderLayout());
canvas = new JPanel () {
⋮
}
};
⋮
window.getContentPane().add (canvas,
BorderLayout.CENTER);
JPanel controls = new JPanel();
⋮
window.getContentPane().add (controls,
BorderLayout.SOUTH);
⋮
Miscellaneous Appearance Code
public void createAndShowGUI() {
window = new JFrame();
// set up the components
window.getContentPane().setLayout (new BorderLayout());
canvas = new JPanel () {
//...
}
};
canvas.setBackground(Color.white);
window.getContentPane().add (canvas, BorderLayout.CENTER);
canvas.setPreferredSize(new Dimension(400, 400)); ➊
JPanel controls = new JPanel();
colorChooser1 = new JButton("Color 1");
controls.add (colorChooser1);
setColor(colorChooser1, colors[0]);
colorChooser1.addActionListener (new ColorChooser(colorChooser1, 0));
colorChooser2 = new JButton("Color 2");
controls.add (colorChooser2);
setColor(colorChooser2, colors[1]);
colorChooser2.addActionListener (new ColorChooser(colorChooser2, 1));
stepSizeIn = new JTextField (""+stepSize, 5);
controls.add (stepSizeIn);
stepSizeIn.addActionListener (new ActionListener()
{
public void actionPerformed(ActionEvent e) {
try {
Integer newSize = new Integer(stepSizeIn.getText());
stepSize = newSize.intValue();
canvas.repaint();
} catch (Exception ex) {};
}
});
window.getContentPane().add (controls, BorderLayout.SOUTH);
window.setDefaultCloseOperation((startedInAnApplet) ? JFrame.DISPOSE_ON_CLOSE : JFrame.EXIT_ON_CLOSE);
window.pack();
window.setVisible(true);
}
/**
* Sets the background color of a button to the indicated color.
* Changes the foreground to wither black or white, depending on
* which will give more contrast agasint the new background.
*
* @param button
* @param color
*/
private void setColor(JButton button, Color color) { ➋
button.setBackground(color);
int brightness = color.getRed() + color.getGreen() + color.getBlue(); // max of 3*255
if (brightness > 3*255/2) {
// This is a fairly bright color. Use black lettering
button.setForeground (Color.black);
} else {
// This is a fairly dark color. Use white lettering
button.setForeground (Color.white);
}
}
Most of the highlighted code is self-explanatory
➊The canvas, a panel with no internal components, has no “natural” size and tends to shrink to \( 0\times 0 \) if we aren’t careful
➋This utility function sets the background color of a button to match the color that it selects, and makes sure that the foreground color of the text is visible in contrast.
We need to implement some behaviors
Closing the window shuts down the program
Entering a number in the text box changes the step size in the model
Clicking a color button pops up a color selection dialog and updates the model
Closing the Window
Here we use a shorthand technqiue rather than create a full-fledged functor to listen:
public void createAndShowGUI() {
window = new JFrame();
⋮
window.setDefaultCloseOperation(
(startedInAnApplet)
? JFrame.DISPOSE_ON_CLOSE
: JFrame.EXIT_ON_CLOSE);
⋮
}
Entering the Step Size
This is handled by an ActionListener, which is triggered when we hit Enter/Return with the cursor positioned in the text box.
stepSizeIn = new JTextField (""+stepSize, 5);
controls.add (stepSizeIn);
stepSizeIn.addActionListener (new ActionListener()
{
public void actionPerformed(ActionEvent e) {
try {
Integer newSize = new Integer(
stepSizeIn.getText());
stepSize = newSize.intValue();
⋮
} catch (Exception ex) {};
}
});
Color Selection
Buttons notify an ActionListener when a button is clicked
colorChooser1 = new JButton("Color 1");
controls.add (colorChooser1);
setColor(colorChooser1, colors[0]);
colorChooser1.addActionListener (
new ColorChooser(colorChooser1, 0));
colorChooser2 = new JButton("Color 2");
controls.add (colorChooser2);
setColor(colorChooser2, colors[1]);
colorChooser2.addActionListener (
new ColorChooser(colorChooser2, 1));
We implement this with our own subclass of ActionListener, ColorChooser.
ColorChooser
private class ColorChooser implements ActionListener {
private JButton button; ➊
private int colorNum;
public ColorChooser (JButton button, int colorNum) {
this.button = button; ➋
this.colorNum = colorNum;
}
@Override
public void actionPerformed(ActionEvent arg0) { ➌
Color chosen = JColorChooser.showDialog(window, "Choose a color",
colors[colorNum]);
if (chosen != null) {
colors[colorNum] = chosen; ➍
setColor (button, chosen);
canvas.repaint();
}
}
};
➊ Each ColorChooser object will remember which button it is attached to and which of the two colors it controls.
➋ That info is gathered and remembered in the constructor.
➌ Here is the function that we must provide if we are to implement ActionListener
The JColorChooser invoked in the first line pops up the stnadard Java color selection box.
The proper moment for drawing GUI elements is a bit tricky.
Think of some of the various situations in which all or part of a window needs to be redrawn:
The program concludes some kind of computation and needs to display the results (i.e., a program state change).
We had previously clicked on the minimize/iconify control to reduce the window to an icon, and now we click on the icon to open it back up to a full window.
The window was completely or partially hidden beneath other windows and we Alt-tab to bring it back up to the top.
We drag another window or other item across the front of window, continuously hiding and revealing different potions of the window as we do so.
Any of these can force part or all of an application to need redrawing. Some of these (state changes) are under direct program control. Most are not.
paint and repaint
Most GUI elements already take care of drawing themselves.
For elements with customized appearance:
canvas = new JPanel () {
public void paint (Graphics g) {
super.paint(g);
drawLines (g, getSize());
}
};
Paint and Repaint
We never call paint directly
Repainting the StringArt Canvas
private class ColorChooser implements ActionListener {
private JButton button;
private int colorNum;
public ColorChooser (JButton button, int colorNum) {
this.button = button;
this.colorNum = colorNum;
}
@Override
public void actionPerformed(ActionEvent arg0) {
Color chosen = JColorChooser.showDialog(window, "Choose a color", colors[colorNum]);
if (chosen != null) {
colors[colorNum] = chosen;
setColor (button, chosen);
canvas.repaint();
}
}
};
public void createAndShowGUI() {
window = new JFrame();
// set up the components
window.getContentPane().setLayout (new BorderLayout());
canvas = new JPanel () {
public void paint (Graphics g) {
super.paint(g);
drawLines (g, getSize());
}
};
canvas.setBackground(Color.white);
window.getContentPane().add (canvas, BorderLayout.CENTER);
canvas.setPreferredSize(new Dimension(400, 400));
JPanel controls = new JPanel();
colorChooser1 = new JButton("Color 1");
controls.add (colorChooser1);
setColor(colorChooser1, colors[0]);
colorChooser1.addActionListener (new ColorChooser(colorChooser1, 0));
colorChooser2 = new JButton("Color 2");
controls.add (colorChooser2);
setColor(colorChooser2, colors[1]);
colorChooser2.addActionListener (new ColorChooser(colorChooser2, 1));
stepSizeIn = new JTextField (""+stepSize, 5);
controls.add (stepSizeIn);
stepSizeIn.addActionListener (new ActionListener()
{
public void actionPerformed(ActionEvent e) {
try {
Integer newSize = new Integer(stepSizeIn.getText());
stepSize = newSize.intValue();
canvas.repaint();
} catch (Exception ex) {};
}
});
window.getContentPane().add (controls, BorderLayout.SOUTH);
window.setDefaultCloseOperation((startedInAnApplet) ? JFrame.DISPOSE_ON_CLOSE : JFrame.EXIT_ON_CLOSE);
window.pack();
window.setVisible(true);
}
The Full Listing
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
* A simple example of GUI event handling in a Java application.
*
* This can be run as a main program or as an applet.
*
* @author zeil
*
*/
public class StringArt extends JApplet {
private boolean startedInAnApplet;
// The Model
private Color[] colors;
private int stepSize = 5;
// The View & Controls
private JFrame window;
private JPanel canvas;
private JButton colorChooser1;
private JButton colorChooser2;
private JTextField stepSizeIn;
private class ColorChooser implements ActionListener {
private JButton button;
private int colorNum;
public ColorChooser (JButton button, int colorNum) {
this.button = button;
this.colorNum = colorNum;
}
@Override
public void actionPerformed(ActionEvent arg0) {
Color chosen = JColorChooser.showDialog(window, "Choose a color", colors[colorNum]);
if (chosen != null) {
colors[colorNum] = chosen;
setColor (button, chosen);
canvas.repaint();
}
}
};
public StringArt()
{
startedInAnApplet = false;
window = null;
colors = new Color[2];
colors[0] = Color.red;
colors[1] = Color.blue;
}
public static void main (String[] args)
{
StringArt instance = new StringArt();
instance.createAndShowGUI();
}
public void createAndShowGUI() {
window = new JFrame();
// set up the components
window.getContentPane().setLayout (new BorderLayout());
canvas = new JPanel () {
public void paint (Graphics g) {
super.paint(g);
drawLines (g, getSize());
}
};
canvas.setBackground(Color.white);
window.getContentPane().add (canvas, BorderLayout.CENTER);
canvas.setPreferredSize(new Dimension(400, 400));
JPanel controls = new JPanel();
colorChooser1 = new JButton("Color 1");
controls.add (colorChooser1);
setColor(colorChooser1, colors[0]);
colorChooser1.addActionListener (new ColorChooser(colorChooser1, 0));
colorChooser2 = new JButton("Color 2");
controls.add (colorChooser2);
setColor(colorChooser2, colors[1]);
colorChooser2.addActionListener (new ColorChooser(colorChooser2, 1));
stepSizeIn = new JTextField (""+stepSize, 5);
controls.add (stepSizeIn);
stepSizeIn.addActionListener (new ActionListener()
{
public void actionPerformed(ActionEvent e) {
try {
Integer newSize = new Integer(stepSizeIn.getText());
stepSize = newSize.intValue();
canvas.repaint();
} catch (Exception ex) {};
}
});
window.getContentPane().add (controls, BorderLayout.SOUTH);
window.setDefaultCloseOperation((startedInAnApplet) ? JFrame.DISPOSE_ON_CLOSE : JFrame.EXIT_ON_CLOSE);
window.pack();
window.setVisible(true);
}
/**
* Sets the background color of a button to the indicated color.
* Changes the foreground to wither black or white, depending on
* which will give more contrast agasint the new background.
*
* @param button
* @param color
*/
private void setColor(JButton button, Color color) {
button.setBackground(color);
int brightness = color.getRed() + color.getGreen() + color.getBlue(); // max of 3*255
if (brightness > 3*255/2) {
// This is a fairly bright color. Use black lettering
button.setForeground (Color.black);
} else {
// This is a fairly dark color. Use white lettering
button.setForeground (Color.white);
}
}
// Applet functions
public void init() {
startedInAnApplet = true;
}
public void start() {
if (window == null)
createAndShowGUI();
}
public void stop() {
}
public void destroy() {
}
int interpolate (int x, int y, int i, int steps)
{
return (i * x + (steps-i)*y) / steps;
}
Color interpolate(Color c1, Color c2, int i, int steps)
{
return new Color (interpolate(c1.getRed(), c2.getRed(), i, steps),
interpolate(c1.getGreen(), c2.getGreen(), i, steps),
interpolate(c1.getBlue(), c2.getBlue(), i, steps));
}
class Point {
double x;
double y;
}
Point ptOnCircle (int degrees, int radius, Point center)
{
Point p = new Point();
double theta = Math.toRadians((double)degrees);
p.x = center.x + (double)radius * Math.cos(theta);
p.y = center.y + (double)radius * Math.sin(theta);
return p;
}
public void drawLines(Graphics g, Dimension d)
{
int dmin = (d.width < d.height) ? d.width : d.height;
if (stepSize < 1)
stepSize = 1;
Point center = new Point();
center.x = (double)d.width/2.0;
center.y = (double)d.height/2.0;
for (int i = 0; i < 60; ++i) {
Point origin = ptOnCircle(6*i, dmin/2, center);
int j = i + stepSize;
while (j >= 60)
j -= 60;
while (i != j) {
Point destination = ptOnCircle(6*j, dmin/2, center);
g.setColor(interpolate(interpolate(colors[0], colors[1], i%30, 30),
interpolate(colors[0], colors[1], j, 60),
1, 2));
g.drawLine ((int)origin.x, (int)origin.y,
(int)destination.x, (int)destination.y);
j += stepSize;
while (j >= 60)
j -= 60;
}
}
}
}