Comp201: Principles of Object-Oriented Programming I
Spring 2008 -- Shape Calculator, part 4    


Adding a Dynamically Generated Dialog Box to Set a Shape's Properties

Before starting this section, copy all the code from part 3 into your part4 subdirectory and make any modifcations to the code in part4. Do NOT modify your part3 code!

We've gotten a lot to work so far. One last thing to do...

Our system is currently capable of dynamically loading a factory class and using it to create an arbitrary shape. There's a catch however. The parameters necessary to specify how to make any given shape varies from shape to shape. For example, to create a square requires a single value for the length of its side, but to create a triangle requires two values, one for the length of its base and the other for the length of its height. The null shape requires no initialization parameters, but a circle requires a Color object, which is not a numeric value at all!

One of the most important precepts in object-oriented programming is "delegate to the object that knows best". So who knows what parameters a square needs? There's only one choice here, as a square shape is defined by the factory that made it. The SquareFactory must be the one that determines what parameters are needed to instantiate a square shape. Likewise, the CircleFactory is the only object that knows what parameters are needed to construct a circle shape.

The number and type of parameters is clearly a variant here, but then what is the invariant?

As we've mentioned before, sometimes the obvious is the most difficult thing to see: the invariant is that we always ask for the parameters to construct the shape.

Thus what we need is a mechanism that enables us to ask for the construction parameters but without know what those parameters are (the invariant process) plus a way to get the values for the necessary specific, concrete parameters but without knowing how to ask for them (the variant process).

Dialog Boxes

All modern windowed GUIs have a special kind of window called a dialog box that pops up with some sort of question that we must answer before continuing, e.g. "Do you agree to the following license agreement that indentures your first born child? Yes? or No?". These "modal" windows are unlike regular windows (frames) that come up and have a "life" of their own, instead they are tied to a parent window and prevent ("block") the parent from further processing until modal window has been closed.

Dialog boxes are all about asking questions of the user, so they suit our needs quite well here. In the end,what we need is a dialog box which has a panel supplied by the shape factory that has al the necessary labels, buttons and textfields needed to ask the user what parameter values are desired to make the shape instance.

We will take this step by step again, but with less detailed directions now since you are more versed at building GUIs! Reference your ShapeCalc code while writing this code. Don't forget to import any required packages!

  1. Create a class shapecalc.AShapePropertyDlg that extends JDialog.
  2. Create a constructor for AShapePropertyDlg that takes two parameters, a JFrame owner and a JPanel property panel.
  3. Add whatever import statements are necessary.
  4. Test your code using DrJava's interaction pane.
    1. First import javax.swing.*
    2. Then instantiate an AShapePropertyDlg object, handing 2 null values to its constructor.
    3. Set the dialog's visibility (setVisible) to true.
    4. You should see the dialog box pop up and the interactions pane should not accept any input until the dialog is closed.
  5. Now using local variables, create 2 JButtons, one that says "Ok" and one that says "Cancel".
  6. Likewise, create a button panel (JPanel) to hold the two above buttons.
  7. For kicks, let's try a different layout other than the default FlowLayout in the button panel. Let's use a BoxLayout, which lays out components from the left to the right, or from the top to the bottom, all non-centered.
    1. Set the layout of the button panel to a new BoxLayout.
    2. The constructor for a BoxLayout takes 2 parameters,
      1. the first is the component it is laying out, here, the button panel.
      2. the second is the direction of the layout, here we'll use the static field value: BoxLayout.X_AXIS
    3. Add the two buttons to the button panel. No additional parameters are needed.
  8. Add the button panel to the SOUTH edge of the dialog's content pane(defaults to BorderLayout as all frames do).
  9. Add the properties panel to the CENTER of the content pane.
  10. Using DrJava's interaction pane again, test your dialog box. Be sure to give the constructor a new JPanel as its second input parameter since it will be added to the content pane.
    1. Your dialog frame wll probably pop up too small, so add the call, pack(), at the end of the constructor to force the layout manager to set the sizes of the frame to its minimal size that still shows all the components.
    2. Your dialog also probably doesn't pop up in the middle of the screen either. Use the same method call as in ShapeCalc to center the dialog on the screen.
  11. Write the following two methods with stub code that prints a message to the console as their bodies:
  12. Back in the constructor, add ActionListeners to the Ok and Cancel buttons using anonymous inner classes. The actionPerformed methods of these listeners should call their respective "click" methods above.
  13. Test your dialog box again to make sure that your buttons work properly. When you click a button, you should see the message from the respective click method appear on the console.
  14. Now that you've verified that your dialog box is fully operational, we can make it abstract, as the actual click methods will perform duties specific to our ShapeCalc system.
    1. Make the entire class abstract
    2. Make both click methods (in step 11 above) abstract and remove their stub-coded method bodies completely.

Now all we have to do is to subclass AShapePropertyDlg and override the two click methods with the specific behavior we need. If we instantiate the dialog with a panel filed with all the necessary buttons, textfields, etc needed to construct a shape, then we will have everything we need!

Notice how the development of this abstract class started at with very concrete behavior that we could easily understand and test. Then we abstracted that behavior to get the final expression of our problem. This is a classic technique to develop highly abstract systems. And as usual, the driving motivation through this process is the separation of the variant from the invariant. Here, AShapePropertyDlg represents the invariant of asking the use for property values, without the variant collection of the actual properties needed (the property panel) or the variant process of what to do with those properties (the concrete click methods implemented in any subclasses).

 

Factories Making Panels

Since the shape factories are going to be creating property panels to input the varous parameters needd to construct their shapes, we need to add another method to the IShapeFactory interface:
JPanel getPropertyPanel()
.

