Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#
Article

Creating a Timer Using the Amazing New Windows 7 Features

Rate me:
Please Sign up or sign in to vote.
4.89/5 (99 votes)
4 Jan 2010CPOL7 min read 152.5K   6K   166   31
This article shows how to create a simple egg timer that uses features new to Windows 7.

TaskbarTimer screenshot

Table of Contents

Introduction

Microsoft has introduced some amazing new features in Windows 7. I have used these features to create this small app: taskbar, jump-list, task-dialog, and Aero Glass.

The application is just an egg-timer: you tell the time after which the timer must elapse, and it shows you the time left. After the time has elapsed, you are notified:

  • by sound;
  • by window flash.

In this article, you will see how Windows 7 features can be used for a timer application.

Background

I always thought, why is there no timer in standard Windows applications? I've created this one to make my life and work easier with Windows 7.

The Code

The Environment

First of all, about the environment. To develop this application, I've used:

I've made a small patch for the Windows API Code Pack: please see the Shell/Ext folder with the files I've added. The patch allows you to create a glass-window that updates its taskbar screenshot on-demand. The details of the patch are described in the following section.

Taskbar

TaskbarTimer's main form is just an Aero Glass window with the time displayed on it (I've described it later in this article). Actually, the application doesn't need it at all, all functionality is used from the taskbar.

Here's a taskbar thumbnail and a preview for the timer:

Timer preview

Every second, the application updates the thumbnail and the preview. It also sets the taskbar progress:

