From iLab Cookbook

INetwork: iNetwork Tutorial #3.2: Drawing Board 2.0

back to iNetwork

Contents

Introduction

Summary

This Drawing Board 2.0 tutorial example is an extension of and thus builds on its code and composition.

Just as the previous version, this example simulates a communal drawing board the allows for multiple users to draw collaboratively in the same space. As far as functionality goes version 2.0 is exactly the same as version 1.0 with the exception of one additional feature: telepointers. These telepointers are used to show and track the mouse movements of other users on a particular users screen. This way each user is able to see where on the canvas the other users are. A user's telepointer also changes in color according to the color, from the palette, they are using.

High Level Overview

When a client connects a telepointer is created which is tracked by the client's ID number. This telepointer defaults to black, since the default brush color is also black, and is shown to all other clients. This means that everyone can see the telepointer with the exception of the client itself. During a MouseMove coordinates are given out according to client ID's which allows the telepointers to track the user's mouse movements. The telepointers are still available when users are drawing on the board, on a MouseDown. Selecting a color, MouseLeftButtonDown, from the palette also changes a client's telepointer accordingly.

In the case that a client connects in the middle of an on going session, the server, which has been keeping track of client ID's and color selections sends the most recent information to this client. Therefore they are also able to see the telepointers for clients who have been previously connected.

Reminder: The executable file, called DrawClient, for this example has been provided in a folder named Tutotrial3.2 when the toolkit was installed. You will find this in the Tutorial Examples folder within the GroupLab iNetwork Toolkit directory. Be sure to extract the files before opening them.

back to top

Setting Up the Form

Note: If you had completed the Drawing Board 1.0 tutorial example as recommended then you no longer have to build the form from scratch. If you did not complete the aforementioned tutorial example, please do so before attempting this tutorial.

  1. Given the form that had been created in the Drawing Board 1.0 tutorial example, in the desginer view (XAML) add a new Canvas. Be sure to place and size it so that it does not cover any of the other elements on the UI.

    • In the XAML file the code for this new canvas should look something like the following:
      <Canvas Canvas.Left="12" Canvas.Top="104" Height="24" Name="_extraCanvas" Width="35" Background="White" IsHitTestVisible="False" AllowDrop="True" Opacity="1"></Canvas>
    • Ensure that IsHitTestVisible is not selected.

back to top

Adding the Code

This part of the tutorial will build on the code provided in the Drawing Board 1.0 tutorial example.

Client

Open the C# file for the client.

Class Instance Variables Region
  1. Begin by declaring the following:
    1. private int _clientID;
    2. private Dictionary<int, Ellipse> _telepointers;
    • _clientID is an integer chosen at random to be the client's ID number.
    • _telepointers is a dictionary that keeps track of client ID's and their corresponding telepointers.
Initialization Region
  1. In the OnConnectionDiscovered event handler method randomly generate an integer and assign it to be the client's ID.
    1. Random rand = new Random();
    2. this._clientID = rand.Next();
  2. Next create and send a message called OnConnect to the server. This message will contain the client's ID.
    1. Message newMsg = new Message("OnConnect");
    2. newMsg.AddField("client", this._clientID);
    3. this._connection.SendMessage(newMsg);
Main Body Region

