Page 1 of 1

Adding and using child windows in a Java GUI in an MVC architecture Rate Topic: -----

#1 GregBrannon  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 2198
  • View blog
  • Posts: 5,226
  • Joined: 10-September 10

Posted 20 November 2010 - 08:42 PM

*
POPULAR

Adding and using child windows in a Java GUI in an MVC architecture

Summary: This tutorial provides a working example of how to create an Model-View-Controller (MVC) Java GUI using Swing, including the use of two child windows that share data with the main GUI and among themselves.

Prerequisites:
(1) An understanding of the Model-View-Controller (MVC) architecture or pattern. If you are not familiar with the MVC architecture, I recommend you study the example at:

http://www.macs.hw.a...he%20GUI&page=1

(2) A basic understanding of the process to build a GUI in Java using Swing, including the basic elements JFrame, JDialog, JButton, JTextField, and the use of ActionListeners. If you need more background in the use of Swing or the components mentioned, I recommend the series of Swing Tutorials on DIC by jinnveshere and the Java online documentation.

I’m not an expert on MVC, or Java for that matter, and I’ve discovered that not all MVC architecture examples on the web follow the same pattern. I used the information at the above link to begin my MVC study, and I attempted to faithfully follow that same same pattern in this tutorial. From that foundation I will demonstrate how to add and use child windows. For more info on MVC, search for other examples on the Internet, and/or review the Wikipedia article.

Examples that show how to create a Java GUI program using the MVC architecture do exist on the Internet, but I could not find an MVC example that showed how to create and use child windows or dialogs. Adding child windows to an MVC architecture presents challenges that do not exist in less structured architectures.

A SHORT summary of what you’ll find at the above link: MVC stands for “Model -View-Controller.” The model contains all of the program’s data and the logic, or methods and functions, to manipulate the data. A restriction on the model is that it may not change the view. The view displays the data, and the view is not allowed to change the model’s data. The controller uses inputs to the view and the logic in the model to update the data. A way to think of this is that the controller maps the user’s actions in the view to the model’s functions and methods, resulting in updates to the program’s data. The controller is in control, using its ActionListeners to detect inputs that are communicated to and through the view and the model to act on the data according to the user’s inputs.

Another file, separate from the MVC, contains a main() method that instantiates a view class and a model class and then instantiates a controller class, sending the view and model to the controller through the controller’s constructor.

My code example includes a model (“ExampleModel.java”), controller (“ExampleController”), main view (“ExampleView.java”), and 2 child views (“ShowTextDialog.java”, and “ShowReversedTextDialog.java”). The code has not been optimized or simplified, and I’m sure there are many improvements that could be made. I tested it, and it all works. Here’s HOW it works:

Let’s begin by reviewing the code for the M-V-C components in MVC order. First, the model:

/*  ExampleModel.java
*  
*  Description: This class is the model for the Example program. It contains
*               the data and the methods and functions that are required to
*               manipulate the data.
*               
*  Author: GregBrannon, November 2010
*/

package examplemfcgui;

public class ExampleModel
{
    // variables, methods, etc. required for the program's logic
    private String enteredText = null;
   
    // constructor ExampleModel()
    protected ExampleModel()
    {
       // nothing to do - expand as necessary
       
    } // end constructor ExampleModel()
   
    // getters, setters, and other methods to manipulate data
   
    // setter setEnteredText() to capture user-entered text
    protected void setEnteredText( String text )
    {
       enteredText = text;
       
    } // end setter setEnteredText()
   
    // getter getEnteredText() returns enteredText;
    protected String getEnteredText()
    {
       return enteredText;
       
    } // end getter getEnteredText()
   
    // reverse the text
    protected String getReversedText()
    {
       StringBuffer reversedText = new StringBuffer(
               getEnteredText() ).reverse();
       String reversedTextString = reversedText.toString();
       return( reversedTextString );

    } // end getter getReversedText()

} // end class ExampleModel



ExampleModel.java:
As specified on line 10 of this source file, all files in this tutorial are members of the examplemfcgui package. Once my project is safely collected in a package, I use “protected” liberally as the access modifier for methods and, whenever possible, private for variable and method declarations. The MVC architecture provides the programmer with a framework to guard the application’s variables and methods against unauthorized corruption and use by outside forces, and using the most restricted access modifier possible increases the protection. The use of static as an access modifier is discouraged, and a diligent programmer will find alternatives to creating “global” variables and methods.