C#
/// Handles timer tick event. Invalidates form image and thumbnails 
private void Timer_Tick(object sender, EventArgs e) {
    _timeLeft = _timeLeft.Subtract(new TimeSpan(0, 0, 0, 1));
 
    // Invalidate the image 
    Invalidate();
    // Tell the taskbar to invalidate the thumbnail and the preview 
    InvalidateThumbnails();
 
    // Calculate the percent of completion 
    int percent = _timeLeft.TotalMilliseconds > 0 ? 
      (100 - (int)(_timeLeft.TotalMilliseconds / _totalMilliseconds * 100d)) : 100;
    if (percent < 0 || percent > 100) {
        percent = 0;
    }
    TaskbarManager.Instance.SetProgressValue(percent, 100);
    // ... the rest of code is below

Invalidate is a standard method of Control. In our case, we need to call it because the form may be open. In this case, Windows will re-paint the form and update the time on it.

InvalidateThumbnails is a method of the GlassFormWithCustomThumbnails class, which I have created as a patch. It's pretty simple:

C#
/// Invalidates the thumbnails 
protected void InvalidateThumbnails() {
    TabbedThumbnailNativeMethods.DwmInvalidateIconicBitmaps(Handle);
}

The only reason I have put it in that class is that the TabbedThumbnailNativeMethods class is declared as protected, so we can use it only from the Shell assembly.

After this method, Windows checks whether the custom previews are enabled for the window. We enable custom previews in the OnLoad method of the GlassFormWithCustomThumbnails class:

C#
/// Enables custom thumbnails for the form 
protected override void OnLoad(EventArgs e) {
    if (!DesignMode) {
        TabbedThumbnailNativeMethods.EnableCustomWindowPreview(Handle, true);
    }
    base.OnLoad(e);
}

So, the previews are enabled in our application. This means that Windows behaves in such a way after calling InvalidateThumbnails:

Invalidating taskbar thumbnails

As you can see, if the taskbar thumbnail is closed, Windows doesn't do anything. If it's open, Windows sends the events to the window. That's why we have to implement the WndProc method:

C#
/// Handles taskbar-related messages 
protected override void WndProc(ref System.Windows.Forms.Message m) {
    if (m.Msg == (int)TaskbarNativeMethods.WM_DWMSENDICONICTHUMBNAIL) {
        int width = (int)((long)m.LParam >> 16);
        int height = (int)(((long)m.LParam) & (0xFFFF));
        Size requestedSize = new Size(width, height);
        using (Bitmap iconicThumb = GetIconicThumbnail(requestedSize)) {
          TabbedThumbnailNativeMethods.SetIconicThumbnail(Handle, 
                                       iconicThumb.GetHbitmap());
        }
    } else if (m.Msg == (int)TaskbarNativeMethods.WM_DWMSENDICONICLIVEPREVIEWBITMAP) {
        using (Bitmap peekBitmap = GetPeekBitmap()) {
          TabbedThumbnailNativeMethods.SetPeekBitmap(Handle, 
                                       peekBitmap.GetHbitmap(), false);
        }
    } 
 
    base.WndProc(ref m);
}

This is a method of our GlassFormWithCustomThumbnails class. As you can see, the derived class must handle the following events:

C#
/// Event to set peek bitmap 
protected event GetPeekBitmapDelegate GetPeekBitmap;
 
/// Event to set the iconic thumbnail 
protected event GetIconicThumbnailDelegate GetIconicThumbnail;

We implement them in the FrmMain class:

C#
/// Constuctor 
public FrmMain(int minutes, int elapsedMinutes, TimerOptions options) {
    // ... (constructor logic) 
    GetPeekBitmap += OnGetPeekBitmap;
    GetIconicThumbnail += OnGetIconicThumbnail;
}

We don't need to dispose bitmaps returned from these items; they are disposed automatically in the GlassFormWithCustomThumbnails class.

Perhaps you will ask: Why haven't you done everything using the Windows API Code Pack???

First, we'll do a small investigation:

Let's find all the references to the TabbedThumbnailNativeMethods.DwmInvalidateIconicBitmaps method using Visual Studio: right-click the method, and click the Find all references menu item. We'll see that it's used only in one place (TaskbarWindowManager.cs: 632):

C#
internal void InvalidatePreview(TaskbarWindow taskbarWindow)
{
    if (taskbarWindow != null)
        TabbedThumbnailNativeMethods.DwmInvalidateIconicBitmaps(
                         taskbarWindow.WindowToTellTaskbarAbout);
}

It's a method of the TaskbarWindowManager class. If we find all references to this method, we'll see that in order to use it, we must create an instance of the TabbedThumbnail class.

Unfortunately, we cannot get the preview for the default window. Okay, here's the code we can write:

  1. Create a new project and reference the Windows API Code Pack libraries: you need Core and Shell. Also reference the PresentationCore and WindowsBase libraries.
  2. Write this code in the form Load event:
  3. C#
    private void Form1_Load(object sender, EventArgs e) {
        TabbedThumbnail thumb = new TabbedThumbnail(Handle, Handle);
        thumb.DisplayFrameAroundBitmap = false;
        TaskbarManager.Instance.TabbedThumbnail.AddThumbnailPreview(thumb);
        thumb.SetImage(Bitmap.FromFile(@"d:\temp\vs.png") as Bitmap);
        thumb.DisplayFrameAroundBitmap = false;
    }

I've used a Visual Studio screenshot as a sample image. Here's the result:

Unwanted frame

Windows displays a frame, although we have specified DisplayFrameAroundBitmap = false! And, there's no way to get rid of it.

I would be really glad if Microsoft implemented this:

C#
// this.Handle here is a form for which we want to get the default thumbnail: 
TabbedThumbnail thumb = 
  TaskbarManager.Instance.TabbedThumbnail.GetThumbnailPreview(this.Handle);

Unfortunately, we get null in this method. That's why I've implemented the patch for the Windows API Code Pack.

Taskbar Buttons

Now, let's add two small pause and about buttons near the taskbar thumbnail:

C#
/// A button in the taskbar to stop the timer 
private readonly ThumbnailToolbarButton _buttonPause = 
   new ThumbnailToolbarButton(Resources.Pause, Resources.PauseTimerTooltip);

/// A button in the taskbar show About dialog 
private readonly ThumbnailToolbarButton _buttonAbout = 
   new ThumbnailToolbarButton(Resources.About, 
       Resources.AboutTooltip) { DismissOnClick = true };

And later, after we have initialized it:

C#
_buttonPause.Click += PauseTimer_Clicked;
_buttonAbout.Click += About_Clicked;
ThumbnailToolbarButton[] buttons = new[] {_buttonPause, _buttonAbout};
TaskbarManager.Instance.ThumbnailToolbars.AddButtons(Handle, buttons.ToArray());

Later, we can disable this button (in our case, when the time has elapsed, it's no use to click the button):

C#
_buttonPause.Enabled = false;

After this call, the button doesn't become blue when you put your mouse over it. And of course, the handler is not called.

We also change the image and the tooltip of the button (when the user clicks it, the play image becomes the pause image, and vice versa):

C#
private void PauseTimer_Clicked(object sender, ThumbnailButtonClickedEventArgs e) {
    // ... (event logic) 
    e.ThumbnailButton.Icon = Resources.Play;
    e.ThumbnailButton.Tooltip = Resources.StartTimerTooltip;
    // ... (event logic continues)
}

As you can see, we get the reference to our button in ThumbnailButtonClickedEventArgs.

Please pay your attention to the DismissOnClick property (we set it for the About button). This property determines what happens to the taskbar thumbnail when the button is clicked:

  • If DismissOnClick is set to true, the taskbar thumbnail will disappear.
  • If DismissOnClick is set to false, nothing will happen. This is the default behavoir of taskbar buttons.

In our case, the taskbar thumbnail will disappear when the user clicks the About button.

Taskbar-Buttons

Aero Glass Window

To create the Aero Glass window, I've derived my form from GlassFormWithCustomThumbnails. This is my class, it's derived from GlassWindow.

All we need is to just override the OnPaint method to draw the timer state:

C#
protected override void OnPaint(PaintEventArgs e) {
    base.OnPaint(e);
 
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
    e.Graphics.DrawString(GetTimeLeftText(), _textFont, 
                          Brushes.Black, ClientRectangle, _stringFormat);
}

As I've mentioned before, the main window of the application is very simple because it's almost never used. But with Aero Glass, it looks fine (the Windows logo is my wallpaper, it's not on the window):

Aero Glass window

Task-dialog

When you start the timer, it shows you the following window:

Task-dialog

Such windows are called task-dialogs. Let's examine its parts in detail:

Task-dialog

Everything can be set in the TaskDialog class from the Windows API Code Pack:

C#
/// Constructor. Initlalizes the _dialog 
public TimerTaskDialog() {
    _dialog = new TaskDialog();
    _dialog.Caption = "Taskbar Timer";
    _dialog.Cancelable = true;
    _dialog.InstructionText = "Please select a time interval for taskbar timer";
    _dialog.FooterText = "TaskbarTimer 1.0 Copyright (C) Dmitry Vitkovsky 2009";
    _dialog.HyperlinksEnabled = true;
    _dialog.StandardButtons = TaskDialogStandardButtons.Close;
 
    TaskDialogCommandLink btn5Munutes = new TaskDialogCommandLink("btn5min", 
              "5 minutes", 
              "Set timer to 5 minutes and start the timer");
    btn5Munutes.Default = true;
    btn5Munutes.Click += (sender, e) => SetInterval(5);
    
    // ... (create other buttons here) 
 
    _dialog.Controls.Add(btn5Munutes);
    // ... (add other buttons to the dialog here) 
}

Here's a function that handles the button click event:

C#
/// Set the interval to N minutes and close the dialog
/// minutes - N, number of minutes to set
void SetInterval(int minutes) {
    _minutes = minutes;
    _dialog.Close(TaskDialogResult.CustomButtonClicked);
}

Please note, to use the task-dialog in your application, you must add the app.manifest file. You can find a sample in the Windows API Code Pack samples, or in my code for this article. If the manifest is absent, you will get an exception: TaskDialog feature needs to load version 6 of comctl32.dll, but a different version is currently loaded in memory.

Jump-list

Jump-list is another amazing feature of Windows 7. I've used it in this application: you can select one of a pre-defined time intervals from the list.

Also, there's a link for the last selection.

All the items are added to the Tasks section. Here's the code:

/// Configures the jump list 
public void ConfigureJumpList() {
    JumpList.AddUserTasks(new[] { GetMinutesLink(5, null, Icon), 
          GetMinutesLink(10, null, Icon), GetMinutesLink(30, null, Icon) });
}
 
/// Adds link "N minutes timer"
/// 
/// minutes - The number of minutes
/// timerName - Name of the timer
/// icon - The icon for the jump-list 
private static JumpListLink GetMinutesLink(int minutes, 
               string timerName, IconReference icon) {
    string title = string.Format("New {0} minute{1} timer", 
                                 minutes, IsPlural(minutes) ? "s" : "");
    string arguments = " -minutes=" + minutes;
    if (!string.IsNullOrEmpty(timerName) && 
               timerName != TimerOptions.Default.TimerName) {
        arguments += string.Format(" -name=\"{0}\"", timerName);
        title += string.Format(" ({0})", timerName);
    }
    return new JumpListLink(Application.ExecutablePath, title)
           { IconReference = icon, Arguments = arguments};
}

Please pay attention: we cannot directly add an icon to the task list - we must pass the IconReference object. There are two options for loading icons:

  • From an .ico file
  • From a resource in an exe file

Here's the code to obtain the IconReference:

C#
/// Gets the icon for the jump-link 
private static IconReference Icon {
    get {
        if (_icon == null) {
            string pathToIcon = Path.Combine(
              Path.GetDirectoryName(Application.ExecutablePath), 
                                    "Timer.ico");
            int iconNum = 0;
 
            if (!File.Exists(pathToIcon)) {
                pathToIcon = Path.Combine(
                   KnownFolders.System.Path, "shell32.dll");
                iconNum = 20;
            }
            _icon = new IconReference(pathToIcon, iconNum);
        }
        return _icon.Value;
    }
}

This code first tries to find the .ico file and adds the IconReference with it. If there's no .ico file on the disk, it will add the task items with the icon in the shell32.dll file.

Please pay your attention on how we get the path to the shell32.dll file: we use the KnownFolders class introduced in the Windows API Code Pack for Windows 7.

Also, we add a custom task every time the timer is started:

C#
if (_totalMinutes != 5 && _totalMinutes != 10 && _totalMinutes != 30) {
    jumpListHelper.AddCustomLink(_totalMinutes, _options.TimerName);
}

After we have added all the items to the jump-list, we have to save it:

C#
/// Saves the jump-list
public void Save() {
    JumpList.Refresh();
}

Here's a jump-list with the last timer settings which appear when you right-click the timer icon in the taskbar:

Jump-list

All that the items in the jump-list do is start the timer app with parameters:

<path_to_the_app> -minutes=<number_of_minutes> -name="<name_of_the_timer>"

Native Methods

Unfortunately, not everything is already implemented in the .NET Framework. I have imported these routines (if you are not familiar with these functions, you can see their description by clicking the following links):

You will find them in the NativeMethods class:

C#
internal static class NativeMethods {
    [DllImport("user32.dll", CallingConvention = CallingConvention.Winapi)]
    public static extern bool FlashWindow(IntPtr hwnd, bool bInvert);
 
    [DllImport("winmm.dll", SetLastError = true, 
       CallingConvention = CallingConvention.Winapi)]
    public static extern bool PlaySound(string pszSound, 
                              IntPtr hMod, PlaySoundFlags sf);
}

The FlashWindow function is used to notify the user about the timeout by highlighting the window icon in orange color:

Flash window

If you open a taskbar thumbnail with a mouse, the windows will look like as shown in the following picture:

Flash window expanded

When the timer has elapsed, we simply use these methods and remove the taskbar progress:

C#
if (_timeLeft.TotalMilliseconds <= 0) {
    TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.NoProgress);
    timer.Stop();
    _buttonPause.Enabled = false;
    NativeMethods.FlashWindow(Handle, true);
    if (!_options.DisableSound) {
        NativeMethods.PlaySound(Path.Combine(KnownFolders.Windows.Path, 
          "Media\\Windows Default.wav"), IntPtr.Zero, 
          PlaySoundFlags.SND_FILENAME | PlaySoundFlags.SND_ASYNC);
    }
}

As you can see, the sound is played asynchronously (using the flag SND_ASYNC), so the function returns immediately.

Points of Interest

In this article, we have learned:

  • How to create updateable thumbnails without a frame;
  • Which events must be handled if we want to create a window that uses owner-draw taskbar thumbnails;
  • How to add a button to a taskbar;
  • Parts of the task-dialog;
  • How to create a jump-list.

Thank you for reading :)

Revision History

  • Nov. 2009 - Initial revision.
  • Jan. 2010 - Added a more detailed description of the taskbar buttons.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vots of 5 Pin
Member 1084613621-Jul-14 2:38
Member 1084613621-Jul-14 2:38 
QuestionSuperb Pin
Dave Kerr20-Apr-12 4:42
mentorDave Kerr20-Apr-12 4:42 
QuestionChange Sound? Pin
Member 872879614-Mar-12 6:11
Member 872879614-Mar-12 6:11 
GeneralFantastic Pin
sunita03264-Jan-12 4:53
sunita03264-Jan-12 4:53 
GeneralMy vote of 5 Pin
Filip D'haene31-Dec-11 4:54
Filip D'haene31-Dec-11 4:54 
QuestionThanks ! Pin
Amir Hosein Nasr1-Oct-11 4:43
Amir Hosein Nasr1-Oct-11 4:43 
GeneralThis is Awesome, if i may add a suggestion/request Pin
Member 774389610-Mar-11 12:25
Member 774389610-Mar-11 12:25 
GeneralMy vote of 5 Pin
M Bester27-Feb-11 23:13
M Bester27-Feb-11 23:13 
GeneralNice! Pin
_rbs_23-Sep-10 10:38
_rbs_23-Sep-10 10:38 
GeneralRe: Nice! Pin
Dimitri Witkowski24-Sep-10 7:15
Dimitri Witkowski24-Sep-10 7:15 
GeneralVB.NET 2008 - Intercept right click menu in windows 7 taskbar [modified] Pin
dxmedia11-Sep-10 2:59
dxmedia11-Sep-10 2:59 
GeneralMy vote of 5 Pin
Eric Xue (brokensnow)6-Sep-10 18:28
Eric Xue (brokensnow)6-Sep-10 18:28 
GeneralMy vote of 5 Pin
Andre xxxxxxx28-Aug-10 23:37
Andre xxxxxxx28-Aug-10 23:37 
QuestionAeroPeek and preview image not synch'd? Pin
Brad Christie12-Mar-10 8:08
Brad Christie12-Mar-10 8:08 
AnswerRe: AeroPeek and preview image not synch'd? Pin
Dimitri Witkowski12-Mar-10 8:23
Dimitri Witkowski12-Mar-10 8:23 
GeneralRe: AeroPeek and preview image not synch'd? Pin
Brad Christie12-Mar-10 14:51
Brad Christie12-Mar-10 14:51 
GeneralSet thumbnail as glass as form [modified] Pin
ddxtreme24-Jan-10 6:40
ddxtreme24-Jan-10 6:40 
GeneralRe: Set thumbnail as glass as form Pin
Dimitri Witkowski28-Jan-10 10:14
Dimitri Witkowski28-Jan-10 10:14 
GeneralAlong with being an excellent article ... Pin
Chris Meech8-Jan-10 7:11
Chris Meech8-Jan-10 7:11 
AnswerRe: Along with being an excellent article ... Pin
Dimitri Witkowski8-Jan-10 7:51
Dimitri Witkowski8-Jan-10 7:51 
GeneralWell done, Dmitry! Pin
Marcelo Ricardo de Oliveira8-Dec-09 11:34
mvaMarcelo Ricardo de Oliveira8-Dec-09 11:34 
GeneralRe: Well done, Dmitry! Pin
Dimitri Witkowski8-Dec-09 12:02
Dimitri Witkowski8-Dec-09 12:02 
GeneralInspiring and useful! Pin
Homncruse8-Dec-09 6:53
Homncruse8-Dec-09 6:53 
GeneralRe: Inspiring and useful! Pin
Dimitri Witkowski8-Dec-09 7:15
Dimitri Witkowski8-Dec-09 7:15 
GeneralExcellent Pin
Muneeb R. Baig7-Dec-09 17:42
Muneeb R. Baig7-Dec-09 17:42 
Excellent and beautifully explained.

Thanks.

-muneeb
A thing of beauty is the joy forever.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.