.Net Code Monkey RSS 2.0
 Friday, April 11, 2008

Back to Part 2

Review Of Progress So Far

So, to date we have created an interface for our ShapeFactory and any other shape factory to implement, an enumeration that represents all the available shapes we may wish to build, and a factory class that can build any of our two current shapes. The next step is to create a graphical user interface that will call the factory and receive a couple of shape objects for it's trouble.

The Client Gets What They Deserve

Next we need a client to display the shapes, so for simplicity we are going to create a small Windows Forms project called WindowsApplication and add it to the solution. The project will have a single form called Main (700x550) which will have a picture box (uxPictureBox) and a button, uxDrawButton.

We now need to add a handler for the Draw Button and declare a Bitmap and a Graphics object at the top of the class. In the form constructor we will initialise the bitmap to a new instance which will be the size of the PictureBox. We will then create the Graphics object from the Bitmap.

In the button handler method we will clear the graphics object to a white background.

public partial class Main : Form
 {
     private Bitmap _bitmap;
     private Graphics _graphics;

     public Main()
     {
         InitializeComponent();

         _bitmap = new Bitmap(uxPictureBox.Width, uxPictureBox.Height);
         _graphics = Graphics.FromImage(_bitmap);
         uxPictureBox.Image = _bitmap;
     }

     private void uxDrawButton_Click(object sender, EventArgs e)
     {
         _graphics.Clear(Color.White);
     }
 }

We now need a collection to hold our shapes, so we will declare a list of objects that implement IShape at the top of the class, and to do this we will need to provide a reference to the ShapeInterfaces project. At the same time add a reference to the Factories project and import the namespaces for both projects at the top of the form code.

using System.Text;
using System.Windows.Forms;
using Playground.ShapeMaker.Factories;
using Playground.ShapeMaker.ShapeInterfaces;

namespace Playground.ShapeMaker.WindowsApplication
{
    public partial class Main : Form
    {
        private Bitmap _bitmap;
        private Graphics _graphics;
        private List<IShape> _shapes;

