Introduction
I love my MSN Messenger, but I wished that I could use it to check other mail accounts. I really like the ability to see in the bottom corner how many unread messages I have. So that is where this application comes in. All you need to get this working is an IMAP mail server, a valid user name, and a valid password. To put this together I had to throw together what I know about Windows forms and IMAP protocols. I tried to give the app a very Messenger like feel. I hope you like it
Using the code
There were a few new things I had to figure out in order to get this app working:
1. Notify Icons
2. Multiple Forms
3. Timers
4. Sockets
5. Context Menus
To Start off, create a new Windows Application using C#. You can start out with one form, but you will have to add another one later. The first thing you will want to do is drag a few Windows Forms Components to your form. You will need: a NotifyIcon, a fontDialog, 2 timers, and a contextMenu. Lets take each one of these apart:
Notify Icon:
The notify icon is what will display in the system tray when you minimize this application. The first thing you will want to do is go to the properties of the form and set ShowInTaskbar to true. This will ensure that the form will be displayed in the taskbar. Now for the purpose of this application, I wanted the Icon that is displayed to be the number of new mail messages (1,2, etc.). If there is an error in the process I want the Icon to be an 'X'. For this I had to draw a bitmap using the Graphics objects. The code below shows how it works. This is also where the fontDialog comes into play. Rather than messing with getting a particular font form the classes, I just added a fontDialog and used the default font from it. Cheating? Yes. Does it work? Yes
//must be used to destroy the icon.
[DllImport("user32.dll", EntryPoint="DestroyIcon")]
static extern bool DestroyIcon(IntPtr hIcon);
//Pass in the number of new mail messages
private void UpdateTaskBar(int i)
{
String TaskBarLetter;
// Create a graphics instance that draws to a bitmap
Bitmap bitmap = new Bitmap(16, 16);
SolidBrush brush = new SolidBrush(fontDialog1.Color);
Graphics graphics = Graphics.FromImage(bitmap);
// Draw then number of Messages to the bitmap using the user selected font
if(i != -1)
TaskBarLetter= i.ToString();
else
TaskBarLetter = "X";
graphics.DrawString(TaskBarLetter, fontDialog1.Font, brush, 0, 0);
// Convert the bitmap into an icon and use it for the system tray icon
IntPtr hIcon = bitmap.GetHicon();
Icon icon = Icon.FromHandle(hIcon);
notifyIcon1.Icon = icon;
// unfortunately, GetHicon creates an unmanaged handle which must be manually destroyed
DestroyIcon(hIcon);
}
When the application starts, you just minimize the main form and hide it. Now it only resides in the System tray.
Now when you want to get the application out of the System Tray you have to use a Context Menu.
Context Menu:
I added a context menu to this application in order to give the system tray icon some interactivity. I basically needed only two items for the menus: Show Config and Exit. First you must associate both the form and the Notify Icon with this context Menu. That is easy. Just go into the properties for both of those items and set the ContextMenu property to the name of your ContextMenu (in this case ContextMenu1).
Then you need to add the code. Double click on the contextMenu and add the code seen here to handle the popup event.
//Add the menu items to the context menu
private void contextMenu1_Popup(object sender, System.EventArgs e)
{
// Clear the contents of the context menu.
contextMenu1.MenuItems.Clear();
// Add a menu item
contextMenu1.MenuItems.Add("Show Config",new System.EventHandler(this.Config_OnClick));
contextMenu1.MenuItems.Add("Exit",new System.EventHandler(this.Exit_OnClick));
}
protected void Exit_OnClick(System.Object sender, System.EventArgs e)
{
Application.Exit();
}
protected void Config_OnClick(System.Object sender, System.EventArgs e)
{
showForm2();
}
When you right click on the icon in the system tray, it will pop up with a menu for you to exit or open the config. This would probably be a good time to talk about the second form. The second form is a configuration file which stores the values for Username, password, and server name. The function showform2() contains the information for loading form 2.
private void showForm2()
{
oForm2.MyParentForm = this;
oForm2.Show();
oForm2.WindowState = FormWindowState.Normal;
}
This will bring the second form up to the front and allow the user to change the configuration. This data is read from an XML file and will be stored there also. After the user changes the information and clicks the "Update" button, the data is written back out to the XML and the values in memory are updated. You will also have to handle the Closing event of Form2 or else next time you try and use it you will receive an error because it will be removed from memory at that time. To do that just override the method. You will also want to override the minimize event so that the form disappears and not just goes down the bottom of the screen. You will want to do this for both forms.
private void Form2_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
// Hide the form...
this.Hide();
// Cancel the close...
e.Cancel = true;
}
protected override void OnResize(EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
this.Hide();
}
Timers:
There are 2 timers need for this application. The first one controls how long the form that notifies of new mail is displayed. Currently this is set to 3 seconds. The second timer controls how much time elapses in between checks for new messages. This is set to 30 seconds.
Sockets:
The other thing that is extremely important to this app is the Socket connection and the IMAP protocol specification. I did some research on how to communicate through IMAP and tested all of my commands using a telnet session. I know that this works with the MyRealBox IMAP server, I am assuming it works with all of them but I don't know. I haven't tested it. First off you will want to create a TCPClient that is attached to the Class Form1 (If you don't do this, you will once again run into the variable losing scope when you try and reuse it). Here is all of the code for making the connection, Sending data to the server, and closing the connection:
//connect to the IMAP Server
private bool Connect()
{
try
{
string sResponse="";
richTextBox1.AppendText("Connecting.....");
tcpclnt= new TcpClient();
tcpclnt.Connect(oForm2.Server,143);
// use the ipaddress as in the server program
Stream stm = tcpclnt.GetStream();
byte[] bb=new byte[4096];
int k=stm.Read(bb,0,4096);
for (int i=0;i<k;i++)
sResponse += Convert.ToChar(bb[i]).ToString();
richTextBox1.AppendText(sResponse);
return true;
}
catch
{
return false;
}
}
//sends data to the server and returns the response
//errString is the error message that we are looking for to see if it worked.
private string SendToServer(string str, string errString)
{
string sResponse="";
Stream stm = tcpclnt.GetStream();
ASCIIEncoding asen= new ASCIIEncoding();
byte[] ba=asen.GetBytes(str);
richTextBox1.AppendText("Transmitting.....");
stm.Write(ba,0,ba.Length);
byte[] bb=new byte[4096];
int k=stm.Read(bb,0,4096);
for (int i=0;i%lt;k;i++)
sResponse += Convert.ToChar(bb[i]).ToString();
richTextBox1.AppendText(sResponse);
if(sResponse.StartsWith(errString))
return "";
return sResponse;
}
//send logout message and close the socket
private bool Disconnect()
{
SendToServer("? LOGOUT"+ CRLF,"");
tcpclnt.Close();
return true;
}
The last thing you should probably see is the overridden OnLoad event for Form1 which contains the commands to make the connection to the server.
protected override void OnLoad(EventArgs e)
{
UpdateTaskBar(-1);
oForm2.MyParentForm = this;
//rect get data from the function returning the working area of the screen
Rectangle rect=new Rectangle();
rect=Screen.GetWorkingArea(this);
//set the window down to the lower right hand corner
this.Location = new Point(rect.Right - this.Size.Width, rect.Bottom - this.Size.Height);
if(Connect())
{
if(SendToServer("? LOGIN " + oForm2.UserName + " " + oForm2.Password +CRLF,"? NO")!="")
{
if(SendToServer("? Select Inbox"+CRLF,"? NO")!="")
{
string sMessages = SendToServer("? SEARCH UNSEEN"+CRLF," ");
if(sMessages != "")
{
iMessages = NewMessages(sMessages);
if(iMessages > 0)
{
label1.Text = "You have " + iMessages.ToString() + " new Messages";
}
else
{
label1.Text = "You have no new mail messages";
}
UpdateTaskBar(iMessages);
}
else
label1.Text = "Some Sort of Error on getting messages";
}
else
label1.Text = "Some Sort of Error on Selecting Inbox";
}
else
label1.Text = "Some Sort of Error on Login";
Disconnect();
}
else
label1.Text = "Error on connect";
timer1.Enabled = true;
}
That is about it for weird things in the app. I didn't put the full source on this page because it is quite long. So download the zip and get it from there.
Points of Interest
I can't say that this is the best coding job that I have ever done, but hey it killed a day at work and I learned something in the process. So I am happy with it. There are a few things that I will point out that are wrong with this:
- I still cannot get the icon in the system tray to go away when the app is closed. Oh well, just brush over it with your mouse and you will get it.
- I don't like how I did the loading of the parameters in XML but I didn't have time to pretty it up.
- There really is not much in the way of error handling in this application.
Have fun with it!