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 ]