ExampleModel.java contains the single class, ExampleModel, a constructor that doesn’t do anything, and 3 simple accessor methods, 2 ‘getters’ and 1 ‘setter,’ included to demonstrate the proper use of the model and its complete control of the program’s data. I choose to declare my accessors “protected,” which means they can be seen and used by all classes that belong to the package. It’s worth noting that the accessors provide access to the class’ variables, but the variables are private and cannot be accessed directly by any force outside the containing class.

When I first constructed the code for this example, I included a simple, one-line piece of code in the controller that did the work of the accessor getReversedText() in ExampleModel. I decided that was outside the desired authority of the controller, violating the rule that changes to the program’s data be done only by the model. If a violation at all, it was a minor one, because the data stored in memory with that variable name wasn’t changed.

There are 3 view elements. Another important point to understand: just because the MVC architecture has one M, V, and C, the implementation is not restricted to a single code element for each. I think most small projects can be managed with a single model and a single controller, but even small projects will probably require multiple view elements. My small projects haven’t required more than one model or controller element - yet - but I can imagine that there is a size or complexity of a model or controller that is simply too big to easily manage. When that occurs, using more than one model or controller will make sense.

The first view element, ExampleView.java, included below, is the example program’s main user interface. It is created and shown before the other views and remains on screen as long as the program is running.

/*  ExampleView.java
*  
*  Description: This class sets up the main GUI screen of the Example program.
*
*  Author: GregBrannon, November 2010.
*/

package examplemfcgui;

import java.awt.Dimension;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;

public class ExampleView extends JFrame
{
    private ExampleView exampleViewFrame;
   
    private JButton showDialog1Btn;
    private JButton showDialog2Btn;
    private JButton cancelBtn;
   
    private JTextField shareTextArea;
    private JLabel textAreaLabel;

    private String showDialog1BtnName = "showDialog1Btn";
    private String showDialog2BtnName = "showDialog2Btn";
    private String cancelBtnName = "cancelBtn";
   
    // constructor ExampleView()
    // does all of the work to build the Example's main GUI
    protected ExampleView()
    {
       // set the title, size, close action, location, resizability, and layout
       setTitle( " Example Frame " );
       setPreferredSize( new Dimension( 400, 220 ) ); // width, height
       setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE );
       setLocationRelativeTo( null );
       setResizable( false );
       // uses default layout
       
       add( createAndShowExampleContent() );

       // finalize the frame's components and make it visible
       pack();
       setVisible( true );
       toFront(); // brings the window to the front
       
    } // end constructor ExampleView()
   
    // creates the content panel which shows the main elements of the GUI for
    // display on the frame
    private JPanel createAndShowExampleContent()
    {
       JPanel mainPanel = new JPanel();
       JPanel buttonPanel = new JPanel();
       
       // create the button panel
       buttonPanel = new JPanel();
       showDialog1Btn = new JButton( "Button 1" );
       showDialog2Btn = new JButton( "Button 2" );
       cancelBtn = new JButton( "Cancel" );
       
       // add action names to buttons
       showDialog1Btn.setActionCommand( showDialog1BtnName );
       showDialog2Btn.setActionCommand( showDialog2BtnName );
       cancelBtn.setActionCommand( cancelBtnName );
       
       // create a simple text area to hold a value
       shareTextArea = new JTextField( 20 );
       
       // and a label to describe the text area's function
       textAreaLabel = new JLabel( "Share this text: " );
       String instructionString =
             ( "<html><BR><BR><center>Enter text in the box, press " +
               "<BR>Button 1 to pass that text to a new dialog. " +
       "<BR>Press Button 2 to see the text reversed.</html>" );
       JLabel instructions = new JLabel( instructionString );
              
       // add buttons to buttonPanel
       buttonPanel.add( showDialog1Btn );
       buttonPanel.add( showDialog2Btn );
       buttonPanel.add( cancelBtn );
       
       // add the GUI panels and text field to the main panel
       mainPanel.add( buttonPanel );
       mainPanel.add( textAreaLabel );
       mainPanel.add( shareTextArea );
       mainPanel.add( instructions );

       return mainPanel;
       
    } // end method createAndShowExampleFrame()
   
    protected void exampleViewBtnListener( ActionListener viewBl )
    {
       showDialog1Btn.addActionListener( viewBl );
       showDialog2Btn.addActionListener( viewBl );
       cancelBtn.addActionListener( viewBl );
    }
   
    // getters and setters (if any) to get and set displayed data
    protected String getShareTextField()
    {
       return shareTextArea.getText();
    }

} // end class ExampleView



