WoWing a Small Chat Project
One of the goals of the new programming focus this year was to try a variety of small projects, small exercises to stimulate the brain and increase my programming agility. There's no shortage of things to try, so I consider this a warm-up of sorts.
If any of you have played World of Warcraft, you'll get the idea pretty quickly, for everyone else, in WoW, there is a chat box that allows you to talk to everyone else in the game world. To make this as efficient as possible (theoretically), the chat is split into channels. You can use a menu to select what channel you are talking to, or just type "slash commands" which is a forward slash (/) followed by a letter or number to indicate the channel you want. When you do this, the slash command gets replaced with the name of the channel. The goal of this project is to emulate that.
I figure that this project presents a few challenges. First off, finding the most effective way of detecting the entry of a slash command. Once we get the channel name in there, it's part of the text string, so we want to have a way of pushing what we consider the "start" of the entered text. Once we get lines of chat added, we want the chat window to keep our place when new lines are added. So we've got a few things to tackle.
Stage One: Planning -
The way I figure, the best way to go about this is to have a pair of richTextBoxes (which can do the necessary formatting easily), one for the chat entry, one for the chat window displaying the already entered lines of text. There would also be a menu detached from the top of the window that will be the alternate method of changing channels.
Here's the naming conventions I use:
Chat Entry window - rChatEntry
Chat display window - rChatWindow
Channel Selection window - menuChannelSelector
And here's what it looks like in designer:
Not the most complicated design ever. The "Channel --->" part is the Menu, don't feel that you need to add it yet.
A few things to note:
- both text boxes are multiline = true, the bottom window (rChatWindow) is read-only and scrollbars = vertical. rChatEntry has it's tab index set to 0 (zero) so that's the first thing that gets input focus on loading.
- I toyed with disabling rChatWindow completely, but learned quickly that then you couldn't scroll back up. This ended up presenting it's own issues later on.
- I'm using Visual Studio 2008 Professional on Vista Home Premium, Aero turned off for that little bit of extra juice, just in case your view looks different. Oh, and big one, I believe this is built for .NET 3.5.
I'll get to the context menu later.
Continuing the planning, let's lay out the structure of what's going to be happening here:
We're basically working completely off of events here, so we have a few functions to handle these events:
- chatTextChanged, called whenever a change is made to the text of rChatEntry. Previously, this could be handled by detecting a keypress, but this is the preferred way of going about it. This function will see if we've entered a slash command and handle it properly.
- chatEnterPressed, called whenever you hit the enter key on your keyboard. While our chat entry line will wrap if you go past the end of the line, we want "enter" keypresses to send the line to the chat window.
In addition to this, we'll have some initial stuff happening on the loading of Form1 (I left the default name there) and a helper function called chatSetChannel that does the repetitive work of changing the channel.
So, let's get started.
public Form1()
{
InitializeComponent();
}
In our Form1 constructor, we've got some stuff to add later, but for now we are fine.
Right above that, let's get some of the needed variables in there:
// The currently selected channel
private string selectedChannel = "General";
private Color selectedColor = Color.Black;
These are to keep track of what channel we are on and what color is associated with it. Thanks to .NET, we don't have to do much with the colors, there are plenty predefined.
Now, when the form loads, we want to put a message in the chat window that we've joined the General channel at this date and time, and get our entry box set up on the correct channel. I like doing this in the OnLoad event, because it keeps it clean and separated from the constructor and it also makes sure everything is actually set and loaded before I go screwing around with it. I don't really think that would be a problem, but I do it anyways.
private void Form1_Load(object sender, EventArgs e)
{
// Get datetime of entry into the channel
string currentDate = DateTime.Now.ToString();
// Write out the join message
string entryText = "You have joined the General Chat Channel at "+currentDate+System.Environment.NewLine;
// Add the text to the chat window
rChatWindow.Text += entryText;
}
In this first part, we're using .NET to get the current time, slapping it in a string and putting it in the chat window. Piece of cake. Run the code to give it a try and we should see this:
Outstanding. There is, however, a reason why we used richTextBoxes instead of just normal textboxes, and that's formatting. Programmatically, it works almost the same as it does through a UI. You select the text you want to edit and apply the formatting to that selection.
In our case, we want to select the text and make it blue.
Right after that last bit of code:
// Now we have to select the text to make formatting changes to it.
rChatWindow.Select(0, entryText.Length);
// make the text blue and bold
rChatWindow.SelectionColor = Color.Blue;
The Select method on a richTextBox sets the range of text to be selected, in this case, from 0 (the beginning) to the end of the string. Then we simply set the color of the selection with SelectionColor. As I said earlier, the .NET framework helps with the colors.
If we run it now, we should probably see blue text. What's interesting is that you could change a few things so that your input focus starts in rChatWindow and you'd see that at this point, the text would actually be highlighted in the box, as if you had selected it with a mouse. We want to take care of that, and we do so with this:
// Now make sure nothing is selected
rChatWindow.Select(rChatWindow.Text.Length, rChatWindow.Text.Length);The syntax is "Select (StartIndex, EndIndex)" so why not make it (0,0)? Well, the select function is more like a mouse than we even saw before, it places the caret, or flashing cursor, our position in the textbox at the point at the end of the selection. So by using the Length property of the textbox, we are setting the cursor to the end, so we can append more lines on to the chat window without trouble.
The other thing we want to do is set our chat entry box to represent the current channel, which starts on
"General". It's here where we get to explore some of the issues presented to us. Turns out, most of them are easy to solve. A quick rundown: The textbox shows the name of the active channel in front of whatever you type, i.e. "<General> Here is your message". We have to insert that channel tag and make sure the user can't alter it. In addition to that, if the user types "/g" or another slash command as the first characters in the box, we need to change that "<General>" to the new active channel and color.
To solve the issue, we need to know what the "real" starting point of the user inputted string is. We're going to be sticking our own text in the beginning of the string, so we need a count of how much we've added. This will give us an index pointing to where the user's entry began. To do this, we're going back to the top where we've declared our variables and we're going to add an integer called lineStart. This should reasonably tell us where to start our selections when searching user input.
// The currently selected channel
private string selectedChannel = "General";
private Color selectedColor = Color.Black;
// Since we are appending a channel name to the beginning of the line, we need to keep our own count
// where the "real" line (i.e. the user input) starts
private int lineStart = 0;
We initialize it to zero. Now, for the sake of completeness, we could go back into our Form1_Load and replace the "0" in our select call with lineStart.
Now that we've knocked that out, we start building our chatSetChannel helper function. We're going to be changing the channel name a few times in here, so we don't want to repeat the same code.
private void chatSetChannel(string channelName, Color channelColor)
{
// Select everything
rChatEntry.Select(0, rChatEntry.Text.Length);
// Unprotect everything first
rChatEntry.SelectionProtected = false;
}
These first two methods get us started with some validation. Before anything else, we select the whole text box and make it unprotected. Protected text can't be altered. This is one of the benefits of richTextBoxes, and it's how we plan on keeping the user from messing with our channel name.
The rest of this is pretty straightforward.
// Set channel name
rChatEntry.Text = "<" + channelName + "> ";
// Select the text again
rChatEntry.Select(0, rChatEntry.Text.Length);
// Set the color
rChatEntry.SelectionColor = channelColor;
// Set the current text as protected
rChatEntry.SelectionProtected = true;
// Clear the selection to the end of the channel name
rChatEntry.Select(rChatEntry.Text.Length, rChatEntry.Text.Length);
We format the channel name in brackets and slap it in the text box. Now that our text is longer, we select the whole thing again, and use the SelectionColor property to make it all whatever the channel color is. Not very exciting, but the General channel is black, as we set at the beginning. With this section selected, we protect it, locking it from changes. This will protect anything entered in after the selection until we make a new selection, so we move our selection to the end, ready for user input.
// set our "real" start of line position
lineStart = rChatEntry.Text.Length;
// set our color
rChatEntry.ForeColor = channelColor;
// Selected Channel/Color
selectedChannel = channelName;
selectedColor = channelColor;
Now, some housekeeping, we update the lineStart variable to indicate where the user input begins in rChatEntry and make the foreground (which means the text, in this case) color to our channel color. This is so whatever the user types is the same color as the channel name. Wrapping it up, we set our channel tracking variables to the values of the current channel. That doesn't change right now, but it will when we are actually switching channels.
All of which will come tomorrow as it is very late right now. Here's the code so far:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace AutoChatChannels
{
public partial class Form1 : Form
{
// The currently selected channel
private string selectedChannel = "General";
private Color selectedColor = Color.Black;
// Since we are appending a channel name to the beginning of the line, we need to keep our own count
// where the "real" line (i.e. the user input) starts
private int lineStart = 0;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// Get datetime of entry into the channel
string currentDate = DateTime.Now.ToString();
// Write out the join message
string entryText = "You have joined the General Chat Channel at "+currentDate+System.Environment.NewLine;
// Add the text to the chat window
rChatWindow.Text += entryText;
// Now we have to select the text to make formatting changes to it.
rChatWindow.Select(lineStart, entryText.Length);
// make the text blue and bold
rChatWindow.SelectionColor = Color.Blue;
// Now make sure nothing is selected
rChatWindow.Select(rChatWindow.Text.Length, rChatWindow.Text.Length);
// Enter that in our text entry box
chatSetChannel(selectedChannel, selectedColor);
}
private void chatSetChannel(string channelName, Color channelColor)
{
// Select everything
rChatEntry.Select(0, rChatEntry.Text.Length);
// Unprotect everything first
rChatEntry.SelectionProtected = false;
// Set channel name
rChatEntry.Text = "<" + channelName + "> ";
// Select the text again
rChatEntry.Select(0, rChatEntry.Text.Length);
// Set the color
rChatEntry.SelectionColor = channelColor;
// Set the current text as protected
rChatEntry.SelectionProtected = true;
// Clear the selection to the end of the channel name
rChatEntry.Select(rChatEntry.Text.Length, rChatEntry.Text.Length);
// set our "real" start of line position
lineStart = rChatEntry.Text.Length;
// set our color
rChatEntry.ForeColor = channelColor;
// Selected Channel/Color
selectedChannel = channelName;
selectedColor = channelColor;
}
}
}
When I give it a shot, I get this:
Next I'll be making chat entry actually enter chat into the chat window and allow for the changing of colors.
Labels: Programming, Visual Studio 2008
0 Comments:
Post a Comment
<< Home