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. If you are curious about how to create a client-server chat application, check out the chat server written on C# Corner by Nanu or by Pramod Singh.
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.
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.
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.
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
/// <summary>
/// Draws the Chat Text with proper coloring and font
/// based on the html tags and emoticons inside the text
/// </summary>
/// <param name="g"></param>
private void Draw(Graphics g)
{
// initialize local variables for drawing
Color color = Color.Black;
Font font = null;
int pos = 0;
Brush b = null;
bool nextLine = false;
bool last = false;
bool hasTab = false;
int smileyGraphic = 0;
try
{
// we want really smooth drawing for our chat
// speed of drawing isn't an issue here
g.SmoothingMode = SmoothingMode.AntiAlias;
// create the parser with the user control font
// and the current text of our HTMLDisplay
_parser = new HtmlParser(_html, Font);
// start the parser from the beginning
Reset();
_parser.Reset();
_scrollPosition = vScrollHTML.Value;
// keep processing the html text until we have no more
while (last == false)
{
// get the next piece of relevant text from the state machine
// along with all the text's relative font, color, and position information
string text = _parser.ProcessNext(ref font, ref color, ref nextLine, ref last, ref hasTab, ref smileyGraphic);
// create a brush to color the text
b = new SolidBrush(color);
// Calculate the length and width of the string
// in order to determine the next available text position
int stringWidth = (int)g.MeasureString(text, font).Width;
int stringHeight = (int)g.MeasureString("X", font).Height + 2;
if (_position + stringWidth > ClientRectangle.Width - vScrollHTML.Width)
{
_position = _tabWidth;
_verticalPosition += stringHeight;
}
// get clip region for smart validation
Rectangle clip = Rectangle.Truncate(g.ClipBounds);
// calculate the absolute vertical position in client coordinates
// by taking the scrollbar into account
int absoluteVerticalPosition = _verticalPosition - _scrollPosition;
// if its not an emoticon, we need to draw a string
if (smileyGraphic == 0)
{
if (_position <= clip.Right && absoluteVerticalPosition <= clip.Bottom && absoluteVerticalPosition >= clip.Top)
{
// draw the string here with the determined font, color and
// position
g.DrawString(text, font, b, _position, absoluteVerticalPosition, new StringFormat());
}
}
// track the current horizontal position
_position += (int)stringWidth;
// if the state machine determine we need to go to the next line,
// recalculate current horizontal and vertical position
if (nextLine)
{
_position = 0;
_verticalPosition += stringHeight;
}
// if the state machine determined we have a tab at the next
// token, calculate the position of the tab
if (hasTab)
{
_position = _tabWidth;
}
// handle the case where we don't have text, but an emoticon graphic
if (smileyGraphic > 0)
{
if (_position <= clip.Right && absoluteVerticalPosition <= clip.Bottom && absoluteVerticalPosition >= clip.Top)
{
// draw the emoticon bitmap image from the ImageList
g.DrawImage(imageList1.Images[smileyGraphic-1], _position, _verticalPosition - _scrollPosition, imageList1.Images[0].Width, imageList1.Images[0].Height);
}
// Shift the current position by the width of the emoticon
_position += imageList1.Images[smileyGraphic-1].Width + 2;
}
b.Dispose(); // clean up the brush resources
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
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:
void InitializeEmoticonMap()
{
EmoticonMap[":)"] = 1;
EmoticonMap[":-)"] = 1;
EmoticonMap[":-("] = 2;
EmoticonMap[":("] = 2;
EmoticonMap["8-)"] = 3;
EmoticonMap[":-S"] = 4;
EmoticonMap[":-@"] = 5;
EmoticonMap["+o("] = 6;
EmoticonMap[":-|"] = 7;
EmoticonMap[";-)"] = 8;
EmoticonMap[":-#"] = 9;
EmoticonMap["(A)"] = 10;
EmoticonMap["(6)"] = 11;
EmoticonMap["(%)"] = 12;
EmoticonMap["(N)"] = 13;
EmoticonMap["(Y)"] = 14;
EmoticonMap["(P)"] = 15;
EmoticonMap[":-$"] = 16;
EmoticonMap["<:o)"] = 15;
}
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.
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#
[DllImport("user32.dll")]
static extern bool FlashWindow(IntPtr hwnd, bool bInvert);
private void FlashWindow(bool invert)
{
FlashWindow(this.Handle, invert);
}
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.
bool _invert = false;
private void OnTimer(object state)
{
_invert ^= true; /// toggle the titlebar
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.