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

Creating a Timer Using the Amazing New Windows 7 Features

By , 4 Jan 2010
 
Prize winner in Competition "Windows 7 "Windows @ Work" Article Contest"

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)

About the Author

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

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionSuperbmvpDave Kerr20 Apr '12 - 4:42 
A very well written article on these Windows 7 features - great work!

QuestionChange Sound?memberMember 872879614 Mar '12 - 6:11 
I am not a coder, so please keep that in mind.
 
Can I easily change the sound that is used when the timer is completed? I need something very obnoxious. The preset sound is ...too polite for my needs.
 
Thanks!
Ben
GeneralFantasticmembersunita03264 Jan '12 - 4:53 
Exactly what I needed!!! Thank you!!
GeneralMy vote of 5memberFilip D'haene31 Dec '11 - 4:54 
Excellent article!!!
 
Thanks for sharing.   Smile | :)
QuestionThanks !memberAmir43171 Oct '11 - 4:43 
Excellent !
This is very wonderful !
OMG | :OMG: Big Grin | :-D Wink | ;) OMG | :OMG: WTF | :WTF:
GeneralThis is Awesome, if i may add a suggestion/requestmemberMember 774389610 Mar '11 - 12:25 
If this is a working progress, and I'd love to see it further developed, I could only add one suggestion - that would be that you some how add to the options menu a result of the timer running out (shut-down, restart, open a program form source... ext)
 

But great bit of coding, Smile | :)
GeneralMy vote of 5memberM Bester27 Feb '11 - 23:13 
..
GeneralNice!memberMember 72328523 Sep '10 - 10:38 
Very nice article. Thank you.
 
What is the image editor that you used to compose Splash.png?
GeneralRe: Nice!memberDmitry Vitkovsky24 Sep '10 - 7:15 
Thanks. That was either Photoshop, or the gimp, I don't remember now.
Die Energie der Welt ist konstant. Die Entropie der Welt strebt einem Maximum zu.

GeneralVB.NET 2008 - Intercept right click menu in windows 7 taskbar [modified]memberMember 448238611 Sep '10 - 2:59 
I am developing an application in VB.NET 2008 on Windows 7, having a Form with no Title Bar (FormBorderStyle = None, and ShowInTaskbar = True), and I need to intercept and prevent from showing the right click context menu, for my application, in windows 7 taskbar.
 
Is there any way to intercept WM_CREATE or WM_ACTIVATE messages, or any other useful messages in WndProc ?
 
I found an example for intercepting those messages, I don't know if it really helps for my exact request, in C#, on this page:
 
http://www.winvistaside.de/forum/index.php?showtopic=3212
 
I forgot to mention that I can globally hook (subclass) the mouse, and I can intercept the mouse clicks on my application in windows 7 taskbar, but I didn't receive any msg in WndProc when the user actually click on my application, not other applications in windows 7 taskbar.
 
I really need help.
Thank you in advance.

modified on Monday, July 25, 2011 6:57 AM

GeneralMy vote of 5memberEric Xue (brokensnow)6 Sep '10 - 18:28 
well written! have a 5, man!
GeneralMy vote of 5memberAndre xxxxxxx28 Aug '10 - 23:37 
Great sample of the new TaskBar features. I just wonder why the DWM guys didn't use underscores in their identifiers.
 
WM_DWMSENDICONICLIVEPREVIEWBITMAP
 
WM_DWMSEND_ICONIC_LIVE_PREVIEW_BITMAP
QuestionAeroPeek and preview image not synch'd?memberBrad Christie12 Mar '10 - 8:08 
i am fond of the new taskbar, windows has done a great job, but is it a limitation of this application or windows that the preview generated for the "hover" on the taskbar image and the resulting preview on the (what I believe is referred to as the aeropeek) not being synchronized? I'm probably explaining this inadequately, so I'll simplify:
 
a. The application has its own dialog view (exempt from anything taskbar related, just the dialog itself)
b. The hover preview is its own image (hover over the taskbar icon and windows displays a preview which was generated by the application, separate from the dialog's presentation)
c. hovering over item b's preview makes all other windows on your desktop glassy and shows your preview as the dialog's display. [this is where the issue is]
 
I'm faces with the taskbar's preview image showing on my screen off-set from the actual dialog's real-estate and larger than the dialog itself. is this just how windows uses the preview image (it "steals" the preview image and uses it as the dialog's interface for purposes of locating the window with a hover effect?) If so, is this just due to how the preview was made, perhaps because the extends of the preview exceed the dialog's size it just looks weird?
 
