Modeling the Objects
Any time we like to create ourselves
a more complex application we benefit from sketching a model of the objects we need
using a pencil, that is the classes we need to implement, how they will interact
and the interface between them. In this column we will certainly not learn anything
deeper on modeling since that is a chapter of its own. Anyone interested may search
for books on UML and patterns and that way get readings for several years.
A first sketch may look like the image at your right,
we would like to make a ColorBox class that is a mere holder of three other components.
Hence we assume ColorBox to be of JFrame type, a class we have used and are acquainted
with. In our minds we may see an area that will show us the actual color, the ColorArea.
Probably it may be of the JPanel type.
Continuing with the SlidePanel we
are thinking of a panel that gives us an opportunity to change the three basic colors,
red, green and blue, that makes the RGB scheme. There are of course many ways to
achieve such a feature, but common sliders are both intuitive to work with and easy
to implement since the Swing package of Java gives them to us. Since we will use
three colors we need three JSliders, and thus we understand that they must be placed
on kind of a panel, why not a JPanel?
Finally we discuss the JPanel that
is used to show us the different values computed from the sliders. Yes, a JPanel
is used and to it several JTextFields are added along with JLabels that label the
text fields. We will return to why use panels as kind of surfaces to hold the items,
and why not add them directly on the main frame.
Responsibilities
It is quite obvious that the SliderPanel
will serve us with the input from the user, hence it must have, or at least redirect
user input to an event listener. We chose to have the listener within this panel.
Then we only need to know about the interface to the ColorBox, why not decide to
have a setColor(r, g, b) method in ColorBox? Yes, that will work. Any user input
is catched within the SliderPanel and the event's information is parsed an sent
to the ColorBox's method. That is all we need worry about in the SliderPanel class.
We do not need to send the values
directly to the HexRgbPanel since the ColorBox will do that work. This way we have
separated these classes from each other, the SliderPanel is not relying on the HexRgbPanel,
any of them may be replaced without the other one's knowledge, as long as the interface
to ColorBox does not change. In other words, if we change the JSliders to any other
kind of graphical items, yes to a file reader or anything else that is reasonable,
the HexRgbPanel does not have to know about that. And the SliderPanel does not know
anything about how the information harvested is used by the ColorBox either. That
is good coding style, always make classes replaceable.
We have discussed the HexRgbPanel
to some extent, but how will the interface to ColorBox look like, and vice versa,
how will the interface of ColorBox to HexRgbPanel look like? The latter is of no
interest since we do not like the HexRgbPanel to listen to user input, hence no
interface to ColorBox is needed. But HexRgbPanel need to have a method that can
be used by the ColorBox, let us call it setValues(r,
g, b).
Finally the ColorArea. Looking closer
to it we find that it need only has a method setColor(r,
g, b) to show the actual
color combination set by the user input. Researching the JPanel API we find that
there is such a method, setBackground(Color c) that we may use. Hence we do not need to implement
a special class for ColorArea, we only need instantiate a JPanel object within the
ColorBox class and hold that object as a variable. How convenient!
Summary of interfaces of classes
-
ColorBox need a method
setColor(int r, int g, int b)
-
HexRgbPanel need a method setValues(int r, int g, int b)
This way we now have a short list of methods we really must implement, and we are
done so far.
Summary of classes to implement
-
ColorBox, that holds SliderPanel, HexRgbPanel
and colorArea (that is a JPanel)
-
SliderPanel, that holds three sliders
thats takes user input
-
HexRgbPanel, that holds four text fields,
a HEX field, three fields for RGB values; red, green and blue values. This class
has to convert integers to HEX values as well
Now that we know what to do we can start with the classes one after another. There
is no problem to that since we may mark some codelines as comments as long as they
do not work, that is, we continue working as if the other classes really exist.
Or think of a friend doing these classes in parallel with your work. So, whenever
you like to view your work, mark non-working lines as comments, compile and run.
The ColorBox application
The application will finally look like this
image and you will see that the tools Java gives you makes coding this a breeze.
In fact, every item we use is pre built, we only need find them. Gluing them together
may be tricky, but this time we will be satisfied with a most rudimentary approach.
We can discern the four objects discussed,
the frame, the color area, the SliderPanel and the HexRgbPanel, with their components
respectively. Dragging the slider up or down will instantly change the HEX value
and the value of the color being adjusted. At the same time the color panel will
render the correct RGB color. This application is capable of computing 16,777,216
colors (256 x 256 x 256), the bottleneck may be your screen resolution.
The ColorBox class
We know that we have to implement a
method setColor(int r, int g, int b) that takes arguments from the SliderPanel.
Further we have to hold some variables that will be instantiated in the constructor,
and finally we add a simple driver.
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class ColorBox extends JFrame {
private SliderPanel slidePane;
private HexRgbPanel hexRgbPane;
private JPanel colorPane;
/*
* The constructor of the main class
*/
public ColorBox() {
addWindowListener(new WindowAdapter()
{
public void
windowClosing(WindowEvent e) {
System.exit(0);
}
});
setTitle("ColorBox");
setSize(500,300);
/*
* Find the middle position
on the screen, all screen sizes.
*/
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int w = (dim.width - 600) / 2;
int h = (dim.height - 80) / 2;
/* Are there such small screens
around? If so... */
if (w < 0 || h < 0) {
w = 0;
h = 0;
}
setLocation(w, h);
init(); // call a helper method
}
private void init() { // A helper method
to the constructor
Container pane = getContentPane();
pane.add(hexRgbPane = new HexRgbPanel(),
"South");
pane.add(slidePane = new SliderPanel(this),
"East");
pane.add(colorPane = new JPanel(),
"Center");
setColor(0, 0, 0); // black color
default
}
/*
* A public method that is called both from the hexPane
and the
* rgbPane upon changes to their values.
*/
public void setColor(int r, int g, int b) {
colorPane.setBackground(new Color(r,
g, b));
hexRgbPane.setValues(r, g, b);
}
/*
* The tiny driver
*/
public static void main(String[] args) {
ColorBox box = new ColorBox();
box.show();
}
}
|
|
We need to import the usual packages, Swing since we use JFrame and JPanel, java.awt.event since
we use the WindowAdapter class, and finally java.awt
since the Component and
Color classes resides there. Then we begin with our new class that inherits from
JFrame. Since we will add three components we declare them so that we are later
able to refer to them. In fact we will never refer to the SliderPanel object, but
it does not hurthaving the variable at hand. The constructor is common to us, it
is in fact close to a copy&paste from the last time's installment, except for
the last line of code in it, a call to init().
The last time we to some extent discussed
the use of helper classes, too, and this is no exception to the rule. Actually we
are able to move out the complete code of the constructor too, to another helper
method, I think I will in future columns and that way leave some code to the reader
to type <grin>. The method name this time, init(), is chosen so it will be more easy to convert
this class into an applet later on if you like to.
The init method is simply instantiating
the different components and adding them to the contentPane, as we are used to.
If you mark some lines as comments you may compile and run from here, without having
to implement the other classes yet. This class will be named
ColorBox.java, as a file
of its own. Do not forget to mark the last line of the method
setColor as a comment too,
since hexRgbPanel is still not ready to run.
As you see we add the SliderPanel to the
east of the main frame. The main frame, that is JFrame, uses a BorderLayout as the
default layout manager. It looks like the image to the right. Center is the one
to be used to the main component, this time the colorPane. We do not use North and
hence that area will be empty, as the West side will. But to the East we add the
SliderPanel object, and to the South the HexRgbPanel object. So in fact, we only
use three of the five areas available with this manager. There are other layout
managers as well, but we will not use any of them today, only mention another one
in a moment.
Anyway we see that we are not in
the position to add the JSliders to the contentPane one by one. Or the different
text fields of the HexRgbPanel. It is much easier to add them to JPanels and add
only the panel that will act as a mere holder of the content. But since we inherit
from JPanel into a class of our own we may add some extra features to our class
if we would like to, so we use the class inheriting from JPanel both as a
holder and a place for extra methods that we are implementing.
Then comes the method setColor(int r, int g, int b).
The most intuitive line is probably the first line, it calls the
colorPane object's method setBackground with
a new Color as a parameter. setBackground does only take Color objects as arguments,
so we need to instantiate one from the parameters r,
g and
b. Color is found in the java.awt package.
Next line calls the method setValues of the HexRgbPanel object, that we have not
implemented this far. Now we see that the SliderPanel object makes a call to the
main frame's setColor that
both sets the color of the colorPane, but further pass the call on to the HexRgbPanel
object, that this method works as a telephone relay that pass on signals from a
sender to the receiver. Hence SliderPanel does not need to know about HexRgbPanel,
the intermediate setColor method does the connection between them.
Finally the tiny driver that launches
the rocket.
The HexRgbPanel class
This class will create a JPanel that holds the text fields for the
HEX value, and the values of the red, green and blue colors. We know that we have
to implement a method called setValues(int r, int g,
int b). This method has to know how to convert from
the three values given as parameters from the ColorBox's
setColor. Further we add labels to each text field.
import javax.swing.*;
import java.awt.*;
public class HexRgbPanel extends JPanel {
private JTextField hexValue;
private JTextField redValue;
private JTextField greenValue;
private JTextField blueValue;
/*
* The constructor that adds the items along with labels
*/
public HexRgbPanel() {
add(new JLabel("HEX value:"));
add(hexValue = new JTextField(6));
// 6 columns wide
hexValue.setEditable(false); //
not to edit the value
hexValue.setBackground(Color.white);
add(new JLabel("Red:"));
add(redValue = new JTextField(3));
redValue.setEditable(false);
redValue.setBackground(Color.white);
add(new JLabel("Green:"));
add(greenValue = new JTextField(3));
greenValue.setEditable(false);
greenValue.setBackground(Color.white);
add(new JLabel("Blue:"));
add(blueValue = new JTextField(3));
blueValue.setEditable(false);
blueValue.setBackground(Color.white);
}
/*
* The method that takes the values, computes the
* HEX value and adds the values to the text fields
*/
public void setValues(int r, int g, int b) {
redValue.setText("" +
r);
greenValue.setText(""
+ g);
blueValue.setText("" +
b);
String h; // temp string
String hex = Integer.toHexString(r);
if (hex.length() < 2)
hex = "0"
+ hex;
h = Integer.toHexString(g);
if (h.length() < 2)
h = "0"
+ h;
hex += h;
h = Integer.toHexString(b);
if (h.length() < 2)
h = "0"
+ h;
hex += h;
hexValue.setText(hex.toUpperCase());
}
}
|
|
We import the usual packages, but since we do not use any listeners in this class
we do not need java.awt.event. We have said this class will inherit from JPanel,
that is this class is a JPanel, but we add some objects to it, making the
JPanel a holder of objects. We need to have references to the different text fields
to be able to later update the values of them. The constructor is only adding the
JLabel objects and the JTextFields to the JPanel. I recommend you to read the Java
API to find more information on these items.
Then comes the promised method. The
first three lines is not much to say about, except that the
setText method of JTextField
does only take String objects as parameters, we need to convert the integers to
text strings and I use a hack; make yourself a string and concatenate the integer
to that string and Java will do the cast on the fly. Yes, strings may be empty,
they are still strings with a memory foot print of their own.
The next lines need a short explanation.
To our relief Java offers a convenient static method placed in the Integer class
that converts integers to HEX values. Hence our only work was to look that information
up in our Java API, and, yes it can be tricky sometimes, especially when we do not
know were to look and if the answer is to be found at all. But you did it this time.
Unfortunately this method returns one digit HEX values if it is not above the ten
based value of 15, that is 15 is converted to F, not 0F as we would like to have
it to be able to copy&paste it into our HTML pages. 16 is converted to 10 as
should be. Thus we look at the answer and if it is only one digit long we prefix
it with an extra 0 (zero).
Further we have to convert the color values
one by one using a temporary string, and each one we have to test for this one digit
case before appending it to the resulting HEX string. Finally we can show the value,
but we would like to show it upper case. Done with this class! After saving this
class as a file of its own, named HexRgbPanel.java, you now may remove most of the comment marks,
compile and run. Though still no values will change, we are in desperate need of
the SliderPanel.
I might say that the default layout
manager of the JPanel class is FlowLayout. It works almost as its name, the items
are added in a flow and nothing is promised about were the object will end up, except
that it will be between the previous and the next item. If a new row is needed to
pour objects into, anew row will be created by Java. But this is not always working
as expected neither, as you may see if you resize the main frame to a less wide
size, the bottom panel is not "reflown", but some text fields will be
invisible. Still FlowLayout is useful this time, but be aware of the shortcomings.
The SliderPanel class
This JPanel will hold the three sliders
and act as a listener to user input. We will use a new technique of making ourselves
a listener, not hard to learn but convenient from time to time. This technique is
in no way needed this time, a much simpler way exist, but it is good to use
this way this time when no real problems are at hand. (This class is also a file
of its own, SliderPanel.java.)
import javax.swing.*;
import javax.swing.event.*;
import java.awt.event.*;
import java.awt.*;
public class SliderPanel extends JPanel {
private ColorBox owner; // the main JFrame
private JSlider red;
private JSlider green;
private JSlider blue;
public SliderPanel(ColorBox o) {
owner = o; // reference to the main
frame
Box box = Box.createHorizontalBox();
// an invisible holder
box.add(red = addSlider(Color.red,
true));
box.add(green = addSlider(Color.green,
true));
box.add(blue = addSlider(Color.blue,
false));
add(box); // add the holder to the
JPanel
}
/*
* A helper method to avoid duplicated code
*/
private JSlider addSlider(Color c, boolean bol) {
JSlider s = new JSlider(SwingConstants.VERTICAL,
// direction
0, //
min value
255, //
max value
0); //
initial value
s.setMinorTickSpacing(25); // the
small ticks
if (bol) // if different (larger)
ticks wished
s.setMajorTickSpacing(50);
// larger ticks
s.setPaintTicks(true); // paint
the ticks
s.setForeground(c); // foreground
color
s.addChangeListener(new SliderChangeListener());
return s; // return a fresh to constructor
}
public void setColor() {
int r = red.getValue(); // read
the actual value from slider
int g = green.getValue();
int b = blue.getValue();
owner.setColor(r, g, b); // call
the main frame with values
}
/*
* An anonymous listener class that have full access
to
* the variables and methods of the "surrounding"
object
*/
class SliderChangeListener implements ChangeListener {
public void stateChanged(ChangeEvent
e) {
setColor();
// call to SliderPanel's setColor
}
}
}
|
|
An extended import is made, mainly the listener is added. This class will inherit
from the JPanel and we will add three JSliders to it and a feature to listen for
user input. Strangely we do not implement any listener interface, and we will see
why. We declare the sliders, but also a ColorBox variable
owner. The latter is new
to us.
Since we need to call the main frame's setColor we
need to have a reference to that object. I did not say much about it in the constructor
of the ColorBox (actually the init method), but look careful
and you will see that we pass on a reference this to the constructor of SliderPanel. Standing
in the ColorBox object we are actually giving the reference to itself--this--to the
constructor of SliderPanel, that catches and assigns it to the variable owner. This
variable then work as the connector, or cable, to the
setColor method of the main
frame.
Since FlowLayout is not always trustworthy
we can force the sliders to stand side by side, using a Box of horizontal type.
The Box class resides in the Swing package and gives us either horizontal or vertical
boxes to fill. There is no limit how many items we may add to the box, but they
will always be placed side by side (horizontally) or on top of each other (vertical)
the first item added lies top most. When the box is filled we add the box to the
panel, and the SliderPanel is ready to go.
The lines adding sliders to the box
need an explanation. We may split them into twoliners
red = addSlider(Color.red, true);
box.add(red);
but why should we when we can make them
oneliners? Furthermore, what is taking place? Since there are quite a few lines
looking the same upon creating the sliders we move them out into a helper method,
that returns a fresh slider. So we assign a fresh slider to
red, green and blue, taking
a short tour through the helper method. But since we both will give each slider
a look of its own and as an extra option choose between two looks of the slider
ticks, the marks, we send both a Color object and a boolean as parameters. After
the assignment we add the object to the box.
Please look up the Java API and read
more about the JSlider, I have added some comments into the helper method, but there
is much more to be said. We see how the boolean gives us an opportunity to choose
whether we would like to have major ticks or not. Note that we then have to tell
Java that we really would like to see the ticks too, only stating how narrow
they will appear is not enough. The color is added as the foreground color, visible
only when the slider is used.
The the new listener comes, we add a
listener named SliderChangeListener. At the bottom of the SliderPanel class we find
an anonymous inner class named so, SliderChangeListener. That class implements,
inherits from, the common ChangeListener that is normally used in conjunction with
JSliders and a few other Swing items. ChangeListener is an interface that says we
have to implement the method public void stateChanged(ChangeEvent
e) {} that in our case only
calls the setColor method
of the SliderPanel class. Using inner classes this way gives us the benefit of having
full access to every variable and method of the "surrounding" class. Since
we are not interested in exactly which slider being changed we do not need to parse
the ChangeEvent object this time. The SliderPanel's method
setColor parses all three
sliders and calls the owner.setColor with the three slider values as parameters.
Summary
This far you may compile and run the
now complete ColorBox. We have touched two different layout managers and used a
way to override their way of laying the objects out. We have used some new items
from the Swing tool box, and found that Java have some really convenient classes
and methods pre built. Now we do know how to talk between classes, we pass on this as a
parameter and the receiving class holds it, as a suggestion as the variable owner that
is easily understood. Then the next class can easily call
owner.xxx when needed. We
found that the main frame had no problems passing on the information to another
class, using that class' object's variable. Sketching the objects always helps us
finding how the objects rely on each other and we may see the communication paths
needed.
Next time we will add to and remove
from this frame, trying to make it a PaintBox. CU around.
|