IM Chat Interface for C# and .NET

Introduction

It's a hot one hear in Manhattan, but who is complaining, summer is finally here. At least I can sit in the shade and chat. Fortunately there is a colorful way to chat through this new chat interface using C# and .NET.  Although this chat control does not serve up a chat on a remote box, it will give you a starting point for creating fancier chat screens.

ChatInterface.jpg

Figure 1 - Chat Interface

Features

This chat screen allows you to type in a pseudo-subset of html in your command line to produce bold, italic, and underlined text. It also recognizes a few of the emoticons such as the smiley face. The chat screen will automatically put a timestamp and user on each line and also nicely auto-indent the chat message.

The following tags are currently available in the sample. By altering the state machine contained in the HtmlParser class, you can add additional style features:

Tag Description
<b></b> bold text
<i></i> italics
<u></u> underline
<b color=red></b> bold color attribute
:)   :-)  :-(  8-)  :-S  :-@  (N) some emoticons 

Table 1 - allowable tags in the chat control

Design

The chat interface is mainly a form containing three classes: the ChatForm which contains all the controls for sending and receiving messages on the client (Figure 1), the user control HTMLDisplay which paints the chat messages and the HTMLParser which parses the text sent to the user control and determines colors and styles. The UML Diagram in Figure 2 shows the relationship between these classes in the design.

UmlChatInterfaceDiagram.jpg

Figure 2 - Chat Interface Class Diagram Reverse Engineered using WithClass UML Tool

The way the interface should work in a regular client-to-client chat is that upon hitting the Send button on the chat form (client 1), the message is sent remotely and and event is listened to on the another client's form containing the message(client 2). Upon hearing the event the client calls the ChatForm's AddMessage method to add the message to the HTMLDisplay. AddMessage appends the message to the Html property contained in the HtmlDisplay class. The HtmlDisplay then parses this message using the HtmlParser and paints the message according to the parsers resulting fonts, colors, and position indicators.

UmlSequenceSendMessage.jpg

Figure 3 - Sequence Diagram for sending and receiving a chat message drawn in WithClass

The chat interface uses a state machine contained in the HtmlParser class to parse each entry typed into the chat screen. The state machine looks for html tags and emoticons in the message sent and changes state based on the content of the message. When the Draw method in the Html Display it processes each token contained in the html text. The results returned from the state machine tell the Draw routine exactly what font, color, and relative position it needs to paint the next text entry.

UMLStateParsing.jpg

Figure 4 - State Diagram of Tag Parsing drawn in WithClass UML Tool

The Drawing Code

In order to better understand how the drawing of HtmlDisplay user control works, let's take a look at the Draw method of the HTMLDisplayin Listing 1. The code begins by creating the parser that implements our state machine. The parser loops through the entire piece of html text in the chat and picks apart the text with our parser state machine. Each time the state machine finishes processing another sliver of text with a call to ProcessNext, it returns information to the Draw routine to tell it out to color, style and position the text. If the state machine returns a  emoticon graphic index greater than 0, the Draw method places the proper emoticon from a stored ImageList into the calculated position in the chat window. 

Listing 1 - The Code used to Draw the Html in the HTMLDisplay

  1. /// <summary>  
  2. /// Draws the Chat Text with proper coloring and font  
  3. /// based on the html tags and emoticons inside the text  
  4. /// </summary>  
  5. /// <param name="g"></param>  
  6. private void Draw(Graphics g)  
  7. {  
  8.     // initialize local variables for drawing  
  9.     Color color = Color.Black;  
  10.     Font font = null;  
  11.     int pos = 0;  
  12.     Brush b = null;  
  13.     bool nextLine = false;  
  14.     bool last = false;  
  15.     bool hasTab = false;  
  16.     int smileyGraphic = 0;  
  17.     try  
  18.     {  
  19.         // we want really smooth drawing for our chat  
  20.         // speed of drawing isn't an issue here  
  21.         g.SmoothingMode = SmoothingMode.AntiAlias;  
  22.         // create the parser with the user control font  
  23.         // and the current text of our HTMLDisplay  
  24.         _parser = new HtmlParser(_html, Font);  
  25.         // start the parser from the beginning  
  26.         Reset();  
  27.         _parser.Reset();  
  28.         _scrollPosition = vScrollHTML.Value;  
  29.         // keep processing the html text until we have no more  
  30.         while (last == false)  
  31.         {  
  32.             // get the next piece of relevant text from the state machine  
  33.             // along with all the text's relative font, color, and position information  
  34.             string text = _parser.ProcessNext(ref font, ref color, ref nextLine, ref last, ref hasTab, ref smileyGraphic);  
  35.             // create a brush to color the text  
  36.             b = new SolidBrush(color);  
  37.             // Calculate the length and width of the string  
  38.             // in order to determine the next available text position  
  39.             int stringWidth = (int)g.MeasureString(text, font).Width;  
  40.             int stringHeight = (int)g.MeasureString("X", font).Height + 2;  
  41.             if (_position + stringWidth > ClientRectangle.Width - vScrollHTML.Width)  
  42.             {  
  43.                 _position = _tabWidth;  
  44.                 _verticalPosition += stringHeight;  
  45.             }  
  46.             // get clip region for smart validation  
  47.             Rectangle clip = Rectangle.Truncate(g.ClipBounds);  
  48.             // calculate the absolute vertical position in client coordinates  
  49.             // by taking the scrollbar into account  
  50.             int absoluteVerticalPosition = _verticalPosition - _scrollPosition;  
  51.             // if its not an emoticon, we need to draw a string  
  52.             if (smileyGraphic == 0)  
  53.             {  
  54.                 if (_position <= clip.Right && absoluteVerticalPosition <= clip.Bottom && absoluteVerticalPosition >= clip.Top)  
  55.                 {  
  56.                     // draw the string here with the determined font, color and  
  57.                     // position  
  58.                     g.DrawString(text, font, b, _position, absoluteVerticalPosition, new StringFormat());  
  59.                 }  
  60.             }  
  61.             // track the current horizontal position  
  62.             _position += (int)stringWidth;  
  63.             // if the state machine determine we need to go to the next line,  
  64.             // recalculate current horizontal and vertical position  
  65.             if (nextLine)  
  66.             {  
  67.                 _position = 0;  
  68.                 _verticalPosition += stringHeight;  
  69.             }  
  70.             // if the state machine determined we have a tab at the next  
  71.             // token, calculate the position of the tab  
  72.             if (hasTab)  
  73.             {  
  74.                 _position = _tabWidth;  
  75.             }  
  76.             // handle the case where we don't have text, but an emoticon graphic  
  77.             if (smileyGraphic > 0)  
  78.             {  
  79.                 if (_position <= clip.Right && absoluteVerticalPosition <= clip.Bottom && absoluteVerticalPosition >= clip.Top)  
  80.                 {  
  81.                     // draw the emoticon bitmap image from the ImageList  
  82.                     g.DrawImage(imageList1.Images[smileyGraphic - 1], _position, _verticalPosition - _scrollPosition, imageList1.Images[0].Width, imageList1.Images[0].Height);  
  83.                 }  
  84.                 // Shift the current position by the width of the emoticon  
  85.                 _position += imageList1.Images[smileyGraphic - 1].Width + 2;  
  86.             }  
  87.             b.Dispose(); // clean up the brush resources  
  88.         }  
  89.     }  
  90.     catch (Exception ex)  
  91.     {  
  92.         Console.WriteLine(ex.ToString());  
  93.     }  

 
Adding Emoticons

The emoticons are mapped to an image index in the image list. Listing 2 shows  the initialization code for the emoticons in the HTMLDisplay which maps each emoticon to an image. The emoticon text representations are all unique, so they are used as the keys in the hash table:

  1. void InitializeEmoticonMap()  
  2. {  
  3.     EmoticonMap[":)"] = 1;  
  4.     EmoticonMap[":-)"] = 1;  
  5.     EmoticonMap[":-("] = 2;  
  6.     EmoticonMap[":("] = 2;  
  7.     EmoticonMap["8-)"] = 3;  
  8.     EmoticonMap[":-S"] = 4;  
  9.     EmoticonMap[":-@"] = 5;  
  10.     EmoticonMap["+o("] = 6;  
  11.     EmoticonMap[":-|"] = 7;  
  12.     EmoticonMap[";-)"] = 8;  
  13.     EmoticonMap[":-#"] = 9;  
  14.     EmoticonMap["(A)"] = 10;  
  15.     EmoticonMap["(6)"] = 11;  
  16.     EmoticonMap["(%)"] = 12;  
  17.     EmoticonMap["(N)"] = 13;  
  18.     EmoticonMap["(Y)"] = 14;  
  19.     EmoticonMap["(P)"] = 15;  
  20.     EmoticonMap[":-$"] = 16;  
  21.     EmoticonMap["<:o)"] = 15;  
  22. }
You can add your own emoticons simply by adding entries in the method above and placing the corresponding image in the image list of the HTMLDisplay. The emoticon images can be added directly to the HTMLDisplay user control through the EmoticonImages property shown in figure 5.

HtmlDisplayPropertyWindow.jpg

Figure 5 - HTMLDisplay Properties Window for adding Emoticon Images

Flashing the Chat Window

Another nice feature of the interface is that it will flash the Chat Window on the task bar if a message has been added to the chat. This way you won't miss any urgent chat messages from your friends and colleagues. On the other hand, the flashing may eventually annoy the heck out of you, so you might want to rip it out of the code. In any case, the flashing task bar is a standard function in the Windows SDK so we will explain how to implement it here. The FlashWindow SDK call is slightly misleading, because it doesn't actually flash the window. It simply inverts the window title bar and task bar color. In order to flash the window, you have to call FlashWindow with a flag that toggles the titlebars color state in a timer. Listing3 shows the pinvoke call for FlashWindow and an encapsulating method in the ChatForm.

Listing 3- FlashWindow SDK call implemented in C#

  1. [DllImport("user32.dll")]  
  2. static extern bool FlashWindow(IntPtr hwnd, bool bInvert);  
  3. private void FlashWindow(bool invert)  
  4. {  
  5.     FlashWindow(this.Handle, invert);  
  6. }  
  7.   
  8. Flashing the ChatForm window is accomplished by adding a timer to the ChatForm and calling FlashWindow once a second in an event handler.Each time the event handler is called, the FlashWindow is called with the inverted boolean flag giving us our flashing effect.  
  9.   
  10. bool _invert = false;  
  11. private void OnTimer(object state)  
  12. {  
  13.     _invert ^= true/// toggle the titlebar  
  14.     FlashWindow(_invert);  

Conclusion

Sometimes you need to express yourself in more than just words. This chat interface will help you get started in creating your own IM with fancy styles and emoticons. You can add additional styles by altering the state machine in the HTMLParser class. You can also add additional Emoticons by adding graphics to the ImageList and altering the EmoticonMap in the HTMLDisplay. You may want to experiment with adding buttons to the chat form in order to make it easier for users to send their emoticons through the chat.  Anyway, feel free to chat it up with the power of C# and .NET.