Monday, August 25, 2008

Serialization

Before we begin with main program let us now see how to attach a menu to a blank form. Carry out the following steps:

  1. Drag in the 'MainMenu' control from the toolbox and release it on the blank form.

  2. Click on the control and type the menu item name as 'File'.

  3. Add the items 'New', 'Open', 'Save', 'Generate' and 'Exit' to the 'File' menu in a similar manner.

The resultant form is shown in the following figure.

Each menu item is governed by a control variable of the type MenuItem. We can change the names of these variables through the 'Properties' window. For the program that follows we have used the names as filemenu that will hold the other menu items, newmenu, openmenu, savemenu, generatemenu and exitmenu.

Let us now decide what should happen when these menu items are selected. When we select the �Generate� menu item, shapes like rectangle, ellipse and line should get generated at random and in random colors. On selecting the �Save� menu item these shapes should be saved in a file. We must be able to load this file and display the shapes again. This would be achieved through the �Open� menu item. When we click the �New� menu item the earlier shapes should vanish and we must get a new form to draw new shapes.

While saving the shapes we should not save the image of the shape. Instead we should save the relevant information of the shape using which we should be able to regenerate the shape again when the file is loaded. This means we must write the object onto the disk while saving it and load it back while opening the file. This process of writing the state of an object is called serialization and reading it back is called deserialization.

To make all these activities to happen we need to add handlers for these menu items. The Windows Forms programming model is event based. When we click on a menu item it raises an event. In order to handle an event, our application should register an event-handling method. For adding these handlers click on the menu item for which we wish to add the handler. Then go to the 'Properties' window and select the 'Events' tab (shown by a yellow lightening icon). From the list of events select 'Click'. As a result an event handler called say, openmenu_Click( ) would get added to our code. On similar lines rest of the handlers can be added. All these handlers will get added to the Form1 class, which is the default class name.

Before we add code to these menu handlers let us insert four new classes. The first amongst these is an abstract class called shapes. In this class we will store the color of the shape in variables r, g, b, representing red, green and blue components of the color. The other three classes that we would add are line, rectangle and ellipse. These classes are derived from the shapes class. In these classes we will store the coordinates of the shapes. Each of these three classes would have a constructor to initialize the data members. Each class would have a function draw( ) which would contain the logic to draw the respective shape. This function would be declared as abstract in the base class shapes. To make all the classes capable of carrying out serialization/deserialization we need to add the attribute Serializable as shown in the program listing given below.

using System ;
using System.Drawing ;
using System.Collections ;
using System.ComponentModel ;
using System.Windows.Forms ;
using System.Data ;
using System.IO ;
using System.Threading ;
using System.Runtime.Serialization ;
using System.Runtime.Serialization.Formatters.Binary;

