Click here to Skip to main content
Click here to Skip to main content

Creating a Timer Using the Amazing New Windows 7 Features

, 4 Jan 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
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:

/// 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:

/// 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:

/// 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:

/// 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:

/// 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:

/// 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):

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. 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:

// 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:

/// 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:

_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):

_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):

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:

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:

/// 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:

/// 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:

/// 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:

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:

/// 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:

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:

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)

Share

About the Author

Dmitry Vitkovsky
Software Developer (Senior)
Russian Federation Russian Federation
No Biography provided

Comments and Discussions

 
GeneralMy vots of 5 PinmemberMember 1084613621-Jul-14 3:38 
QuestionSuperb PinmvpDave Kerr20-Apr-12 5:42 
QuestionChange Sound? PinmemberMember 872879614-Mar-12 7:11 
GeneralFantastic Pinmembersunita03264-Jan-12 5:53 
GeneralMy vote of 5 PinmemberFilip D'haene31-Dec-11 5:54 
QuestionThanks ! PinmemberAmir43171-Oct-11 5:43 
GeneralThis is Awesome, if i may add a suggestion/request PinmemberMember 774389610-Mar-11 13:25 
GeneralMy vote of 5 PinmemberM Bester28-Feb-11 0:13 
GeneralNice! PinmemberMember 72328523-Sep-10 11:38 
GeneralRe: Nice! PinmemberDmitry Vitkovsky24-Sep-10 8:15 
GeneralVB.NET 2008 - Intercept right click menu in windows 7 taskbar [modified] PinmemberMember 448238611-Sep-10 3:59 
GeneralMy vote of 5 PinmemberEric Xue (brokensnow)6-Sep-10 19:28 
GeneralMy vote of 5 PinmemberAndre xxxxxxx29-Aug-10 0:37 
QuestionAeroPeek and preview image not synch'd? PinmemberBrad Christie12-Mar-10 9:08 
AnswerRe: AeroPeek and preview image not synch'd? PinmemberDmitry Vitkovsky12-Mar-10 9:23 
GeneralRe: AeroPeek and preview image not synch'd? PinmemberBrad Christie12-Mar-10 15:51 
GeneralSet thumbnail as glass as form [modified] Pinmemberddxtreme24-Jan-10 7:40 
GeneralRe: Set thumbnail as glass as form PinmemberDmitry Vitkovsky28-Jan-10 11:14 
GeneralAlong with being an excellent article ... PinmemberChris Meech8-Jan-10 8:11 
AnswerRe: Along with being an excellent article ... PinmemberDmitry Vitkovsky8-Jan-10 8:51 
GeneralWell done, Dmitry! PinmemberMarcelo Ricardo de Oliveira8-Dec-09 12:34 
GeneralRe: Well done, Dmitry! PinmemberDmitry Vitkovsky8-Dec-09 13:02 
GeneralInspiring and useful! PinmemberHomncruse8-Dec-09 7:53 
GeneralRe: Inspiring and useful! PinmemberDmitry Vitkovsky8-Dec-09 8:15 
GeneralExcellent PinmemberMuneeb R. Baig7-Dec-09 18:42 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411023.1 | Last Updated 4 Jan 2010
Article Copyright 2009 by Dmitry Vitkovsky
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid