Click here to Skip to main content
Click here to Skip to main content

A Simple 2 User Whiteboard application in C#/.NET

, 17 Jul 2002
Rate this:
Please Sign up or sign in to vote.
This article is my attempt at developing an extremely simple Whiteboard application in C#/ WinForms and sockets for messaging, to be used simultaneously by 2 users.
<!-- Download links --> <!-- Article HTML -->

Introduction

>This article is my attempt at developing an extremely simple whiteboard application to be used simultaneously by two users. Although I started my attempt with support for more than two users, I dropped it to two to reduce the complexity of the project.

Here are some of the features this whiteboard supports:

  • It allows two users to connect to each other, with either one running as a listener with a valid IP on a valid port, and the other one running as a client that connects to the listener’s IP/Port.
  • It allows 4 drawing primitives, namely scribbling via a pencil tool, drawing rectangles with a rubberbanding like effect in Paintbrush, drawing ellipses, clearing the screen.

System Requirements

To compile the solution you need to have Microsoft Visual Studio .NET installed. To run any of the client executable you need to have the .NET framework installed.

Here is a snapshot that allows the client and server both running on the same machine, but make sure the correct IP is specified in the "ConnectTo" box.

Starting the application:

To start the application, do the following:

  • Fire up an instance of the application on one machine.

  • Select the mode this instance would run in, i.e. client mode or server mode. To make it run as a server listener, select the "Start as Listener" option as shown and accept the default 8888 as the listening port, unless you have another application on your system listening on it, then click 'Start listening'

    Note: Unfortunately this application is unable to accept requests from another Whiteboard client if it's behind a firewall, to make it work you need to poke a hole in the firewall for port 8888.(eeeeks !)

  • Fire up another instance of the Whiteboard application on another machine. This time select the Connect to option as shown:

  • The ConnectedPeers title on the top right would add a node with the peer's IP.

A user drawing on one end is reflected on the other, i.e. shown below at the listener end.

Sorry, Chris, that's the best I could get with my limited drawing skills as well as the limited no of drawing tools provided in this app.

Explaining the implementation

The following diagram illustrates the internal architecture of the app.

The application consists of a WinForms based client which contains a couple of UI controls for user input, like textboxes for IP address/port and treeview control, toolbar, buttons, radio buttons, etc. It contains a custom control which takes care of all the drawing related code, see DrawAreaCtrl.cs. To persist the scribbled data on the whiteboard, the custom control has code that makes EXACT changes as that made on the whiteboard drawing area on a Bitmap object. This ensures that the changes made aren’t lost.

protected override void OnPaint(PaintEventArgs e)
{
    Graphics g = e.Graphics;
    g.DrawImage(m_bmpSaved, 0,0,m_bmpSaved.Width, m_bmpSaved.Height);
}

The UI has two modes of operation:

  • Server mode (Listener)
  • Client mode (Connects to a listener)

If you select the first mode as shown above the application starts listening on the machine’s IP on the port specified as shown in the following snippet.

    m_enNetMgrMode = NETWORK_MANAGER_MODE.enServerMode;
    string StrHostName        = Dns.GetHostName();
    m_SockListener            = new Socket(0, SocketType.Stream, ProtocolType.Tcp);
    IPHostEntry IpEntry        = Dns.GetHostByName (StrHostName);
    IPAddress [] IpAddress    = IpEntry.AddressList; 
    IPEndPoint LocEndpoint    = new IPEndPoint(IpAddress[0], m_iPort);
    m_SockListener.Bind(LocEndpoint);
    m_SockListener.Blocking = true;
    m_SockListener.Listen(-1);
    m_SockListener.BeginAccept(m_AsyncCallbackAccept, m_SockListener);