I extended JFrame for the main class ExampleView. Lines 22 - 33 declare the components that I’ll show on the JFrame: 3 JButtons, 2 to show the child JDialogs; a cancel button; a text area that will be used to share data between the other componentsa and a label to title the text area. The constructor, lines 37 - 54, does typical JFrame housekeeping: titles the frame, sets the size, defines close button behavior, frame location, and resizability. I use the default layout for all GUIs to minimize the code required. Line 47 calls the method createAndShowExampleContent() which creates a JPanel, mainPanel, that will be the main content panel on the JFrame. The method also creates a buttonPanel on which to stick the 3 buttons to help organize the components.

Note that the JFrame constructor sets the JFrame visible and brings it to the front. This makes the JFrame visible from the time it is constructed until the program ends.

Lines 70 - 72 are key to making the MVC architecture work. These lines set the ActionCommands using the String button names defined at lines 31 - 33. The ActionCommands can be detected by built-in methods so that they can be easily recognized by other classes, like the controller. This is a good time to point out that there is no ActionListener interface to the view. You can tell this, because there is no “implements ActionListener” on line 20 where the class ExampleView is defined, and there is no actionPerformed() method which would be required if there were an ActionListener interface present.

Lines 78 - 83 provide minimal instructions to the user in a JLabel which will display an HTML formatted String. Lines 86 - 88 add the buttons to the buttonPanel, and lines 91 - 94 add all of the constructed components to the mainPanel, including the buttonPanel. The mainPanel is returned by the method to the class constructor and added to the JFrame by line 47.

The second key piece of this class that makes the MVC architecture work is coded at lines 102 - 104 in the exampleViewBtnListener() method. This method is called by the controller, which passes its own ActionListener as the argument to detect button presses that can then be reacted to by code in the controller’s actionPerformed() method.

A single accessor is used to return the contents of the JTextField that can be edited by the user for sharing with other components, as is and modified by the model.

I’m going to briefly mention the two other view components (included below), ShowTextDialog.java and ShowReversedTextDialog.java, before moving to the controller. The two child views are JDialogs that contain a JTextField and OK and Cancel buttons. The JTextField in ShowTextDialog is passed the text typed by the user into the main view’s JTextField by the controller. Similarly, the ShowReversedTextDialog displays the text from the main view but after it has been reversed by the model. The construction of these two dialogs is very similar to the main view, and the same important MVC architecture elements exist.

/*  ShowTextDialog.java
*  
*  Description: this class creates a modal dialog box that presents the user-
*               entered text in a text field, and 2 buttons, ok and cancel
*
*  Author: GregBrannon, October 2010
*/

package examplemfcgui;

import java.awt.Dimension;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;

public class ShowTextDialog extends JDialog
{
    private JPanel mainPanel;
   
    private JButton showTextDialogOkBtn;
    private JButton showTextDialogCancelBtn;
   
    private String showTextDialogOkBtnName = "showTextDialogOkBtn";
    private String showTextDialogCancelBtnName = "showTextDialogCancelBtn";
   
    private JTextField textField;

    // constructor showTextDialog()
    public ShowTextDialog(  String textToShow,
                           ExampleView owner, boolean modality )
    {
       super( owner, modality );
       setTitle( "Show Text" );
       
       setPreferredSize( new Dimension( 320, 150 ) );
       setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE );
       setResizable( false );
       
       // create the panel that will be the container
       mainPanel = new JPanel();
       
       // create the buttons
       showTextDialogOkBtn = new JButton( "OK" );
       showTextDialogCancelBtn = new JButton( "Cancel" );
       
       // create the text area
       textField = new JTextField( textToShow, 20 );
       