Note: This region is called Drawing within the executable files provided during installation.

  1. In the OnMessageReceived event handler method add the following cases:
    1. case "OnConnect":
    2.     ...
    3.     break;
    4. case "AddOtherPointers":
    5.     ...
    6.     break;
    7. case "MoveTelepointer":
    8.     ...
    9.     break;
    10. case "ColorSelect":
    11.     ...
    12.     break;
  2. Before anything else, declare the following instance variables before the if statement in the OnMessageReceived event handler method:
    1. int clientID;
    2. string colorCode;
    3. Color colorFill;
  3. If the message is the OnConnect message the client ID and the brush color is retrieved.
    1. clientID = msg.GetIntField("client");
    2. colorCode = msg.GetStringField("fill");
  4. The brush color is currently a string object and needs to be a Color object in order to be of use. Convert the brush color then call the CreateTelepointer method.
    1. colorFill = (Color)ColorConverter.ConvertFromString(colorCode);
    2. CreateTelepointer(clientID, colorFill);
  5. If the message is the AddOtherPointers message add the exact same tasks as in the case of the OnConnect message. Retrieve both the client ID and the brush color and convert it into a Color object then call the CreateTelepointer method.
    1. clientID = msg.GetIntField("client");
    2. colorCode = msg.GetStringField("fill");
    3. colorFill = (Color)ColorConverter.ConvertFromString(colorCode);
    4. CreateTelepointer(clientID, colorFill);
  6. If the message is the MoveTelepointer message, again retrieve the client's ID.
    1. clientID = msg.GetIntField("client");
  7. This time, instead of retrieving brush color, get the 'x' and 'y' values that are to be use to set the coordinates for the telepointer.
    1. double teleX = msg.GetDoubleField("x");
    2. double teleY = msg.GetDoubleField("y");
  8. Then call the MoveTelepointer method.
    1. MoveTelepointer(clientID3, teleX, teleY);
  9. Lastly, if the message is the ColorSelect message, just as both the OnConnect and AddOtherPointers messages, retrieve the client ID and the brush color and convert it into a Color object.
    1. clientID = msg.GetIntField("client");
    2. colorCode = msg.GetStringField("fill");
    3. colorFill = (Color)ColorConverter.ConvertFromString(colorCode);
  10. This time call the ChangePointerColor method.
    1. ChangePointerColor(clientID, colorFill);
  11. Create the methods mentioned above, starting with the CreateTelepointer method, which is responsible for, using the GUI thread, creating the circles that will stand in as each client's telepointer.
    1. private void CreateTelepointer(int id, Color fill)
    2. {
    3.     this.Dispatcher.Invoke(
    4.         new Action(
    5.             delegate()
    6.             {...}
    7.     ));
    8. }
  12. Within delegate() [line 6, step 14], impose the following conditions where the client ID given as input is not equal to the current client's ID and a pre-existing telepointer has not already been made for the ID. The former condition ensures that a user does not see their own telepointer. A client's telepointershould appear on all other clients' UI but not their own. The latter condition ensures that a duplicate telepointer is not made.
    1. if (id != this._clientID && !this._telepointers.ContainsKey(id))
    2. {...}
  13. Now within that if statement [line 2, step 15], create a SolidBrushColor object using the fill input to assign the color and a new Ellipse object.
    1. SolidColorBrush brush = new SolidColorBrush(fill);
    2. Ellipse pointer = new Ellipse();
  14. Create the ellipse to be a circle with a diameter of 10 pixels and use the SolidBrushColor object to assign the fill color of the ellipse.
    1. pointer.Width = 10;
    2. pointer.Height = 10;
    3. pointer.Fill = brush;
  15. Change its opacity to 0 so that it remains invisible for now then add it to both the telepointer dictionary and the _extraCanvas. The reason the telepointers are added to the _extraCanvas canvas is to make sure that the they remain on top of the path drawings, meaning a high z index. Using this canvas, which is already on top of the _drawingCanvas canvas, ensures that the telepointers will never be obscured from view by any drawn path.
    1. pointer.Opacity = 0;
    2. this._telepointers.Add(id, pointer);
    3. this._extraCanvas.Children.Add(pointer);
  16. Next, create the MoveTelepointer method which, through the GUI thread, ensures that the telepointer that corresponds to a particular client is visible and moves according to the coordinates given from the mouse movements.
    1. private void MoveTelepointer(int id, double x, double y)
    2. {
    3.     this.Dispatcher.Invoke(
    4.         new Action(
    5.             delegate()
    6.             {...}
    7.     ));
    8. }
  17. In the delegate() section [line 6, step 19], add an if statement that checks that _telepointers contains the client ID and that the ID given as input does not belong to the current client.
    1. if (this._telepointers.ContainsKey(id) && id != this._clientID)
    2. {...}
  18. Within the above conditional statement [line 2, step 20], retrieve the ellipse from the telepointers dictionary that corresponds to the client ID provided. then change its opacity to '1' so that it becomes visible and, via TranslateTransform, move the telepointer to the given 'x' and 'y' coordinate values. The off set values ('x-5' and 'y+25') compensate for the placement of the _extraCanvas that was added in the form. It ensures that the center of the telepointer is where the tip of the mouse cursor would be.
    1. Ellipse pointerToMove = this._telepointers[id];
    2. pointerToMove.Opacity = 1;
    3. pointerToMove.RenderTransform = new TranslateTransform(x - 5, y + 25);
  19. Next, create the ChangePointerColor method, which, via the GUI thread, changes the color of the telepointer for a certain client according to that client's color choice through the palette.
    1. private void ChangePointerColor(int id, Color fill)
    2. {
    3.     this.Dispatcher.Invoke(
    4.         new Action(
    5.             delegate()
    6.             (...}
    7.     ));
    8. }
  20. Again, within delegate() [line 6, step 22], create a SolidColorBrush object, grab the appropriate telepointer ellipse and change its fill color.
    1. SolidColorBrush brushColor = new SolidColorBrush(fill);
    2. Ellipse pointer = this._telepointers[id];
    3. pointer.Fill = brushColor;
  21. In order for the telepointers to work a message needs to be sent containing mouse coordinates during a MouseMove event. In the OnMouseMove event handler, before the if statement, create a message called MoveTelepointer.
    1. Message teleMsg = new Message("MoveTelepointer");
  22. Add the client's ID, and the 'x' and 'y' values for the mouse coordinates to the message.
    1. teleMsg.AddField("client", this._clientID);
    2. teleMsg.AddField("x", e.GetPosition(this._drawingCanvas).X;
    3. teleMsg.AddField("y", e.GetPosition(this._drawingCanvas).Y;
  23. Then make sure to send the message to the server.
    1. this._connection.SendMessage(teleMsg);
  24. Lastly, in each event handler method that looks after color selections, create and send a message called ColorSelect containing the client's ID and the current selected color.
    1. Message msg = new Message("ColorSelect");
    2. msg.AddField("client", this._clientID);
    3. msg.AddField("fill", this._selectedColor.ToString());
    4. this._connection.SendMessage(msg);
back to top

Server

Open up the C# code file for the server.

Class Instance Variables Region
  1. Create a dictionary that will contain client ID's and color codes.
    private Dictionary<int, string> _clientIDs;
Initialization Region
  1. Before calling the RedrawPath method in the OnServerConnection event handler method, a message needs to be sent to the connecting client for each available client within the ID and colors dictionary. This allows the client to create a telepointer for each client that was already connected before it.
    1. foreach (var pair in this._clientIDs)
    2. {
    3.     Message teleMsg = new Message("AddOtherPointers");
    4.     teleMsg.AddField("client", pair.Key);
    5.     teleMsg.AddField("fill", pair.Value);
    6.     this._server.BroadcastMessage(teleMsg, this._clients.Where(client => client != e.Connection).ToList());
    7. }
Main Body Region

Note: This region is called Drawing within the executable files provided during installation.

  1. Added the following message cases to the OnConnectionMessage event handler method.
    1. case "OnConnect":
    2.     ...
    3.     break;
    4. case "MoveTelepointer":
    5.     ...
    6.     break;
    7. case "ColorSelect":
    8.     ...
    9.     break;
  2. If the message is the OnConnect message retrieve the client ID and add a new field containing the color code for black as a string. The color of the telepointer is added in the server code to ensure that a newly connect client will always start with a black telepointer, as default.
    1. int clientID = msg.GetIntField("client");
    2. msg.AddField("fill", Brushes.Black.ToString());
  3. Add both the client ID and the color code to the dictionary and broadcast the message to all clients except for the client who sent the message.
    1. this._clientIDs.Add(clientID, Brushes.Black.ToString());
    2. this._server.BroadcastMessage(msg, (Connection)sender);
  4. If the message is the MoveTelepointer message simply broadcast the message to all clients.
    1. this._server.BroadcastMessage(msg);
  5. If the message is the ColorSelect message retrieve both the client ID and the color code.
    1. int clientID = msg.GetIntField("client");
    2. string fill = msg.GetStringField("fill");
  6. Update the color code for the given client ID in the dictionary then broadcast the message to all clients except for the one who sent the message.
    1. this._clientIDs[clientID] = fill;
    2. this._server.BroadcastMessage(msg, (Connection)sender);
back to top

Result

Reminder: Right click on DrawServer in Solution Explorer and select Set as StartUp Project. Now run the server then right click on DrawClient in the Solution Explorer and select Debug then Start a new instance to run/add a new instance of the client.

You should get something like the following:

back to top
Retrieved from http://grouplab.cpsc.ucalgary.ca/cookbook/index.php/INetwork/Tutorial3-2
Page last modified on August 29, 2012, at 03:53 PM