If you select the second option AFTER SPECIFYING the correct IP/Port of another listening whiteboard, the app connects to the server as shown.

    IPAddress    hostadd    = Dns.Resolve(StrHostIp).AddressList[0];
    int            iHostPort = Convert.ToInt32(StrHostPort);
    IPEndPoint    EPhost    = new IPEndPoint(hostadd,iHostPort);

    m_StrHostIp = StrHostIp;
    m_StrHostPort = StrHostPort; 
    m_SockServer = new Socket(
        AddressFamily.InterNetwork, 
        SocketType.Stream,
        ProtocolType.Tcp 
        );
    try
    {
        m_SockServer.Connect(EPhost);
        m_Creator.EnableDisableConnectModeControls(true);
        string msg = "Connected to remote Whiteboard on: " + StrHostIp ;
        msg += " listening on port: " + StrHostPort;
        m_Creator.SetMusicalStatus(msg, "ding.wav");
        m_HndlrForListeningWB = new ClientHandler(m_SockServer, m_Creator);
        m_HndlrForListeningWB.ProcessClientRequest();
            
    }
    catch(NullReferenceException exception)
    {
        m_Creator.SetStatus ("Connect failed. " + exception.Message);
        m_SockServer = null;
    }

Messaging architecture

Once the socket is established between the two users, each peer communicates with the other peer through something called WhiteBoard Messages.(see WhiteBoardMessages.cs). This file contains an abstract class called WBMessage that every other message like WBMsgDrawBegin, WBMsgDrawEnd, WBMsgDrawLine, WBMsgDrawRectangle and WBMsgDrawEllipse inherits from.

For example:

[Serializable]
public class WBMsgDrawBegin :  WBMessage 
{
    //Implement the abstract property 
    private const WHITEBOARD_MESSAGE_TYPE m_enMsgType = 
                                  WHITEBOARD_MESSAGE_TYPE.enWBBegin;
    public override WHITEBOARD_MESSAGE_TYPE MessageType
    {
        get
        {
            return m_enMsgType;
        }
    }
    public Point m_PtBegin;
    public bool  m_bMouseDown;
}

Notice the Serializable attribute before each of these classes. The static methods of the class WBMessageHelper (Serialize, Deserialize, DeserializeL) are the heart of the message encoding/decoding infrastructure of the application.

.NET provides object serialization support through the use of Formatter classes like the BinaryFormatter and SoapFormatter.

This application keeps transporting mouse messages (mousedown, mousemove and mouseup coordinates) from one user to another remote user. Using the SoapFormatter would have meant transporting a LOT more data (since SOAP is an XML based serialization mechanism) than in a Binary Format.

Without BinaryFormatters I could have sent raw structs with longs, ints and floats within them and manipulated byte pointers in unsafe code. But I decided to use what .NET already provides without delving into the unsafe world.

BinaryFormatters serializes and deserializes an object, or an entire graph of connected objects, in binary format. For example, the following function serializes a long into a memory stream object whose raw buffer contents could be passed on the wire through the opened socket and then unpacked with a similar Deserialize routine:

public static MemoryStream Serialize(long lObject)
{
    MemoryStream ms            = new MemoryStream(); 
    BinaryFormatter formatter    = new BinaryFormatter();
    formatter.AssemblyFormat    = FormatterAssemblyStyle.Simple;
    formatter.TypeFormat        = FormatterTypeStyle.TypesWhenNeeded;
    formatter.Serialize(ms, lObject);
    return ms;
}

The weird thing about BinaryFormatters is that even a single long of 8 bytes when serialized into a memory stream takes up 56 bytes of buffer space, which is seven times bigger than just sending a raw long of 8 bytes.

Lets take a use case on how the Scribble line tool works (assuming 2 peers connected to each other with one as listener and other connecting to it) When someone clicks on the scribble button on the toolbar.

The DrawAreaCtrl custom control shifts to the WHITEBOARD_DRAW_MODE of enModeLine

Once this draw mode is enabled, all mouse events are interpreted for drawing related to scribbling lines.

MouseDown

On a mousedown an instance of the serializable class WBMsgDrawBegin is created. This class is then serialized to a byte buffer using the BinaryFormatter class and sent across the socket. But every serialized object's buffer is preceded with a serialized long that specifies the length of the buffer that is going to follow it. This way the client on the other end knows how much data to parse and deserialize.