       // add text field
       mainPanel.add( new JLabel( "Text: " ) );
       mainPanel.add( textField );
       
       // add buttons
       mainPanel.add( showTextDialogOkBtn );
       mainPanel.add( showTextDialogCancelBtn );

       // add the components to the frame, specify placement, and arrange
       add( mainPanel );
       
       setLocationRelativeTo( owner );
       
       pack();
       
    } // end constructor showTextDialog()
   
    // method showTextActionListeners is called by the controller to add its
    // action listener to this class' buttons.
    protected void showTextActionListeners( ActionListener showTextBl )
    {
       showTextDialogOkBtn.setActionCommand( showTextDialogOkBtnName );
       showTextDialogOkBtn.addActionListener( showTextBl );
       showTextDialogCancelBtn.setActionCommand( showTextDialogCancelBtnName );
       showTextDialogCancelBtn.addActionListener( showTextBl );
    }
   
    // getter getTextField() returns the string entered by the user
    protected String getTextField()
    {
       String text = new String( textField.getText() );
       return text;
       
    } // end getter getTextField()
   
    // setter that clears the text field
    protected void clearTextField()
    {
       textField.setText( "" );

    } // end setter clearTextField()
   
} // end class showTextDialog



/*  ShowReversedTextDialog.java
*  
*  Description: this class presents a modal dialog box that presents the user-
*               entered text reversed in a text field, and 2 buttons, ok and
*               cancel
*
*  Author: GregBrannon, October 2010
*/

package examplemfcgui;

import java.awt.Dimension;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class ShowReversedTextDialog extends JDialog
{
    private JPanel mainPanel;
   
    // clock-in dialog components
    private JButton showReversedTextDialogOkBtn;
    private JButton showReversedTextDialogCancelBtn;
   
    private String showReversedTextDialogOkBtnName =
       "showReversedTextDialogOkBtn";
    private String showReversedTextDialogCancelBtnName =
       "showReversedTextDialogCancelBtn";
   
    private JTextField textField;

    // constructor showReversedTextDialog()
    public ShowReversedTextDialog(  String reversedTextToShow,
                           ExampleView owner, boolean modality )
    {
       super( owner, modality );
       setTitle( "Show Text Reversed" );
       
       setPreferredSize( new Dimension( 320, 150 ) );
       setDefaultCloseOperation( JFrame.HIDE_ON_CLOSE );
       setResizable( false );
       
       // create the panel that will be the container
       mainPanel = new JPanel();
       
       // create the buttons
       showReversedTextDialogOkBtn = new JButton( "OK" );
       showReversedTextDialogCancelBtn = new JButton( "Cancel" );
       showReversedTextDialogOkBtn.setActionCommand(
               showReversedTextDialogOkBtnName );
       showReversedTextDialogCancelBtn.setActionCommand(
               showReversedTextDialogCancelBtnName );
       
       // create the text area
       textField = new JTextField( reversedTextToShow, 20 );
       
       // add text field
       mainPanel.add( new JLabel( "Text: " ) );
       mainPanel.add( textField );
       
       // add buttons
       mainPanel.add( showReversedTextDialogOkBtn );
       mainPanel.add( showReversedTextDialogCancelBtn );

       // add the components to the frame, specify placement, arrange the
       // components, and make visible
       add( mainPanel );
       
       setLocationRelativeTo( owner );
       
       pack();
       
    } // end constructor showTextDialog()
   
    // method showReversedTextActionListeners is called by the controller to
    // add its action listener to this class' buttons.
    protected void showReversedTextActionListeners( ActionListener
           showReversedTextBl )
    {
       showReversedTextDialogOkBtn.setActionCommand(
               showReversedTextDialogOkBtnName );
       showReversedTextDialogOkBtn.addActionListener( showReversedTextBl );
       showReversedTextDialogCancelBtn.setActionCommand(
               showReversedTextDialogCancelBtnName );
       showReversedTextDialogCancelBtn.addActionListener( showReversedTextBl );
    }
   
    // getter getTextField() returns the string entered in the text field
    protected String getTextField()
    {
       String text = new String( textField.getText() );
       return text;
       
    } // end getter getTextField()
   
    // setter that clears the text field
    protected void clearTextField()
    {
       textField.setText( "" );

    } // end setter clearTextField()
   
} // end class showTextDialog



I did 3 things slightly differently in the dialogs for instructional purposes. Note that lines 75 and 77 of ShowTextDialog set the button’s action commands in the method that adds the controller’s ActionListener rather than during the construction of the dialog as was done for the main view. And just for the heck of it, in the class ShowReverseTextDialog.java, I set the ActionCommand in both the main body of the JDialog’s constructor (lines 55 and 57) and in the method that attaches the controller’s ActionListener to the buttons (lines 86 and 89). They all work. I see the construction pattern used for the main view as more intuitive, but that may just be me.

Also note that the constructors for the 2 child JDialogs do not set them visible. I suggest you try adding the code that makes them visible as the last line of their constructors to see what happens. In the controller, I also show that the observed behavior is the same whether the JDialogs are removed from view by using either the dispose() or setVisible( false ) methods.

One more thing before moving to the controller. Let’s review the purpose of the very simple Example.java program.

/*  Example.java
*  
*  Description: Example.java contains the main() method that creates a model,
*               a view, and a controller, passing the model and the view to
*               the controller.
*
*  Author: GregBrannon, November 2010
*/

package examplemfcgui;

public class Example
{
    public static void main( String[] args )
    {
       // create a model of the program's logic
       ExampleModel model = new ExampleModel();
       
       // create a view of the program's main user interface
       ExampleView mainView = new ExampleView();
       
       // creates the controller that links the model and the view
       ExampleController controller =
                               new ExampleController( model, mainView );
       
    } // end method main()

} // end class Example



The main() method in Example.java instantiates a model and a view and passes them both to the the controller’s constructor. This simple action gives the controller everything it needs to coordinate activity between the view and the model or to map the user’s inputs on the view(s) to the model’s data and methods.

With the main() methods function fresh in our minds, let’s move to the controller’s constructor to see what it does with what the main() method passes.

/*  ExampleController.java
*  
*  Description: This class maps the user's actions in the the view to the data
*               and methods in the model.
*
*  Author: GregBrannon, November 2010
*/

package examplemfcgui;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class ExampleController implements ActionListener
{
    private ExampleModel model;
    private ExampleView view;
    private ShowTextDialog showTextDialog;
    private ShowReversedTextDialog showReversedTextDialog;
   
    // constructor ExampleController()
    public ExampleController( ExampleModel model, ExampleView view )
    {
       this.model = model;
       this.view = view;
       
       // add this class' actionListener to the view's buttons
       view.exampleViewBtnListener( this );
       
    } // end constructor ExampleController()
   
    // the method actionPerformed() to handle the main GUI button presses
    public void actionPerformed( ActionEvent action )
    {
       // retrieve the user-entered text and send it to the model
       model.setEnteredText( view.getShareTextField() );
       
       String viewAction = action.getActionCommand();
       
       if ( viewAction.equals( "showDialog1Btn" ) )
       {
           // create the dialog that shows the text
           showTextDialog = new ShowTextDialog( model.getEnteredText(),
                   view, true);
           
           // add the listener to the show text dialog
           showTextDialog.showTextActionListeners(
                   new ShowTextDialogActionListener( showTextDialog ) );
           
           showTextDialog.setVisible( true );
       }
       else if ( viewAction.equals( "showDialog2Btn" ) )
       {
           // create the dialog that shows the reversed text
           showReversedTextDialog = new ShowReversedTextDialog(
                   model.getReversedText(), view, true);
           
           // add the listener to the show text dialog
           showReversedTextDialog.showReversedTextActionListeners(
                   new ShowReversedTextDialogActionListener(
                           showReversedTextDialog ) );
           
           showReversedTextDialog.setVisible( true );
       }
       else if ( viewAction.equals( "cancelBtn" ) )
       {
           view.dispose();
       }
       
    } // end method actionPerformed()
   
} // end class ExampleController

// class ShowTextDialogActionListener provides the action listeners for the
// ShowTextDialog and the actionPerformed() method to process them
class ShowTextDialogActionListener implements ActionListener
{
    ShowTextDialog view;
   
    public ShowTextDialogActionListener( ShowTextDialog view )
    {
       this.view = view;
    }
   
