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:
- Notify Icons
- Multiple Forms
- Timers
- Sockets
- 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
[DllImport("user32.dll", EntryPoint="DestroyIcon")]
static extern bool DestroyIcon(IntPtr hIcon);
private void UpdateTaskBar(int i)
{
String TaskBarLetter;
Bitmap bitmap = new Bitmap(16, 16);
SolidBrush brush = new SolidBrush(fontDialog1.Color);
Graphics graphics = Graphics.FromImage(bitmap);
if(i != -1)
TaskBarLetter= i.ToString();
else
TaskBarLetter = "X";
graphics.DrawString(TaskBarLetter, fontDialog1.Font, brush, 0, 0);
IntPtr hIcon = bitmap.GetHicon();
Icon icon = Icon.FromHandle(hIcon);
notifyIcon1.Icon = icon;
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.
private void contextMenu1_Popup(object sender, System.EventArgs e)
{
contextMenu1.MenuItems.Clear();
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)
{
this.Hide();
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:
private bool Connect()
{
try
{
string sResponse="";
richTextBox1.AppendText("Connecting.....");
tcpclnt= new TcpClient();
tcpclnt.Connect(oForm2.Server,143);
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;
}
}
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;
}
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;
Rectangle rect=new Rectangle();
rect=Screen.GetWorkingArea(this);
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!
History
Version 1.0 Initial Version