public void SendWBMessage(WBMessage msg)
{
    if(m_NS != null)
    {
        MemoryStream ms = WBMessageHelper.Serialize(msg);
        MemoryStream msLength = WBMessageHelper.Serialize(ms.Length);
        m_NS.BeginWrite(msLength.GetBuffer(), 0, (int)msLength.Length, 
                        m_AsyncCallbackWrite, null);
        m_NS.BeginWrite(ms.GetBuffer(), 0, (int)ms.Length, m_AsyncCallbackWrite, 
                        null);
        ms.Close();
        msLength.Close();
    }        
}

These bytes, when sent across, look like this in memory:

// ------------------------------------------------------------
// | Data Length | Data ... | Data Length | Data ... | ...
// ------------------------------------------------------------
public void ParseData(byte [] ByteBuff, int iCbRead)
{
    int offset = 0;
    //long lLenData = 0;
    m_iByteCacheLen = 0;

    while(iCbRead >= m_iHdrSize)
    {
        if(m_bOnHeader)
        {
            m_iDataPackLen = (int)WBMessageHelper.DeserializeL(ByteBuff, offset, 
                                                               m_iHdrSize);
            offset += m_iHdrSize;
            m_bOnHeader = false;
            iCbRead -= m_iHdrSize;
        }
        //Parse the data 
        else
        {
            if(m_iDataPackLen == 0) return; 
            if(m_iDataPackLen > iCbRead)
            {
                Array.Clear(m_ByteCache, 0, 2048);
                Array.Copy(ByteBuff, offset, m_ByteCache, 0, iCbRead);
                m_iByteCacheLen = iCbRead;
                iCbRead -= iCbRead;
                //break;
            }
            else
            {
                //Complete Data packet, deserialize and RouteRequest
                WBMessage msg = WBMessageHelper.Deserialize(ByteBuff, offset, 
                                                            m_iDataPackLen);
                RouteRequest(msg);
                offset += m_iDataPackLen;
                m_iByteCacheLen = 0;
                iCbRead -= m_iDataPackLen;
                m_iDataPackLen = 0;
                m_bOnHeader = true;
            }
        }
    }
    //This is where we have surplus/incomplete data packets
    if(iCbRead > 0)
    {
        Array.Clear(m_ByteCache, 0, 2048);
        Array.Copy(ByteBuff, offset, m_ByteCache, 0, iCbRead);
        m_iByteCacheLen = iCbRead;
    }

}

>Some limitations, bugs and of course a disclaimer

  • Doesn’t support more than 2 clients (for now, but the code could be extended);
  • Concurrency checks for concurrent drawings not present, so behaviour unpredictable. Right now its implemented by disabling mouse input from the user over the whiteboard if another client is in the process of updating/drawing on it;
  • Pen size, brush type, and background color are all fixed for now, which could be easily incorporated through the UI and some additional messages;
  • Doesnt work with a firewall restricting access between the two clients. (hint: see personal firewall settings to poke a hole for port 8888 to make it to work);
  • Better mouse tracking required on the client;
  • I have assumed that a BinaryFormatter serializes a long data type to a memory stream object in the form of 56 byes of memory (try it yourself to see);
  • This is BY NO MEANS an article that explains the best software development practises or explains excellent C# coding skills. This is just a proof of concept application that I wanted to write out as soon as I can and see how much time it takes for me to develop.

References

  • Programming Microsoft Windows with C# - Charles Petzold.
  • MSDN

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Ranjeet Chakraborty
Web Developer
United States United States
I am originally from Mumbai, India, Currently living in Los Angeles doing Software Development. If there is something I miss here in LA, its the crazy Mumbai monsoon and delicious "wadapavs".
Apart from spending time writing software, I spend most of my time with XBox riding the Warthog and killing covenants in Halo.