        public Main()
        {

Next we need to add two method stubs that are void of return values, AddShapes() and DrawShapes(). Surprisingly, AddShapes() will take responsibility for adding new shapes to our list and DrawShapes() will draw those shapes. We will add a line to create an instance of our list in the constructor and below that we will place a call to the AddShapes() method. We will place a call in the button handler to the DrawShapes() method, below the line that clears the graphics to a white background. Follow this with a refresh of the PictureBox.

public Main()
 {
     InitializeComponent();
     _bitmap = new Bitmap(uxPictureBox.Width, uxPictureBox.Height);
     _graphics = Graphics.FromImage(_bitmap);
     uxPictureBox.Image = _bitmap;

     _shapes = new List<IShape>();
     AddShapes();
 }

 /// <summary>
 /// Handles the draw button pressed event.
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void uxDrawButton_Click(object sender, EventArgs e)
 {
     _graphics.Clear(Color.White);
     DrawShapes();
uxPictureBox.Refresh(); }

In the AddShapes() method we need to create a new instance of our ShapeFactory object. Then using the ShapeFactory object add two shapes, a circle and a rectangle to the list.

 /// <summary>
 /// Adds shapes to the list
 /// </summary>
 private void AddShapes()
 {
     ShapeFactory shapeFactory = new ShapeFactory();
     _shapes.Add(shapeFactory.CreateShape(Shape.Circle));
     _shapes.Add(shapeFactory.CreateShape(Shape.Rectangle));
 }

Because we know that the shapes Implement the IShape interface we have a contract that says we can call the Draw() methods on any of these shapes. These objects will then take the responsibility for drawing them selves. So in the DrawShapes() method all we need to do is iterate through all the shapes in the list and call each shapes Draw() method.

 /// <summary>
 /// Draws the shapes in the list
 /// </summary>
 private void DrawShapes()
 {
     foreach(IShape shape in _shapes)
     {
         shape.Draw();
     }
 }

And that would be it... apart from we haven't added any working code into the Draw() methods of each concrete object yet!

A Change In The Contract

Apart from the fact there is no code in the concrete objects to actually draw them selves, I have just noticed the need to be able to pass the Graphics object into each shape object, so we need to go back to the interfaces and change the signature for the Draw() method.

So if we take a look in the IShape interface we need to be able to pass in a Graphics object as a method parameter.

public interface IShape
 {
     /// <summary>
     /// Represents the center of the shape
     /// </summary>
     Point Center { get; set;}

     /// <summary>
     /// The method used to draw the shape
     /// </summary>
     /// <param name="graphics">A reference to the Graphics object to draw the shape on.</param>
     void Draw(Graphics graphics);
 }

And while we are in this project I think we will take the opportunity to extend the CreateShape() method signature in the IShapeFactory interface, too. We are going to add a center point parameter; this will allow us to position the shape where we like.

public interface IShapeFactory
 {
     /// <summary>
     /// Use this method to create a shape.
     /// </summary>
     /// <param name="shape">Indicates the type of shape to be made.</param>
     /// <param name="center">Indicates the center point of the shape.</param>
     /// <returns>An object that implements the IShape interface.</returns>
     IShape CreateShape(Shape shape, Point center);
 }

A quick build of the project will highlight where our change in interface has broken the contract between it and the objects that implement it so in the BaseShape object we need to add a Graphics parameter into the Draw() method. This will be the same in the Rectangle and the Circle class.

public abstract void Draw(Graphics graphics);

In the ShapeFactory class we need to extend the CreateShape(), CreateCircle() and CreateRectangle() methods signatures to include the center point parameter. Then in the CreateShape() method we need to pass the center point parameter on to the worker method calls.

/// <returns>A shape object that implements IShape.</returns>
public IShape CreateShape(Shape shape, Point center)
{

In the two worker methods, CreateCircle() and CreateRectangle() we will populate the object's Center property with the value from the center parameter passed in.

 /// <summary>
 /// This method creates a circle object.
 /// </summary>
 /// <returns>A shape object that implements ICircle.</returns>
 private ICircle CreateCircle(Point center)
 {
     Circle circle = new Circle();
     circle.Center = center;
     circle.Diameter = 50;
     return (ICircle)circle;
 }

 /// <summary>
 /// This method creates a rectangle object.
 /// </summary>
 /// <returns>A shape object that implements IRectangle.</returns>
 private IRectangle CreateRectangle(Point center)
 {
     BusinessEntity.Rectangle rectangle = 
         new BusinessEntity.Rectangle();
     rectangle.Center = center;
     rectangle.Size = new Size(400, 200);
     return (IRectangle)rectangle;
 }

Then back in the form code we need to alter the calls to the ShapeFactory CreateShape() methods to include a center position

 /// <summary>
 /// Adds shapes to the list
 /// </summary>
 private void AddShapes()
 {
     ShapeFactory shapeFactory = new ShapeFactory();
     _shapes.Add(shapeFactory.CreateShape(Shape.Circle, new Point(250,250)));
     _shapes.Add(shapeFactory.CreateShape(Shape.Rectangle, new Point(350, 200)));
 }

 /// <summary>
 /// Draws the shapes in the list
 /// </summary>
 private void DrawShapes()
 {
     foreach(IShape shape in _shapes)
     {
         shape.Draw(_graphics);
     }
 }

Back to the Drawing Board

So finally we can get back into the concrete shape classes and write the code that will draw the shape.

So let us look at the Circle's Draw() method first. We'll add the code to allow the Circle to draw it's self.

 /// <summary>
 /// Use this method to draw the shape
 /// </summary>
 public override void Draw(Graphics graphics)
 {
     Pen pen = new Pen(Color.DarkGray, 0);
     int left = (int)Center.X - (Diameter/2);
     int top = (int)Center.Y - (Diameter/2);

     System.Drawing.Rectangle outlineRectangle =
         new System.Drawing.Rectangle(left, top, Diameter, Diameter);
     graphics.DrawEllipse(pen, outlineRectangle);
 }

And the same for the Rectangle.

 /// <summary>
 /// Use this method to draw the shape
 /// </summary>
 public override void Draw(Graphics graphics)
 {
     Pen pen = new Pen(Color.Red, 0);
     int left = (int)Center.X - (Size.Width / 2);
     int top = (int)Center.Y - (Size.Height / 2);

     System.Drawing.Rectangle outlineRectangle =
         new System.Drawing.Rectangle(left, top, Size.Width, Size.Height);
     graphics.DrawRectangle(pen, outlineRectangle);
 }

Now we need to make sure we have set the start up project to be the windows application and we should be good to go!

Conclusions

Now I'm certain that the application architecture and the code may not be laid out to best practises, and I'm aware there are plenty of parts that could have been better thought out and implemented, but really I just wanted to get a simple factory based application down on paper, so to speak, to get it a little clearer in my mind.

If any of these articles have helped you or planted the seed of inquisitiveness in your mind to find out more about factories then that can only be a good thing. If there is anything I have done in this article that is quite clearly incorrect or bad practise, please feel free to leave a comment. Any constructive feed back is appreciated.

The next step will be to make the objects prinatable. But until then, have fun!

Part 1 | Part 2

2008-04-11_ShapeMaker.zip (103.25 KB) <- Solution files all zipped up.
Friday, April 11, 2008 8:44:42 PM (GMT Standard Time, UTC+00:00)  #    Comments [0] -
.Net | C# | Classes | Factory Pattern | Inheritance | Interfaces
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, b, blockquote@cite, em, i, strike, strong, sub, sup, u) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview
Archive
<April 2008>
SunMonTueWedThuFriSat
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910
Blogroll
 Clemens Vasters
 Harry Pierson
Passion * Technology * Ruthless Competence
 Joshua Flanagan
A .NET Software Developer
 Michael Schwarz's Blog
Developing applications on the Microsoft platform since Windows 3.1!
 Omar Shahine
Yet another Microsoft blogger
 Scot GU
Scott Guthrie lives in Seattle and builds a few products for Microsoft
 Scott Hanselman
Programming Life and the Zen of Computers
 Tom Mertens
Tom's corner
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2008
Duane Wingett
Sign In
Statistics
Total Posts: 17
This Year: 17
This Month: 2
This Week: 0
Comments: 2
Themes
Pick a theme:
All Content © 2008, Duane Wingett
DasBlog theme 'Business' created by Christoph De Baene (delarou)