For the moment, simply implement that method in each of the factories by having them return a private static JPanel field that was initialized to a plain JPanel instance. Why is it ok to use a static field here?

 

Bringing Up a Dialog Box

Now we need to get ShapeCalc to bring up the dialog box. The properties dialog needs to pop up after the factory is instantiated but before the resultant shape is used. Find this point in your code and insert the following code there. You will find that the new code will literally engulf the code where the factory creates the new shape instance.

  1. Use an anonymous inner class, subclass and instantiate a new instance of AShapePropertyDlg, overriding the two click methods (we'll fill in their method bodies in a minute).
    1. We've never made an anonymous inner class of a superclass whose constructor required input parameters. But the syntax is very intuitive. In this case, the constructor for AShapePropertyDlg takes two parameters, so we simply say:
      new AShapePropertyDlg(param1, param2) { .... }
    2. The first constructor parameter is the host frame, in this case, the ShapeCalc object itself. But since we are currently inside of the anonymous inner class for the newBtn's ActionListener, we can't say "this" because it would refer to the wrong object. Instead, we must say "ShapeCalc.this" to reference the entire ShapeCalc object.
    3. The second constructor parameter is the property panel that the shape factory is supposed to make, so simply use the result of calling the shape factory's getPropertyPanel method.
    4. The instance of the dialog box doesn't need to be assigned to a variable. It simply needs to be set visible. Since it is modal, the execution of the code will not proceed until the dialog closes. So, without assigning to any sort of variable, instantiate the dialog box and set its visibility to true.
  2. Now for the bodies of the click methods
    1. The okBtnClick method runs if the user clicks the Ok button in the dialog. The user thus is saying "Yes, please make this shape!" In this situation we need to
      1. Tell the factory to make the shape. We had this code already, now we know where to put it.
      2. Get rid of the dialog box. A dialog box, as any frame, has a dispose() method that will close the window and destroy the object.
    2. The cancelBtnClick method runs if the user clicks the Cancel button in the dialog. The user is thus saying "No, please don't do anything!". In this siutation we simply need to close the dialog down using the dispose() method.
  3. Test your program. You should be getting some nice dialogs coming up now with blank panels. Be sure to verify that the Ok and Cancel buttons work properly. Progress, progress!

Now all we need to do is to get the respective shape factories to produce useful property panels and we're all done!

Property Panels for Squares

The property panel for a square would contain simply a texfield into which one could input the length of the side and a label describing the textfield.

  1. The text field needs to be a field of the factory so that it is usable by the makeShape method. It can be static--why?
  2. In the constructor of SquareFactory, add the label and the text field to the property panel.
    1. A BoxLayout might be nice here, but with a BoxLayout.Y_AXIS orientation.
  3. All we need to do now is to initialize the field in the IShape anonymous inner class that represents the length of the side of the square.
    1. Unfortunately, the field is a double and the text field where the user input the desired value holds a String.
    2. All the wrapper classes for the Java primitive types have static methods that can "parse" a String and convert it to the associated primitive type. For instance, in this case we can use thefollowing static method: public static double Double.parseDouble(String s)
    3. Use the above method to initialize the square's field to the value in the text field.
  4. One problem is that the user may have typed in an invalid number, such as "3.g7" or perhaps nothing at all. If parseDouble tries to parse such an invalid input, it will throw an exception and stop the program.
    1. We need to "catch" the exception should it occur.
    2. Use the following try-catch syntax to replace the entire method body of makeShape:
      
      	  try {
      
      	  	[put entire original "return ....;" statement here]
      
      	  } 
      
      	  catch(Exception parseError) {
      
      	    [set the text field's text to the string "0.0"]
      
      		[call makeShape() again]
      
      	  }
      
      	  
    3. This crude exception handling will simply replace any invalid input with "0.0". But it does keep the program from crashing!

     

If you look simply at the code in ShapeCalc, it does not look like the newly constructed shape is at all connected to the property panel used to configure it. They are created and used in different sections of the code after all. But when you look at the SquareFactory, we see that the property panel and the new IShape instance are indeed connected: the text field of the property page is in the closure of the the shape object. Hence the shape object can reference the values of the property panel!

Closures give us a tremendously powerful communications pathway that leaves our systems beautifully decoupled!

 

Property Panels for Circles

The property panel for the CircleFactory is very similar to that for the SquareFactory, but it does need to use another label and a button.

The functionality that you want to achieve for the property panel includes the following:

  1. The button to select the color should be the currently selected color.
  2. The currently selected color should persist between usages. That is, for instance, if red was selected and then any number of non-circle shapes made, then the next time CircleFactory is used, then red should be the default color.
  3. Canceling the creation of a new shape should not affect the color of the current shape, even if it is a circle.
  4. As with SquareFactory, an invalid radius input value will cause a shape of radius=0 to be made.

Here's some hints and tools to use:

Depending on how you implemented things, you may observe some odd behavior when you run the program. Try the following sequence of actions:

  1. Create a new circle shape using the CircleFactory. Select any color and size you want. The shape is made correctly.
  2. Start to create another circle shape using CircleFactory again. Select a new color but don't make the shape yet.
  3. Notice that even though the new circle shape has not been made, the color of the old circle shape has changed to the newly selected color.
  4. Even if you cancel the construction of the new shape, the old shape retains the new color.

Why is this happening? Try to explain the phenomenon in terms of closures.

Fix the problem if it occurs in your program so that you can achieve functionality #3 above.

More stuff to do...

 

 


Last Revised Thursday, 03-Jun-2010 09:50:28 CDT

©2008 Stephen Wong and Dung Nguyen