Comments and Discussions

 
Questionwhiteboard Pinmembermohanrajkiller7-Nov-11 19:56 
AnswerRe: whiteboard PinmemberMember 1047612115-May-14 18:00 
GeneralMy vote of 5 Pinmemberdilipgudumba30-Dec-10 22:09 
GeneralMy vote of 4 Pinmemberdmrsantosh3-Aug-10 1:34 
GeneralA few problems (solved here). PinmemberKirill We7-Jan-10 6:57 
Questionhow about text Pinmemberhk21019-Nov-08 2:12 
QuestionHow To Send Simple Data Between Client and Server PinmemberYefta8-Sep-08 0:06 
QuestionDraw Area Control PinmemberMember 434092319-Feb-08 2:36 
GeneralMultithreding Pinmembercbaby15-Dec-07 12:42 
QuestionHow to support multiple users? Pinmembersheebapattath9-May-07 23:08 
QuestionUnknown error Pinmembermagdoline6-Dec-06 6:50 
Generalplz ...we need ur help Pinmembercoolshad7-Jun-06 13:56 
Questionthanks ..web pages-based conversion Pinmembercoolshad6-Jun-06 10:53 
AnswerRe: thanks ..web pages-based conversion Pinmemberpicazo10-Oct-07 3:32 
GeneralHelp Me Please Pinmember__Shah__12-Mar-06 19:05 
GeneralRe: Help Me Please Pinmembercoolshad7-Jun-06 13:48 
Generalwhiteboard program so that it supports the protocol ipv6 Pinmembermonymony30-Jan-06 4:54 
General.NET 1.1 PinmemberAsela Gunawardena1-Dec-05 17:29 
QuestionHey Pinmembertecywiz12125-Sep-05 8:20 
QuestionWhat about image transfer Pinmemberlslog327-Apr-05 2:50 
Questionwhatabout many users? Pinmemberxinxin_52516-Apr-05 3:10 
GeneralImplement in a PDA application Pinmemberrunarmann2-May-04 18:04 
GeneralReally nice Pinmemberhemakumar27-Apr-04 21:05 
GeneralThanks for the application PinsussKiranmayee26-Jan-04 21:02 
GeneralDispose PinmemberSaikat Sen1-Oct-03 20:48 
GeneralRe: Dispose PinmemberRanjeet Chakraborty2-Oct-03 5:17 
QuestionBug? PinmemberSaikat Sen29-Sep-03 17:10 
GeneralCool but... PinsussAnonymous18-Sep-03 8:04 
Generalnice Pinsussahlam5-Jun-03 3:33 
Generalsending objects Pinmemberneeldp1-May-03 3:43 
GeneralRe: sending objects PinmemberRanjeet Chakraborty1-May-03 6:16 
GeneralRe: sending objects Pinmemberneeldp2-May-03 7:13 
GeneralVery Cool PinmemberDustin21-Feb-03 9:51 
GeneralRe: Very Cool PinmemberRanjeet Chakraborty21-Feb-03 10:06 
GeneralwebApplication PinsussAnonymous18-Dec-02 1:53 
GeneralRe: webApplication Pinmemberiano12318-Dec-02 3:55 
QuestionDamaged Zip? PinmemberAgyklon10-Nov-02 23:31 
AnswerRe: Damaged Zip? PinmemberRanjeet Chakraborty18-Nov-02 10:46 
GeneralNonstandard PinmemberFeng Qin7-Nov-02 16:50 
GeneralRe: Nonstandard PinmemberRanjeet Chakraborty18-Nov-02 10:48 
GeneralHierarchical Message Generation PinsussM Naveen22-Aug-02 23:55 
GeneralRe: Hierarchical Message Generation PinmemberDavid Stone23-Aug-02 7:08 
GeneralRe: Hierarchical Message Generation PinsussAnonymous23-Aug-02 19:27 
GeneralRubberband erase existing drawing Pinmembershilin19-Jul-02 8:42 
GeneralRe: Rubberband erase existing drawing PinmemberRanjeet Chakraborty19-Jul-02 9:33 
GeneralNice! PinmemberRavi Bhavnani19-Jul-02 7:05 
GeneralRe: Nice! PinmemberRanjeet Chakraborty19-Jul-02 7:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 18 Jul 2002
Article Copyright 2002 by Ranjeet Chakraborty
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid