> ### Solution for
> Programming Exercise 7.3

> THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to the following exercise from this on-line Java textbook.

> Exercise 7.3: The RGBColorChooser applet lets the user set the red, green, and blue levels in a color by manipulating sliders. Something like this could make a useful custom component. Such a component could be included in a program to allow the user to specify a drawing color, for example. Rewrite the `RGBColorChooser` as a component. Make it a subclass of `JPanel` instead of `JApplet`. Instead of doing the initialization in an `init()` method, you'll have to do it in a constructor. The component should have a method, `getColor()`, that returns the color currently displayed on the component. It should also have a method, `setColor(Color c)`, to set the color to a specified value. Both these methods would be useful to a program that uses your component.

> In order to write the `setColor(Color c)` method, you need to know that if `c` is a variable of type `Color`, then `c.getRed()` is a function that returns an integer in the range 0 to 255 that gives the red level of the color. Similarly, the functions `c.getGreen()` and `c.getBlue()` return the blue and green components.

> Test your component by using it in a simple applet that sets the component to a random color when the user clicks on a button, like this one:

> Discussion

> This exercise demonstrates that it is possible to write a new component class as a subclass of `JPanel`. Then the new component can contain several other components. However, it is still used in programs as a single object, with a well-defined responsibility. In this case, the responsibility is "let the user input a color", just as the responsibility of a `JTextField` is "let the user input some text". The program needs a method to get the value input by the user. A `JTextField` has a `getText()` method. The color chooser component needs a `getColor()` method. And just as a `JTextField` has a `setText(String text)` method, the color chooser needs a `setColor(Color c)` method. In a typical application, a program could use `setColor()` to show the user a currently selected color. The user could then modify the color, and the program could get the modified color with the `getColor()` method.

> To convert the color chooser applet to a component, the first line of the class is changed to make the component a subclass of `JPanel` (and to give it a different name):

>            public class RGBChooserComponent extends JPanel
>                                     implements ChangeListener {

> The first line of the `init()` method is changed to make it the first line of a constructor:

>            public RGBChooserComponent() {

> (When I worked on the solution to this exercise myself, I left the word "void" in this line, giving `public void RGBChooserComponent`. Disaster! Instead of a constructor, I had a method named "RGBChooserComponent". Unfortunately, it is quite legal to have a method with the same name as a class. However, it has nothing to do with initializing the applet. When I created a RGBChooserComponent, all I got was a blank space.)

> Since we are defining a panel rather than an applet, a few other changes must be made in the `init()` method. An applet has a content pane to hold components, but components are added directly to a panel, so all calls to `getContentPane()` have to be removed. An applet can define a `getInsets()` method to leave a border around its edges where the background color can show through. `JComponents` such as a `JPanel` should use a `Border` object instead. An EmptyBorder can be used to let the background color show through, so I remove the `getInsets()` method of the applet class and add the line:

>                setBorder( BorderFactory.createEmptyBorder(3,3,3,3) );

> to the constructor. Finally, I set a preferred size for the component with the command:

>                setPreferredSize( new Dimension(280,80) );

> I do this because sliders would like to be a lot bigger than I want them to be in this component. Therefore, the natural preferred size of the component would also be bigger than I want. So, I set a preferred size to be used instead of the preferred size that would be computed by the system.

> Other than that, it is only necessary to add the new routines, to get and to set the selected color. The `getColor()` method is supposed to return the color currently displayed by the component. To make `getColor()` simple to write, I added an instance variable, `selectedColor`, to the class to keep track of the color that is currently selected. The `getColor` method only has to return the value of this variable:

>            public Color getColor() {
>                return selectedColor;
>            }

> In `setColor()`, the new color affects four aspects of the components state: The values on the sliders, the numbers displayed on the labels, the background color of the color patch, and the value of the `selectedColor` instance variable. It is important to keep all these aspects of the state consistent with each other:

>            public void setColor(Color c) {
>                  // Set the selected color in this component, setting the
>                  // color patch, sliders, and positions to match.
>                  // Precondition:  c is not null.
>               int r = c.getRed();
>               int g = c.getGreen();
>               int b = c.getBlue();
>               redSlider.setValue(r);
>               greenSlider.setValue(g);
>               blueSlider.setValue(b);
>               redLabel.setText(" R = " + r);
>               greenLabel.setText(" G = " + g);
>               blueLabel.setText(" B = " + b);
>               colorPatch.setBackground(new Color(r,g,b));
>               selectedColor = c;  // Remember the selectedcolor.
>            } // end setColor

> The comment lists "c is not null" as a precondition for this method. This means that the caller of the method is responsible for ensuring that the actual parameter is not `null`. Alternatively, I could have checked the value of `c` and returned immediately if it is `null`.

> The complete source code is shown below, followed by the source code for the little test applet.

> The Solution

> The custom component class:

>         /*
>            A RGBChooserComponent is a component that allows the user to select
>            an RGB color.  It shows three sliders that the user can manipulate
>            to set the red, green, and blue, components of the color.  A color patch
>            shows the selected color, and there are three labels that show the numerical
>            values of all the components.  Values are in the range 0 to 255.
>            The initial color is black.  The getColor() method can be used to retrieve
>            the color that the user has set.  The setColor() can be used to set
>            the color that is displayed in the component.
>         */

>         import java.awt.*;
>         import java.awt.event.*;
>         import javax.swing.*;
>         import javax.swing.event.*;

>         public class RGBChooserComponent extends JPanel
>                                implements ChangeListener {

>            private Color selectedColor = Color.black;
>                       // The color that is currently selected in the
>                       // component.  Initially, the color is black.

>            private JSlider redSlider, greenSlider, blueSlider;
>                       // For setting the color.

>            private JLabel redLabel, greenLabel, blueLabel;
>                       // For displaying RGB values.

>            private JPanel colorPatch;
>                       // Color patch for displaying the color.

>            public RGBChooserComponent() {

>                /* Create JSliders with possible values from 0 to 255. */

>                redSlider = new JSlider(JSlider.HORIZONTAL, 0, 255, 0);
>                greenSlider = new JSlider(JSlider.HORIZONTAL, 0, 255, 0);
>                blueSlider = new JSlider(JSlider.HORIZONTAL, 0, 255, 0);

>                /* Create JLabels showing current RGB and HSB values. */

>                redLabel = new JLabel(" R = 0");
>                greenLabel = new JLabel(" G = 0");
>                blueLabel = new JLabel(" B = 0");

>                /* Set the colors of the labels, and make them opaque */

>                redLabel.setBackground(Color.white);
>                redLabel.setForeground(Color.red);
>                redLabel.setOpaque(true);
>                greenLabel.setBackground(Color.white);
>                greenLabel.setForeground(new Color(0,150,0));
>                greenLabel.setOpaque(true);
>                blueLabel.setBackground(Color.white);
>                blueLabel.setForeground(Color.blue);
>                blueLabel.setOpaque(true);

>                /* Set this panel to listen for changes to the JSliders' values */

>                redSlider.addChangeListener(this);
>                greenSlider.addChangeListener(this);
>                blueSlider.addChangeListener(this);

>                /* Create a JPanel whose background color will always be set to the
>                   currently selected color.  Otherwise, the panel is empty. */

>                colorPatch = new JPanel();
>                colorPatch.setBackground(Color.black);

>                /* Create the panel's layout, which consists of a row of
>                   three equal-sized regions holding the JSliders,
>                   the Labels, and the color patch.  The background color
>                   of the panel is gray, which will show around the edges
>                   and between components.  To make it show around the
>                   edges, an empty border is added to the panel.*/

>                setBackground(Color.gray);
>                setBorder( BorderFactory.createEmptyBorder(3,3,3,3) );

>                setLayout(new GridLayout(1,3,3,3));
>                JPanel scrolls = new JPanel();
>                JPanel labels = new JPanel();
>                scrolls.setBackground(Color.gray);
>                labels.setBackground(Color.gray);
>                add(scrolls);
>                add(labels);
>                add(colorPatch);

>                /* Add the JSliders and the JLabels to their respective panels. */

>                scrolls.setLayout(new GridLayout(3,1,2,2));
>                scrolls.add(redSlider);
>                scrolls.add(greenSlider);
>                scrolls.add(blueSlider);

>                labels.setLayout(new GridLayout(3,1,2,2));
>                labels.add(redLabel);
>                labels.add(greenLabel);
>                labels.add(blueLabel);

>                /* Set a preferred size for this component, since the value
>                   computed for the preferred size based on the components
>                   that are contained in this panel would be too big. */

>                setPreferredSize( new Dimension(280,80) );

>            } // end constructor;

>            public Color getColor() {
>                  // Returns the color that is currently set in this component.
>               return selectedColor;
>            }

>            public void setColor(Color c) {
>                  // Set the selected color in this component, setting the
>                  // color patch, sliders, and positions to match.
>                  // Precondition:  c is not null.
>               int r = c.getRed();
>               int g = c.getGreen();
>               int b = c.getBlue();
>               redSlider.setValue(r);
>               greenSlider.setValue(g);
>               blueSlider.setValue(b);
>               redLabel.setText(" R = " + r);
>               greenLabel.setText(" G = " + g);
>               blueLabel.setText(" B = " + b);
>               colorPatch.setBackground(new Color(r,g,b));
>               selectedColor = c;  // Remember the selectedcolor.
>            } // end setColor

>            public void stateChanged(ChangeEvent evt) {
>                   // This is called when the user has changed the value on
>                   // one of the sliders.  All the sliders are checked,
>                   // the labels are set to display the correct values,
>                   // and the color patch is set to correspond to the new color.
>               int r = redSlider.getValue();
>               int g = greenSlider.getValue();
>               int b = blueSlider.getValue();
>               redLabel.setText(" R = " + r);
>               greenLabel.setText(" G = " + g);
>               blueLabel.setText(" B = " + b);
>               colorPatch.setBackground(new Color(r,g,b));
>            } // end stateChanged

>         }  // end class RGBChooserComponent

> A small applet to test the component class:

>         /*
>            This is a trivial applet that tests the RGBChooserComponent class.
>            In particular, it tests the setColor() method in that class by
>            setting the color to a random color when the user clicks on a button.
>         */

>         import java.awt.*;
>         import java.awt.event.*;
>         import javax.swing.*;

>         public class TestRGB extends JApplet implements ActionListener {

>            RGBChooserComponent rgb;

>            public void init() {
>               rgb = new RGBChooserComponent();
>               getContentPane().add(rgb, BorderLayout.CENTER);
>               JButton button = new JButton("Set Random Color");
>               button.addActionListener(this);
>               getContentPane().add(button, BorderLayout.SOUTH);
>            }

>            public void actionPerformed(ActionEvent evt) {
>               int r = (int)(Math.random()*256) + 1;
>               int g = (int)(Math.random()*256) + 1;
>               int b = (int)(Math.random()*256) + 1;
>               rgb.setColor( new Color(r,g,b) );
>            }

>         }

> [ Exercises | Chapter Index | Main Index ]