namespace myapp
{

[Serializable]
abstract class shapes

{

protected int r, g, b ;
Random rd = new Random( ) ;
public shapes( )
{

r = rd.Next ( 255 ) ;
g = rd.Next ( 255 ) ;
b = rd.Next ( 255 ) ;

// put the thread to sleep for next 5
// milliseconds to ensure proper color
// generation
Thread.Sleep ( 5 ) ;

}

public abstract void draw ( Graphics g ) ;

}

[Serializable]
class line : shapes
{

int x1, y1 ;
int x2, y2 ;

public line ( int i, int j, int k, int l )
{

x1 = i ;
y1 = j ;
x2 = k ;
y2 = l ;

}

public override void draw ( Graphics gg )
{

Color c = Color.FromArgb ( r, g, b ) ;
Pen p = new Pen ( c, 4 ) ;
gg.DrawLine ( p, x1, y1, x2, y2 ) ;

}

}

[Serializable]
class rectangle : shapes
{

int x1, y1 ;
int width, height ;

public rectangle ( int x, int y, int h, int w )
{

x1 = x ;
y1 = y ;
height = h ;
width = w ;

}

public override void draw ( Graphics gg )
{

Color c = Color.FromArgb ( r, g, b ) ;
Pen p = new Pen ( c, 4 ) ;
gg.DrawRectangle ( p, x1, y1, width, height ) ;

}

}

[Serializable]
class ellipse : shapes
{

int x1, y1 ;
int width, height ;

public ellipse ( int x, int y, int h, int w )
{

x1 = x ;
y1 = y ;
height = h ;
width = w ;

}

public override void draw ( Graphics gg )
{

Color c = Color.FromArgb ( r, g, b ) ;
Pen p = new Pen ( c, 4 ) ;
gg.DrawEllipse ( p, x1, y1, width, height ) ;

}

public class Form1 : System.Windows.Forms.Form
{

private System.Windows.Forms.MainMenu
mainMenu1 ;
private System.Windows.Forms.MenuItem
filemenu ;
private System.Windows.Forms.MenuItem
newmenu ;
private System.Windows.Forms.MenuItem
openmenu ;
private System.Windows.Forms.MenuItem
savemenu ;
private System.Windows.Forms.MenuItem
generatemenu ;
private System.Windows.Forms.MenuItem
exitmenu ;
private System.ComponentModel.Container
components = null ;
ArrayList s = new ArrayList( ) ;
BinaryFormatter b = new BinaryFormatter ( ) ;

public Form1( )

{

InitializeComponent( ) ;

}

protected override void Dispose ( bool disposing )

{

if( disposing )

{

if ( components != null )
{

components.Dispose( ) ;

}

}

base.Dispose( disposing ) ;

}

[STAThread]

static void Main( )

{

Application.Run ( new Form1 ( ) ) ;

}

private void openmenu_Click ( object sender, System.EventArgs e )
{

OpenFileDialog od = new
OpenFileDialog( ) ;
od.Filter = "dat files ( *.dat )|*.dat" ;

if ( od.ShowDialog( ) == DialogResult.OK )
{

FileInfo f=new FileInfo ( od.FileName);
Stream st = f.Open ( FileMode.Open );

while ( st.Position != st.Length )

s.Add ( b.Deserialize ( st ) ) ;

st.Close ( ) ;

}

Invalidate( ) ;

}

private void savemenu_Click ( object sender, System.EventArgs e )

{

SaveFileDialog sd = new
SaveFileDialog( );
sd.Filter = "dat files ( *.dat ) | *.dat" ;

if ( sd.ShowDialog( ) == DialogResult.OK )
{

FileInfo f = new FileInfo(sd.FileName);
Stream st = f.Open ( FileMode.Create,
FileAccess.ReadWrite ) ;
foreach ( shapes ss in s )
b.Serialize ( st, ss ) ;

st.Close ( ) ;

}

}

private void generatemenu_Click ( object sender, System.EventArgs e )
{

Size sz = ClientSize ;
Random rd = new Random( ) ;

for ( int i = 0 ; i < 10 ; i++ )
{

int shapeno = rd.Next ( 3 ) ;
int x1 = rd.Next ( sz.Width ) ;
int y1 = rd.Next ( sz.Height ) ;
int x2 = rd.Next ( sz.Height - y1 ) ;
int y2 = rd.Next ( sz.Width - x1 ) ;

switch ( shapeno )
{

case 0:

s.Add ( new line ( x1, y1, x2, y2 ) ) ;
break ;

case 1:

s.Add ( new rectangle ( x1, y1, x2, y2 ) ) ;
break ;

case 2:

s.Add ( new ellipse ( x1, y1, x2, y2 ) ) ;
break ;

}

}

Invalidate( ) ;

}

private void exitmenu_Click ( object sender, System.EventArgs e )

{

Dispose( ) ;

}

private void Form1_Paint ( object sender,
System.Windows.Forms.PaintEventArgs e )
{

Graphics g = e.Graphics ;
foreach ( shapes ss in s )
ss.draw ( g ) ;

}

private void newmenu_Click ( object sender, System.EventArgs e )
{

s.Clear( ) ;
Invalidate( ) ;

}

}

}

When we click the �Generate� menu item its handler gets called. In this handler an object of the Random class is created. The Next( ) method of this class generates a positive random number less than the specified number passed to it.

We have used this function to not only decide which shape should be generated but also the coordinates of this shape. Using these coordinates we have created an object of rectangle, ellipse or line class. While creating these objects the constructors of the respective classes get called. Since all these classes are derived from the shapes class, firstly the base class constructor gets called. This constructor selects a random color.

The references of objects of line, rectangle and ellipse are stored using a collection class called ArrayList. The object s of this class now consists of references of objects of the line, rectangle and ellipse classes. As these classes are derived from shapes, it is perfectly legitimate for a reference to shapes to be set up to point to either shapes or one of its derived classes.

Next we have called the Invalidate( ) method which results in the Form1_Paint( ) method getting called. Here we have collected back each reference from the array s into a reference ss of type shapes. Using this reference it has then called the draw( ) method. Depending upon which (line, rectangle or ellipse) reference is present in ss the draw( ) function of that class gets called. The resulting form is shown below.

If we wish to save the file we can click on �Save� menu item. When we do so savemenu_Click( ) gets called. We have created an object of the SaveFileDialog class and used a �*.dat� filter for it. When we type in a file name and click Ok, a FileInfo object gets created with the selected name. The Open( ) function returns a Stream object associated with the file. We have collected it in a Stream reference. We have used the BinaryFormatter object to serialize the objects. The BinaryFormatter serializes and deserializes an object, in binary format. We have added a BinaryFormatter object b to our class. The Serialize( ) method of this class serializes the object to the given stream. After serializing all the elements of the array we have closed the stream using the Close( ) method.

When we click �Open� menu item an OpenFileDialog is popped with the appropriate filter. Here also we have created a FileInfo object and collected the corresponding Stream of the specified file. Next we have used a while loop to deserialize the objects until the end of stream is reached. The Deserialize( ) method deserializes the specified stream into an object. We have collected these objects into our array, closed the stream and called Invalidate( ) function for painting these shapes.

When we click �New� menu item the array is cleared by deleting all the elements in the array. After this we have called Invalidate( ). This time the method Form1_Paint( ) draws nothing as the array is empty, thereby resulting a clean form.

On clicking the �Exit� menu item the Dispose( ) method gets called and the form is disposed.

1 comment:

RGHill said...

Thanks Sandy

I found this very helpful!

Rory