Animation and Text in System tray using C#
Application running from the system tray is very common these days. Mostly these applications show an icon in the system tray (beside the clock) and almost always have a pop-up context menu.
Adding additional features like showing text in the tray or show animations is also possible with little code. In this post I have discussed a class I wrote to accomplish this. If you are interested in using this enhanced SysTray class and do not care much about how it works jump to the last section Using SysTray.
First things first: How do you show an icon in the system tray
With .NET doing this is pretty simple and there is no need to play around with NOTIFYICONDATA and Shell_NotifyIcon of the Win32 world.
You just need to create a instance of System.Windows.Forms.NotifyIcon and fill out the relevant fields as in
private NotifyIcon m_notifyIcon;
m_notifyIcon = new NotifyIcon();
m_notifyIcon.Text = text; // tooltip text show over tray icon
m_notifyIcon.Visible = true;
m_notifyIcon.Icon = icon; // icon in the tray
m_notifyIcon.ContextMenu = menu; // context menu
That’s it and the icon appear in the system tray and on right clicking on it the menu is popped up.
Extending the NotifyIcon
NotifyIcon is sealed so you cannot extend by inheritance. So I created a class SysTray which extends NotifyIcon by creating an instance of it in a field. The constructor takes in the tooltip text, the default Icon and the context menu.
This class also adds other methods to show animation and text as explained below.
Showing Text
An application can only put in an icon in the tray. So the workaround is (there’s always one) to convert the text into an icon. ShowText API of the SysTray class just does this.
public void ShowText(string text, Font font, Color col)
{
Brush brush = new SolidBrush(col);
// Create a bitmap and draw text on it
Bitmap bitmap = new Bitmap(16, 16);
Graphics graphics = Graphics.FromImage(bitmap);
graphics.DrawString(text, m_font, brush, 0, 0);
// Convert the bitmap with text to an Icon
IntPtr hIcon = bitmap.GetHicon();
Icon icon = Icon.FromHandle(hIcon);
m_notifyIcon.Icon = icon;
}
What we are doing here is creating a bitmap, drawing the text to be shown on it and then converting it to an icon. Since NotifyIcon can show an Icon in the tray it’ll show the textual icon just fine.
Showing Animation
Showing animation in the tray is equally easy by making an array of icons each of which represent an animation frame and then switching the icons in the tray on timer events. SysTray accepts the image frame in different formats
public void SetAnimationClip (Icon [] icons)
Here icon needs to be an array of 16x16 icon each for a frame
public void SetAnimationClip (Bitmap [] bitmap)
Here bitmap needs to be an array of 16x16 bitmap each for a frame
public void SetAnimationClip (Bitmap bitmapStrip)
bitmapStrip is a bitmap strip of size n16x16. Where n is the number of frames. SysTray class extracts the individual frames from this strip and converts it to icons and uses it for the animation.
The last method is the easiest to use. For this you need to prepare an animation strip image. The image needs to have each frame of animation side by side. Each frame is 16x16 pixels and the color to render transparent is the common background of all the frames. Let’s take
The caller prepares this image by making it transparent and then calling SetAnimationClip
private void button1_Click(object sender, System.EventArgs e)
{
m_sysTray.StopAnimation();
Bitmap bmp = new Bitmap("tick.bmp");
// the color from the left bottom pixel will be made transparent
bmp.MakeTransparent();
m_sysTray.SetAnimationClip(bmp);
m_sysTray.StartAnimation(150, 5);
}
SetAnimationClip uses the following code to create the animation frame
public void SetAnimationClip (Bitmap bitmapStrip)
{
m_animationIcons = new Icon[bitmapStrip.Width / 16];
for (int i = 0; i < m_animationIcons.Length; i++)
{
Rectangle rect = new Rectangle(i*16, 0, 16, 16);
Bitmap bmp = bitmapStrip.Clone(rect, bitmapStrip.PixelFormat);
m_animationIcons[i] = Icon.FromHandle(bmp.GetHicon());
}
}
To animate the frame StartAnimation starts a timer and in the timer the icons are changed to animate the whole sequence.
public void StartAnimation(int interval, int loopCount)
{
if(m_animationIcons == null)
throw new ApplicationException("Animation clip not set with
SetAnimationClip");
m_loopCount = loopCount;
m_timer.Interval = interval;
m_timer.Start();
}
private void m_timer_Tick(object sender, EventArgs e)
{
if(m_currIndex < m_animationIcons.Length)
{
m_notifyIcon.Icon = m_animationIcons[m_currIndex];
m_currIndex++;
}
....
}
Using SysTray
This is how you use the class
Create and wire up your menu
ContextMenu m_menu = new ContextMenu();m_menu.MenuItems.Add(0, new MenuItem("Show",new
System.EventHandler(Show_Click)));
Get an icon you want to show statically in the tray.
Create a SysTray object with all the required information
m_sysTray = new SysTray("Right click for context menu",
new Icon(GetType(),"TrayIcon.ico"), m_menu);Create image strips with the animation frames. For 6 frame strip the image will have a width of 6*16 and height as 16 pixels
Bitmap bmp = new Bitmap("tick.bmp");
// the color from the left bottom pixel will be made transparentbmp.MakeTransparent();
m_sysTray.SetAnimationClip(bmp);Start animation indicating how many times you need to loop the animation and the frame delay
m_sysTray.StartAnimation(150, 5);To stop animation call
m_sysTray.StopAnimation();
Sources - SysTray Implementation: SysTray.cs
using System;
using System.Windows.Forms;
using System.Drawing;
namespace Abhinaba.SysTray
{
/// <summary>
/// SysTray class that can be used to display animated icons or text in the system tray
/// </summary>
public class SysTray : IDisposable
{
#region Constructor
/// <summary>
/// The constructor
/// </summary>
/// <param name="text">The toolip text</param>
/// <param name="icon">The icon that will be shown by default, can be null</param>
/// <param name="menu">The context menu to be opened on right clicking on the
/// icon in the tray. This can be null.</param>
public SysTray(string text, Icon icon, ContextMenu menu)
{
m_notifyIcon = new NotifyIcon();
m_notifyIcon.Text = text; // tooltip text show over tray icon
m_notifyIcon.Visible = true;
m_notifyIcon.Icon = icon; // icon in the tray
m_DefaultIcon = icon;
m_notifyIcon.ContextMenu = menu; // context menu
m_font = new Font("Helvetica", 8);
m_timer = new Timer();
m_timer.Interval = 100;
m_timer.Tick += new System.EventHandler(this.m_timer_Tick);
}
#endregion// Constructor
#region Public APIs
/// <summary>
/// Shows text instead of icon in the tray
/// </summary>
/// <param name="text">The text to be displayed on the tray.
/// Make this only 1 or 2 characters. E.g. "23"</param>
public void ShowText(string text)
{
ShowText(text, m_font, m_col);
}
/// <summary>
/// Shows text instead of icon in the tray
/// </summary>
/// <param name="text">Same as above</param>
/// <param name="col">Color to be used to display the text in the tray</param>
public void ShowText(string text, Color col)
{
ShowText(text, m_font, col);
}
/// <summary>
/// Shows text instead of icon in the tray
/// </summary>
/// <param name="text">Same as above</param>
/// <param name="font">The default color will be used but in user given font</param>
public void ShowText(string text, Font font)
{
ShowText(text, font, m_col);
}
/// <summary>
/// Shows text instead of icon in the tray
/// </summary>
/// <param name="text">the text to be displayed</param>
/// <param name="font">The font to be used</param>
/// <param name="col">The color to be used</param>
public void ShowText(string text, Font font, Color col)
{
Bitmap bitmap = new Bitmap(16, 16);//, System.Drawing.Imaging.PixelFormat.Max);
Brush brush = new SolidBrush(col);
Graphics graphics = Graphics.FromImage(bitmap);
graphics.DrawString(text, m_font, brush, 0, 0);
IntPtr hIcon = bitmap.GetHicon();
Icon icon = Icon.FromHandle(hIcon);
m_notifyIcon.Icon = icon;
}
/// <summary>
/// Sets the animation clip that will be displayed in the system tray
/// </summary>
/// <param name="icons">The array of icons which forms each frame of the animation
/// This'll work by showing one icon after another in the array.
/// Each of the icons must be 16x16 pixels </param>
public void SetAnimationClip(Icon[] icons)
{
m_animationIcons = icons;
}
/// <summary>
/// Sets the animation clip that will be displayed in the system tray
/// </summary>
/// <param name="icons">The array of bitmaps which forms each frame of the animation
/// This'll work by showing one bitmap after another in the array.
/// Each of the bitmaps must be 16x16 pixels </param>
public void SetAnimationClip(Bitmap[] bitmap)
{
m_animationIcons = new Icon[bitmap.Length];
for (int i = 0; i < bitmap.Length; i++)
{
m_animationIcons[i] = Icon.FromHandle(bitmap[i].GetHicon());
}
}
/// <summary>
/// Sets the animation clip that will be displayed in the system tray
/// </summary>
/// <param name="icons">The bitmap strip that contains the frames of animation.
/// This can be created by creating a image of size 16*n by 16 pixels
/// Where n is the number of frames. Then in the first 16x16 pixel put
/// first image and then from 16 to 32 pixel put the second image and so on</param>
public void SetAnimationClip(Bitmap bitmapStrip)
{
m_animationIcons = new Icon[bitmapStrip.Width / 16];
for (int i = 0; i < m_animationIcons.Length; i++)
{
Rectangle rect = new Rectangle(i * 16, 0, 16, 16);
Bitmap bmp = bitmapStrip.Clone(rect, bitmapStrip.PixelFormat);
m_animationIcons[i] = Icon.FromHandle(bmp.GetHicon());
}
}
/// <summary>
/// Start showing the animation. This needs to be called after
/// setting the clip using any of the above methods
/// </summary>
/// <param name="loop">whether to loop infinitely or stop after one iteration</param>
/// <param name="interval">Interval in millisecond in between each frame. Typicall 100</param>
public void StartAnimation(int interval, int loopCount)
{
if (m_animationIcons == null)
throw new ApplicationException("Animation clip not set with SetAnimationClip");
m_loopCount = loopCount;
m_timer.Interval = interval;
m_timer.Start();
}
/// <summary>
/// Stop animation started with StartAnimation with loop = true
/// </summary>
public void StopAnimation()
{
m_timer.Stop();
}
#endregion// Public APIs
#region Dispose
public void Dispose()
{
m_notifyIcon.Dispose();
if (m_font != null)
m_font.Dispose();
}
#endregion
#region Event handlers
private void m_timer_Tick(object sender, EventArgs e)
{
if (m_currIndex < m_animationIcons.Length)
{
m_notifyIcon.Icon = m_animationIcons[m_currIndex];
m_currIndex++;
}
else
{
m_currIndex = 0;
if (m_loopCount <= 0)
{
m_timer.Stop();
m_notifyIcon.Icon = m_DefaultIcon;
}
else
{
--m_loopCount;
}
}
}
#endregion// Event handlers
#region private variables
private NotifyIcon m_notifyIcon;
private Font m_font;
private Color m_col = Color.Black;
private Icon[] m_animationIcons;
private Timer m_timer;
private int m_currIndex = 0;
private int m_loopCount = 0;
private Icon m_DefaultIcon;
#endregion// private variables
}
}
Sources - SysTray Usage: Form1.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
Comments
Anonymous
December 14, 2005
Hi there,
It's an interesting and complete example, thanks for posting.
There is a flaw though with how memory is managed in it. For example, if you try to click on "Show Text" button several times you'll see memory is leeking.
I saw suggestions to use DestroyIcon method in User32.dll. But it did not completely solved the problem for me.
Let me know you find a solution for this. My email is aevdokimenko at gmail
Best regards,
Alex.Anonymous
December 15, 2005
Shame on me :( There are many leaks in the code.
in public void ShowText(string text, Font font, Color col)
you need to call bitmap.Dispose() release the graphics object and also call dispose on the
m_notifyIcon.Icon before assigning the new icon to it. The code is pretty bad in shape with respect to memory :(Anonymous
February 22, 2006
I think the m_timer_tick event can be modified as below to run a smooth animation rather than having a break after the last animation icon shown.
private void m_timer_Tick(object sender, EventArgs e)
{
if (m_currIndex > m_animationIcons.Length -1)
{
m_currIndex = 0;
if (m_loopCount <= 0)
{
m_timer.Stop();
m_notifyIcon.Icon = m_DefaultIcon;
}
else
{
--m_loopCount;
}
}
m_notifyIcon.Icon = m_animationIcons[m_currIndex];
m_currIndex++;
}Anonymous
February 22, 2006
Certain modifications done to the SetText function can produce a nice scrolling text in the taskbar icon. You need to include an other function 'SetTextAnimation(Bitmap bitmapStrip)' to split the text filled image to image parts and loaded into the animation collection.
public void ShowText(string text, Font font, Color col)
{
m_text = text;
m_font = font;
m_col = col;
Bitmap bitmap = new Bitmap(16, 16);//, System.Drawing.Imaging.PixelFormat.Max);
Graphics graphics = Graphics.FromImage(bitmap);
SizeF sz = graphics.MeasureString(m_text, m_font);
graphics.Dispose();
int rem = 0;
int reqWid = Math.DivRem((int)sz.Width, 16, out rem);
if (rem > 0)
reqWid = (reqWid+1) * 16;
else
reqWid = reqWid * 16;
bitmap = new Bitmap(reqWid, 16);
graphics = Graphics.FromImage(bitmap);
Brush brush = new SolidBrush(m_col);
graphics.DrawString(m_text, m_font, brush, 0, 0);
graphics.Dispose();
SetTextAnimationClip(bitmap);
}
public void SetTextAnimationClip(Bitmap bitmapStrip)
{
int pos = 0;
int indx = bitmapStrip.Width / 8;
indx--;
m_animationIcons = new Icon[indx];
for (int i = 0; i < m_animationIcons.Length; i++)
{
Rectangle rect = new Rectangle(pos, 0, 16, 16);
Bitmap bmp = bitmapStrip.Clone(rect, bitmapStrip.PixelFormat);
m_animationIcons[i] = Icon.FromHandle(bmp.GetHicon());
pos += 8;
}
}
This makes it a scrolling text notify icon too.Anonymous
April 14, 2006
Any way that the text to icon conversion could be done easily in VB6?Anonymous
April 15, 2006
The comment has been removedAnonymous
May 10, 2006
How can I download source code?Anonymous
June 20, 2006
Very interesting :) Thanks :)Anonymous
November 25, 2006
Is there anyway that I could show more text in one icon? I want to show a full date like windows clock in the tray, but 16x16 icon is not enough.Please HelpThanksAnonymous
January 22, 2008
IS it possible to download the code from somewhere?Anonymous
January 23, 2008
I attached the source to the post. The direct link is http://blogs.msdn.com/abhinaba/attachment/464150.ashxAnonymous
August 26, 2008
Rectangle(i * 16, 0, 16, 16); <- shouldn't this be i*16,16,16,16 since it is the upper left corner of the rectangle???Anonymous
June 07, 2013
can you please guide me how to click able link add in notification?