Into Java - Part IX

From EDM2
Jump to: navigation, search
Into Java / Part
I II III IV V VI VII VIII IX X XI XII
XIII IV XV XVI XVII XVIII XIX XX XXI XXII XXIII

By Simon Grönlund

If you ever have driven a completely new car you know that you have to drive very slow and careful for a certain amount of kilometres not to damage the engine and transmission. But how thrilling then when you have passed that limit and may step heavily on the gas. Now we have came to the first limit of the Java programming scheme, we may step on the gas a few inches more. Today we will make us a HEX- and RGB-driven colour chooser that you may use upon creating the HTML code described in another [article] of this issue of OS/2 e-Zine!

Accomplishing such a tool requires some items from the Swing package, a surface to show the result on, and some coding. Further we have to look into how objects talk with each other, how to pass information from one to another over an object in between. And of course, how do we create objects with parameters set at instantiation time? Finally I promised you a lesson on layout managers, I suppose I have to make that a quick one since the column will not grow to long.

Modelling 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 modelling 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.

Java9a.gif
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 colour, 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 colours, 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 colours 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 caught within the SliderPanel and the event's information is parsed and sent to the ColorBox's method. That is all we need to 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 needs only has a method setColor(r, g, b) to show the actual colour 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 code lines 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

Java9.gif
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 colour 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 colour being adjusted. At the same time the colour panel will render the correct RGB colour. This application is capable of computing 16,777,216 colours (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 hurt having the variable at hand. The constructor is common to us, it is in fact close to a copy&paste from the last time's instalment, 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.

Java9BorderLayout.gif
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 colours. 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).

Java9FlowLayout.gifFurther we have to convert the colour 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 [#init 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 one-liners? 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 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.