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

  1. Create and wire up your menu
    ContextMenu m_menu = new ContextMenu();

    m_menu.MenuItems.Add(0, new MenuItem("Show",new

    System.EventHandler(Show_Click)));

  2. Get an icon you want to show statically in the tray.

  3. 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);

  4. 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 transparent

    bmp.MakeTransparent();
    m_sysTray.SetAnimationClip(bmp);

  5. Start animation indicating how many times you need to loop the animation and the frame delay
    m_sysTray.StartAnimation(150, 5);

  6. 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;

 

SysTray.cs