    public void actionPerformed( ActionEvent ae2 )
    {
       String actionEvent2 = ae2.getActionCommand();
       
       if ( actionEvent2.equals( "showTextDialogOkBtn" ) )
       {
           // one way to remove the dialog
           view.dispose();
       }
       else if ( actionEvent2.equals( "showTextDialogCancelBtn" ) )
       {
           // another way to remove the dialog
           view.setVisible( false );
       }
    }
   
} // end class ShowTextDialogActionListener

// class ShowReversedTextDialogActionListener provides the action listeners for
// the ShowReversedTextDialog and the actionPerformed() method to process them
class ShowReversedTextDialogActionListener implements ActionListener
{
    ShowReversedTextDialog view;
   
    // constructor ShowReversedTextDialogActionListener()
    public ShowReversedTextDialogActionListener(
                                           ShowReversedTextDialog view )
    {
       this.view = view;
       
    } // end constructor ShowReversedTextDialogActionListener()
   
    public void actionPerformed( ActionEvent ae3 )
    {
       String actionEvent3 = ae3.getActionCommand();
       
       if ( actionEvent3.equals( "showReversedTextDialogOkBtn" ) )
       {
           // one way to remove the dialog
           view.dispose();
       }
       else if ( actionEvent3.equals( "showReversedTextDialogCancelBtn" ) )
       {
           // another way to remove the dialog
           view.setVisible( false );
       }
       
    } // end method actionPerformed()
       
} // end class ShowReversedTextDialogActionListener



First, note that the controller is made up of the main public class with its constructor and two additional ActionListener classes. The additional classes aren’t “nested” or “inner” classes. I believe they may be called “helper classes” by some, but I’m fuzzy on that point. Note that the access modifier for the additional ActionListener classes is the default or package-private modifier. Try using any other modifier and see what happens.

The controller is where the magic happens. The controller’s constructor, which as I mentioned before includes the ActionListener interface, first makes a local reference to the model and the view and then adds itself as an ActionListener to the view using the view’s exampleViewBtnListener() method. The controller’s actionPerformed method then waits for something to happen. When it detects the view’s buttons have been pressed, it uses the view’s accessor to retrieve text the user typed in the JTextField and then it ‘decodes’ which button was pressed by using the ActionEvent’s getActionCommand() method. The getActionCommand() method returns the ActionCommands that were assigned to the view’s buttons at lines 70 and 71 of ExampleView.

Once the actionPerformed() method knows which button was pressed using the getActionCommand() method, the proper logic can be executed. If Button 1 was pressed, ActionCommand = “showDialogBtn,” and the child JDialog, showTextDialog is created. A Button 2 signal similarly will cause the showReversedTextDialog JDialog to be built.

But what’s the next step? How does one do anything with the child windows? This is the formula:

1) Create child window
2) Add an ActionListener from the controller to the child window
3) Make child window visible

This formula is executed twice for the 2 child JDialogs in the controller’s lines 43 - 50 and lines 55 - 63. Once step 3 from the above formula is executed, the controller waits for the next button signal from the active view. The child JDialogs are “modal” to the main JFrame ‘owner,’ so only one window can be active at a time. I haven’t tried allowing more than one window to be active at a time. I leave that investigation for interested readers.

In summary, I’ve shown the essential elements necessary to successfully build a Java GUI using Swing in an MVC architecture, extending the simple examples typically found on the Internet to include child windows that have functional buttons and shared data. I welcome your comments, suggestions, and questions.

Is This A Good Question/Topic? 10
  • +

Replies To: Adding and using child windows in a Java GUI in an MVC architecture

#2 v0rtex  Icon User is offline

  • Caffeine: db "Never Enough!"
  • member icon

Reputation: 223
  • View blog
  • Posts: 773
  • Joined: 02-June 10

Posted 29 May 2011 - 08:59 PM

Nicely detailed and thorough explanation of some rather difficult content!
Was This Post Helpful? 0
  • +
  • -

#3 chaparro  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 19
  • Joined: 17-October 09

Posted 03 December 2011 - 08:36 PM

GregBrannon:

The link does not appear to work anymore, it seems that they took the page down.

Just thought you might like to know.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1