A picture is worth 1,000 words so...
http://i44.tinypic.com/mmsw9l.jpg
 
(1) Hover over the image and (2) appears. (3) is the preview when the window has been shrunk to the taskbar (as you can see only the preview displays without the dialog window frame appearing)
KingTomato
http://kingtomato.org

AnswerRe: AeroPeek and preview image not synch'd?memberDmitry Vitkovsky12 Mar '10 - 8:23 
Wow! Blush | :O
Thanks for the reply, I'll try to repeat this of course...
Die Energie der Welt ist konstant. Die Entropie der Welt strebt einem Maximum zu.

GeneralRe: AeroPeek and preview image not synch'd?memberBrad Christie12 Mar '10 - 14:51 
Very fast response!
 
As a follow-up, everything I'm reading suggests assigning an application ID at the program's entry point thought I don't see you do this (though I may have missed it in the code?). I guess to take the strain off Win7 using it's "brute force" method described on channel 9. Also, I assume when you want to add Windows 7 components to a "traditional" application you do conditional checks for TaskBar.IsPlatFormSupported one would assume, or do you feel there is a better way?
 
I'm intrigued by this new API and would like to start implementing it, though at times it can be a bit overwhelming. If you have any other places to go (in additional to the *cough* informative msdn I would be appreciative.
 
Thank you in advance.
KingTomato
http://kingtomato.org

GeneralSet thumbnail as glass as form [modified]memberddxtreme24 Jan '10 - 6:40 
Dmitri, thanks for your sharing, it is a superb project! =)
 
please, i would like to know how can i set the preview thumbnail background as aero glass too...
 
what i mean is, intead of writing the current time is the Splash image that is in resources, i would like to write the timer in a "glass image" as the form, so the thumbnail preview can be transparent as the form...
 
it is possible to do this? thanks for any response
 
modified on Sunday, January 24, 2010 1:32 PM

GeneralRe: Set thumbnail as glass as formmemberDmitry Vitkovsky28 Jan '10 - 10:14 
Hm.. interesting idea Smile | :)
Are there any applications that behave in such way?
GeneralAlong with being an excellent article ...memberChris Meech8 Jan '10 - 7:11 
you've included a flow-chart. That gets a 5. Great work and thanks for sharing it. Smile | :)
 
Chris Meech
I am Canadian. [heard in a local bar]
In theory there is no difference between theory and practice. In practice there is. [Yogi Berra]

AnswerRe: Along with being an excellent article ...memberDmitry Vitkovsky8 Jan '10 - 7:51 
Thank you Wink | ;-)
 
See my article about Windows 7 Taskbar timer here on CodeProject

GeneralWell done, Dmitry!memberMarcelo Ricardo de Oliveira8 Dec '09 - 11:34 
Very nice article and application, you got 5 stars!
 
мои поздравления!
 
Take a look at full source code C# Snooker game here in Code Project.

GeneralRe: Well done, Dmitry!memberDmitry Vitkovsky8 Dec '09 - 12:02 
Спасибо Smile | :)
GeneralInspiring and useful!memberHomncruse8 Dec '09 - 6:53 
I haven't done any such development so closely tied to the Windows API, but wow! Now I'm inspired to do something... if only I had an idea... You make it look so easy! Very well-written and explained. I'll also be downloading the source and compiling it to actually USE it on my PC for cooking times (my PC is upstairs and I can't hear the kitchen timers go off downstairs so I often burn or overcook things).
 
Major kudos.
GeneralRe: Inspiring and useful!memberDmitry Vitkovsky8 Dec '09 - 7:15 
Thank you for the review, hope this small app will be useful to you
GeneralExcellentmemberMuneeb R. Baig7 Dec '09 - 17:42 
Excellent and beautifully explained.
 
Thanks.
 
-muneeb
A thing of beauty is the joy forever.

GeneralNice PostmemberRak_i1 Dec '09 - 6:59 
Good post to start on Windows 7 development Thumbs Up | :thumbsup:

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

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