PS2 Random Walk Simulator Windows Forms Global data using Singletons ArrayList for storing objects Serialization to Files XML Timers Animation This is a fairly extensive Problem Set with several new concepts. I have tried to give you almost all the code you will need. However, its possible I have missed out something. If I do I will post a notice on the Stellar Web Site We are going to develop a Random Walk Simulator. This will involve generating particles and moving them randomly as we time step. We will need to draw the particles on the screen and animate their movement. This problem set will lead you through the steps. First Form Start up a Console Project in Visual Studio. Add a Reference to System.Windows.Forms. Replace the code in Class1.cs with the following code. Windows provides a class called Form (System.Windows.Forms.Form ) that we can use to create a simple form. using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; John R. Williams, Kevin Amaratunga 1
namespace FirstForm public class Form1 [STAThread] static void Main() Form myform = new Form(); myform.show(); Notice that Show() pops up the Form (and a Dos Window) but then returns and the program exits removing the Form. We can turn off the Dos Window by going into the Project Properties and changing the Output Type to a Windows Application. static void Main() Form myform = new Form(); myform.showdialog(); 2 John R. Williams, Kevin Amaratunga
We need a function that does not immediately return but waits for the Form to be killed. The ShowDialog() method will do just that. Now lets add a Button to our Form. static void Main() Form myform = new Form(); myform.text = "My First Form"; Button button1 = new Button(); button1.text = "Click Me"; myform.controls.add(button1); myform.showdialog(); Notice that there is a Button class given to us in.net. We call things like buttons and menus, Controls. We can add text to the button. To display the button we must add it to the Controls collection of the Form. The class Form has an object called Controls (its not a control itself but holds a list of controls and is probably implemented as an ArrayList) that holds all the children controls of the Form. When Form renders itself it goes through that list and renders all the children in it as well. Event Based Programming What we really want is to have a Windows Form that can catch various events, such as a mouse click, and that allows us to then launch certain methods if that event occurs. This style of programming is called Event Based Programming. static void Main() Form myform = new Form(); myform.text = "My First Form"; Application.Run(myForm); To have our program sit in an infinite loop waiting for events we use the Application class s static method Run(). You should see the following Form on your screen. Notice if you click on it nothing happens at present. That s because we haven t programmed anything to happen. Lets see how we can make something happen when we click on the Form. John R. Williams, Kevin Amaratunga 3
Inheriting Form Our goal is to produce a customized form that has various buttons and child controls. If we are going to follow the Object Oriented Paradigm we should really create a new class MyForm, say that inherits Form. When we instantiate MyForm we can make sure that all the buttons and child controls we need are added to the Form. public class MyForm : System.Windows.Forms.Form public MyForm() this.text = "My First Form"; Button button1 = new Button(); button1.text = "Click Me"; this.controls.add(button1); [STAThread] static void Main() Application.Run(new MyForm()); 4 John R. Williams, Kevin Amaratunga
When we click the Button it get rendered as a depressed button which is neat but nothing else happens. Or at least it appears that nothing happens. When we click the Button, a Mouse Event, is caught by the WindowsXP operating system. The operating system sees that it is meant for the process that is running the Windows Form. The Mouse Event is passed to the Windows Form which is smart enough to know that the Button control should get that event. The Button class has many events pre-built into it. One of them is the Click event delegate. The Button class then FIRES the Click event delegate and any Methods that have been registered with that delegate get fired as well. Here is a list of some of the Events that Button supports: John R. Williams, Kevin Amaratunga 5
We ll hand code the Click event into our program. public class MyForm : System.Windows.Forms.Form public MyForm() this.text = "My First Form"; Button button1 = new Button(); button1.text = "Click Me"; this.controls.add(button1); button1.click += new EventHandler(button1_Click); [STAThread] static void Main() Application.Run(new MyForm()); private void button1_click(object sender, EventArgs e) 6 John R. Williams, Kevin Amaratunga
MessageBox.Show("Wow Click Me Again"); When the button is clicked the Message Box will now pop up. A Windows Form Project Now that we ve seen how to develop a Form the hard way we can use the Windows Application Project in Visual Studio. Open a new Project in VS and choose Windows Application You should see something like this John R. Williams, Kevin Amaratunga 7
Instead of code you will see what is called the Designer. We ll see that it makes UI development easy for us. If you right click and choose View Code you will see the code below (along with some extra clean up code I ve removed for clarity) using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace MyForm /// <summary> /// Summary description for Form1. /// </summary> public class Form1 : System.Windows.Forms.Form private System.ComponentModel.Container components = null; public Form1() InitializeComponent(); #region Windows Form Designer generated code private void InitializeComponent() this.components = new System.ComponentModel.Container(); this.size = new System.Drawing.Size(300,300); this.text = "Form1"; 8 John R. Williams, Kevin Amaratunga
#endregion [STAThread] static void Main() Application.Run(new Form1()); You ll recognize this code as being very similar to the code we produced ourselves. Beware: The IDE Designer needs control of the InitializeComponent and you should not make any changes there. If you do they may be lost. In class we saw how to add buttons and other things using the Designer. Now that we can handle Forms lets do an Exercise. John R. Williams, Kevin Amaratunga 9
Exercise Stage 1 Simple Design The user can create a number of shapes including Dots and Circles. We will first write the code to create Dots. The user creates a Dot by entering coordinates in the textboxes and then clicking on Make Shape. For now all objects are a fixed size. 10 John R. Williams, Kevin Amaratunga
We want to keep track of the total number of objects (every time we create one we need to increment a counter. See Appendix on Singleton). We will add more requirements later but for now let s see how we can implement this. We need to construct the following classes: class Shape - we ll make this a Base class so that all kinds of objects at least have this functionality Shape objects should have a Centroid, a color and be able to Draw themselves on the screen and be able to Move. class Dot:Shape This is a simple class that we will use to display Points on the Form class Circle: Shape Circle inherits Shape and should override the Draw method Drawing the objects on the screen Storing the objects in a collection of some kind so we can manipulate all of them easily Drawing Objects You can draw object directly onto a Form or you can use a PictureBox control. The latter is preferable because it supports Double Buffering so that animation is smooth and without flicker. We will use a PictureBox. The PictureBox control has a Paint event that is fired whenever the PictureBox needs to be redrawn or when we force it to be redrawn. We can add our own delegate method to the Paint event by double clicking on Paint in the Properties window of VS. John R. Williams, Kevin Amaratunga 11
private void picturebox1_paint(object sender, System.Windows.Forms.PaintEventArgs e) // Graphics g = e.graphics; // loop over all objects that need to be drawn - obj // obj.draw(graphics g); The Graphics object knows how to draw circles, arcs, squares and all kinds of useful shapes. Every Shape that we create will need its own Draw method so that it can draw itself when required. Storing Objects We also need to think about how we will manage the objects that we create. We ll do that by creating a World class that has an ArrayList into which we can place objects. Classes 12 John R. Williams, Kevin Amaratunga
Here is some code for the classes that you need. Base class Shape abstract public class Shape Point centroid; public Shape() ShapeCounter.counter.Increment(); centroid = new Point(0,0); public Point Centroid getreturn centroid; setcentroid = value; abstract public void Draw(Graphics g); Notice that we are using ShapeCounter. This is another class called a Singleton class. Read about this in the Appendix. Here is its implementation. public class ShapeCounter public static readonly ShapeCounter counter = new ShapeCounter(); private ShapeCounter() private int count = 0; public int Count setcount = value; getreturn count; public int Increment() return count++; This is a very slick class. See if you can see why its written the way it is. Its based on the Design Pattern shown here.net Singleton Example //.NET Singleton sealed class Singleton private Singleton() John R. Williams, Kevin Amaratunga 13
public static readonly Singleton Instance = new Singleton(); Derived class Dot public class Dot:Shape public Dot():base() public override void Draw(System.Drawing.Graphics g) g.fillellipse(brushes.red, Centroid.X,Centroid.Y,5,5); Derived class Circle public class Circle:Shape private int radius; public Circle():base() // Default Constructor public Circle(int rad):base() radius = rad; public int Radius getreturn radius; setradius = value; public override void Draw(System.Drawing.Graphics g) g.fillellipse(brushes.blue, Centroid.X,Centroid.Y,Radius,Radius); World Storage Class for Objects public class World public static World myworld = new World(); public World() static World() World.myWorld.worldList = new ArrayList(); private ArrayList worldlist; public ArrayList WorldList 14 John R. Williams, Kevin Amaratunga
getreturn worldlist; public static void Draw(Graphics g) foreach(shape s in World.myWorld.WorldList) s.draw(g); Making Objects Here is some code that will make a Dot object when the button is clicked. Notice that we can move the Dot to a new position by changing its Centroid. We could possibly do this dynamically and animate the Dots so that move around. We ll maybe do that later. private void button1_click(object sender, System.EventArgs e) // create a dot Dot d = new Dot(); // maybe we should pass in Centroid int x = Convert.ToInt32(this.textBox1.Text); int y = Convert.ToInt32(this.textBox2.Text); d.centroid = new Point(x,y); World.myWorld.WorldList.Add(d); this.textbox3.text = ShapeCounter.counter.Count.ToString(); this.refresh(); Make sure you get this to work before going any further. Adding Circles Now change the User Interface so that you have a second button called Circle that adds a circle at the given coordinates. For now make the radius a fixed size. The code will look almost exactly like the button_click above but you need to create a Circle instead of a Dot. John R. Williams, Kevin Amaratunga 15
Stage 2 - Serializing the World to a File One of the things that would be good to do would be to be able to store our World as a file. There is an easy way to do this and its called Serialization. Basically, serialization takes complex types like Circle or Dot and expresses them in terms of their in-built types, such as int etc. It does this by creating XML. We haven t talked about XML yet but you can still use it without really knowing too much about it. Here is what the serialized World looks like when it contains a Dot, a Circle and another Dot. <?xml version="1.0" encoding="utf-8"?> <World xmlns:xsd="http://www.w3.org/2001/xmlschema" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"> <WorldList> <Dot> <Centroid> <X>50</X> <Y>70</Y> </Centroid> </Dot> <Circle> <Centroid> <X>50</X> <Y>170</Y> </Centroid> <Radius>40</Radius> </Circle> <Dot> <Centroid> <X>15</X> <Y>170</Y> </Centroid> </Dot> </WorldList> </World> Here is the code to serialize and deserialize the World. Put it in the World class. 16 John R. Williams, Kevin Amaratunga
public void Serialize() XmlSerializer myserializer = new XmlSerializer(typeof(World)); // To write to a file, create a StreamWriter object. StreamWriter mywriter = new StreamWriter("World.xml"); myserializer.serialize(mywriter, myworld); mywriter.close(); public void DeSerialize() XmlSerializer myserializer = new XmlSerializer(typeof(World)); // To write to a file, create a StreamWriter object. StreamReader myreader = new StreamReader("World.xml"); myworld = (World)mySerializer.Deserialize(myReader); myreader.close(); In order to make serialization work we also need to do the following: 1. using System.Xml.Serialization; 2. put [Serializable] on the Dot and Circle class [Serializable] public class Circle:Shape Also when we store objects, such as Circle and Dot in an ArrayList we need to give the ArrayList some clues about how to serialize the objects. We do this by adding [XmlArrayItem(typeof(Dot),ElementName = "Dot")] [XmlArrayItem(typeof(Circle),ElementName = "Circle")] as shown in the code below [Serializable] public class World public static World myworld = new World(); public World() static World() World.myWorld.worldList = new ArrayList(); private ArrayList worldlist; [XmlArrayItem(typeof(Dot),ElementName = "Dot")] [XmlArrayItem(typeof(Circle),ElementName = "Circle")] public ArrayList WorldList getreturn worldlist; setworldlist = value; John R. Williams, Kevin Amaratunga 17
Here are some things you need to know about Serialization. Only classes marked [Serializable] will be serialized There must be a public constructor for the class (I had to add one to Circle and you should do the same) Only members of the class marked public will be serialized (in the code above the private ArrayList worldlist is NOT serialized but the public ArrayList WorldList property IS serialized. Notice we only need one of them to get the information we want written out. Modify the design of your code as shown below so that the Serialize button Serializes the World to the file World.xml. Notice that it writes the file under the bin/debug directory. The Clear All button should clear the World ArrayList of all objects. Then DeSerialize should re-create the World by reading from the file World.xml. This is really neat. Now we can edit the World.xml file by hand and say add in new objects. Then when we read it back in all the new objects will be automatically created. 18 John R. Williams, Kevin Amaratunga
John R. Williams, Kevin Amaratunga 19
You should now have a program that looks like this. 20 John R. Williams, Kevin Amaratunga
See if you can read in this file. If it looks different to yours then don t spend too much time on it. Your code may just be a little different to mine. However, wouldn t it be neat if we could exchange files and I could define say a complete model for you just by giving you an XML file. <?xml version="1.0" encoding="utf-8"?> <World xmlns:xsd="http://www.w3.org/2001/xmlschema" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"> <WorldList> <Dot> <Centroid> <X>50</X> <Y>70</Y> </Centroid> </Dot> <Circle> <Centroid> <X>50</X> <Y>87</Y> </Centroid> <Radius>40</Radius> </Circle> <Dot> <Centroid> <X>150</X> <Y>87</Y> John R. Williams, Kevin Amaratunga 21
</Centroid> </Dot> <Circle> <Centroid> <X>150</X> <Y>187</Y> </Centroid> <Radius>40</Radius> </Circle> <Dot> <Centroid> <X>150</X> <Y>17</Y> </Centroid> </Dot> <Dot> <Centroid> <X>10</X> <Y>17</Y> </Centroid> </Dot> </WorldList> </World> Here is what it should look like (I may have the extra Animate button). 22 John R. Williams, Kevin Amaratunga
Stage 3 Animation We want to move our objects around so they behave like molecules bouncing around a box. Lets see if we can figure out how to do this. We know we can update the position of Dots and Circles by changing their Centroid (which is part of the Shape base class). So if we update each Shape and then Redraw all the objects maybe it will look like animation. We need some kind of Timer so that every tick we move the objects and redraw. Fortunately there is one available. From the ToolBox drag a Timer onto the Form. This will create a Timer object called timer1. Double click on the Timer in the Designer and it will automatically create an Event called Tick and a function called timer1_tick Here is my version of what we should do every time there is a Tick event. private void timer1_tick(object sender, System.EventArgs e) // update the positions of the objects Random random = new Random(9); foreach(shape s in World.myWorld.WorldList) int newx = s.centroid.x + random.next(4); int newy = s.centroid.y + random.next(5); s.centroid = new Point(newx,newy); this.refresh(); You may need to set the timer Interval (the time between ticks in milliseconds (1000 = 1sec) timer1.interval = 200 say. You should see the Timer on the Designer as shown below. John R. Williams, Kevin Amaratunga 23
The Random Walk Simulator You now have all the tools you need to make the Random Walk Simulator. Below is an example of the one I wrote. It generates particles every time step at the origin. It applies a bias to the random motion so as to simulate a wind say dispersing particles from a chimney stack. The Animate button sets the simulator in motion. I can alter the speed at which it runs using the slider at the bottom. Think about how you will manage the particles so that you don t waste CPU cycles. For example, once the particles move out of the Problem Space you might want to destroy them. Otherwise, we will end up simulating ever more particles. 24 John R. Williams, Kevin Amaratunga
John R. Williams, Kevin Amaratunga 25