DCSIMG
February 2009 - Posts - All Your Base Are Belong To Us

All Your Base Are Belong To Us

Mostly .NET internals and other kinds of gory details

February 2009 - Posts

Windows 7 Taskbar: Thumbnail Toolbars

Let’s recap what we’ve seen so far.  We’ve started with the fundamentals of the Windows 7 taskbar APIs and the scenarios they address, reviewed the concept of application id, enriched the taskbar button with progress bars and overlay icons, and took a deep long look from all angles at the jump list, including recent/frequent categories, user tasks and custom destinations.  The next feature on our desk(top) today is thumbnail toolbars.

Thumbnail toolbars are another productivity feature that gives us the ability to do more without actually switching to another application’s window.  A thumbnail toolbar is essentially a mini remote-control that is displayed when you hover over the application’s taskbar button, right beneath the application’s thumbnail.  For example, here is the Windows Media Player thumbnail toolbar, featuring the “Play”, “Previous” and “Next” toolbar buttons.

image

A “remote control” of this sort might not be appropriate for any application, but it’s extremely handy to have.  Without interrupting your work in one application, you can easily control what another application does – no switching or tabbing need to get there.

How do you add thumbnail toolbars to your application?  As always, the managed wrappers come to the rescue.  The ThumbButtonManager class (in the Windows7.DesktopIntegration project) manages the application’s thumbnail toolbar, and allows up to 7 buttons to be added to it (this is a Windows limitation, not a limitation of the managed wrapper).  The ThumbButton class represents an individual button on the thumbnail toolbar, and exposes the Clicked event which the application can handle in response to a button click.  Other convenience properties include Enabled and Visible, which change the button’s state in real-time.  (Note that there is no way to completely remove a button after adding it.)

The following code (taken from the Windows7.DesktopIntegration.MainDemo project) demonstrates how to add a thumbnail toolbar to an application.  Note that like all other taskbar-related activities, this should be done only after receiving the “TaskbarButtonCreated” message.

_thumbButtonManager = this.CreateThumbButtonManager();

ThumbButton button2 = _thumbButtonManager.CreateThumbButton(102, SystemIcons.Exclamation, "Beware of me!");

button2.Clicked += delegate

{

    statusLabel.Text = "Second button clicked";

    button2.Enabled = false;

};

ThumbButton button = _thumbButtonManager.CreateThumbButton(101, SystemIcons.Information, "Click me");

button.Clicked += delegate

{

    statusLabel.Text = "First button clicked";

    button2.Enabled = true;

};

_thumbButtonManager.AddThumbButtons(button, button2);

Note that you have tooltips and icons at your disposal to personalize the thumbnail toolbar to your application’s needs.  All you need to do now is override your windows’ window procedure and call the DispatchMessage method of the ThumbButtonManager, so that it can correctly route the event to your registered event handlers (and of course, don’t forget to call the default window procedure when you’re done!):

if (_thumbButtonManager != null)

    _thumbButtonManager.DispatchMessage(ref m);

 

base.WndProc(ref m);

While we’re at it, let’s make note of the not-so-obvious distinction between user tasks and thumbnail toolbar buttons.  A user task within a jump list does not have context – it is always present, regardless of whether the application is currently running or whether it is engaged in some sort of activity.  On the other hand, the thumbnail toolbar is present only when the application is running, and its buttons might be disabled or even hidden – based on the current context of the application.  (E.g. it would make little sense to enable the “Next” button in a media player if there is no current playlist.)

Windows 7 Trigger-Start Services

I’m interrupting our scheduled series on the Windows 7 taskbar to talk about something entirely different: Trigger-start services.  (Don’t worry, we will resume the exhaustive overview of all taskbar features fairly soon.)

Over the years, as Windows has become more complicated, more and more background services were added to the system.  Componentization and new features are the two primary forces behind this current.  Hardware and software manufacturers have jumped the opportunity – webcams, IM clients, MP3 players and photo editing applications all seem to have a burning desire and need for a background service doing something.

And these “somethings” accumulate.  The user might not know or even care that he has 90 services running on the system, but he sure will feel their impact even on modern hardware.  The more background services in the system, the longer the boot and shutdown times, one of the main latency issues plaguing the Windows OS.  The more background services in the system, the more resources they consume – memory utilization, I/O and CPU, as well as the adverse effect on power consumption.  The more background services in the system, the wider the security attack surface becomes, especially considering that many services run with the highest system credentials (the LocalSystem built-in account).

The only solution for this stack of problems is moving code out of permanently-running services and into other hosts of background activity – scheduled tasks and trigger-start services.  Scheduled tasks have been available in previous versions of Windows, but their true blossom was in Windows Vista, when a multitude of core OS functionality was moved into tasks, which we won’t be discussing within the scope of this post.

image

Trigger-start services are a brand new mechanism present in Windows 7.  A trigger-start service is not always running – it is triggered by one of a set of triggers, ranging from the availability of a network IP address and all the way to the arrival of a specific hardware device.  When the service completes its work, or when another trigger occurs, it typically terminates until the start trigger happens again.

Examples might include an antivirus on-demand scan service which scans removable disk drives as they are plugged into the system; a media player service which automatically starts when a media device is detected; a policy management service that is invoked when group policy changes or when the computer joins or leaves a domain; – and I’m sure that you could come up with a few great examples of your own.

Configuring a service to trigger-start is not trivial, considering that the Windows 7 Services MMC UI does not have any graphical knobs for this task.  However, the good old sc.exe utility is always there to the rescue, and there’s also the hardcore way of doing this through code with ChangeServiceConfig2.

Making a service trigger-start with sc.exe

So the first way is relatively straight-forward.  Querying a service for its triggers is only a single line away (within an elevated command prompt):

sc qtriggerinfo <SERVICENAME>

Configuring a service to trigger-start when the computer acquires its first IP address is similarly easy:

sc triggerinfo <SERVICENAME> start/networkon

More complicated configuration options, such as starting the service upon arrival of a specific hardware device, will all become clear if you just run sc triggerinfo on the command line and read all it has to tell you.

Making a service trigger-start from code

From code, hmm.  Well, we will have to use ChangeServiceConfig2 which I mentioned above.  A purely managed wrapper is possible, but due to the nature of these APIs it’s much easier to work with them from C++, so C++/CLI is the way to go if you need these features in a managed application.

The following C++/CLI code determines whether the specified service is configured to trigger-start, without querying its specific triggers:

bool ServiceControl::IsServiceTriggerStart(System::String^ serviceName)

{

    SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (hSCManager == NULL)

    {

        throw Marshal::GetExceptionForHR(GetLastError());

    }

 

    marshal_context context;

 

    SC_HANDLE hService = OpenService(hSCManager,

        context.marshal_as<LPCWSTR>(serviceName), SERVICE_ALL_ACCESS);

    if (hService == NULL)

    {

        DWORD dwLastError = GetLastError();

        CloseServiceHandle(hSCManager);

        throw Marshal::GetExceptionForHR(dwLastError);

    }

 

    DWORD cbBytesNeeded = (DWORD)-1;

    QueryServiceConfig2(hService, SERVICE_CONFIG_TRIGGER_INFO, NULL, 0, &cbBytesNeeded);

    if (cbBytesNeeded == (DWORD)-1)

    {

        DWORD dwLastError = GetLastError();

        CloseServiceHandle(hService);

        CloseServiceHandle(hSCManager);

        throw Marshal::GetExceptionForHR(dwLastError);

    }

    PBYTE lpBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbBytesNeeded);

 

    if (QueryServiceConfig2(hService, SERVICE_CONFIG_TRIGGER_INFO, lpBuffer, cbBytesNeeded, &cbBytesNeeded) == FALSE)

    {

        DWORD dwLastError = GetLastError();

        CloseServiceHandle(hService);

        CloseServiceHandle(hSCManager);

        HeapFree(GetProcessHeap(), 0, lpBuffer);

        throw Marshal::GetExceptionForHR(dwLastError);

    }

 

    PSERVICE_TRIGGER_INFO pTriggerInfo = (PSERVICE_TRIGGER_INFO)lpBuffer;

    BOOL bTriggerStart = pTriggerInfo->cTriggers > 0;

 

    HeapFree(GetProcessHeap(), 0, lpBuffer);

 

    CloseServiceHandle(hService);

    CloseServiceHandle(hSCManager);

 

    return bTriggerStart;

}

The following C++/CLI code configures the specified service to trigger-start when a USB storage device (e.g. a disk on key) is inserted into the system:

static const GUID GUID_USBDevice = {

    0x53f56307, 0xb6bf, 0x11d0, {0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b }};

 

void ServiceControl::SetServiceTriggerStartOnUSBArrival(System::String^ serviceName)

{

    SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (hSCManager == NULL)

    {

        throw Marshal::GetExceptionForHR(GetLastError());

    }

 

    marshal_context context;

 

    SC_HANDLE hService = OpenService(hSCManager,

        context.marshal_as<LPCWSTR>(serviceName), SERVICE_ALL_ACCESS);

    if (hService == NULL)

    {

        DWORD dwLastError = GetLastError();

        CloseServiceHandle(hSCManager);

        throw Marshal::GetExceptionForHR(dwLastError);

    }

 

    LPCWSTR lpszDeviceString = L"USBSTOR\\GenDisk";

 

    SERVICE_TRIGGER_SPECIFIC_DATA_ITEM deviceData = {0};

    deviceData.dwDataType = SERVICE_TRIGGER_DATA_TYPE_STRING;

    deviceData.cbData = (wcslen(lpszDeviceString)+1) * sizeof(WCHAR);

    deviceData.pData = (PBYTE)lpszDeviceString;

 

    SERVICE_TRIGGER serviceTrigger = {0};

    serviceTrigger.dwTriggerType = SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL;

    serviceTrigger.dwAction = SERVICE_TRIGGER_ACTION_SERVICE_START;

    serviceTrigger.pTriggerSubtype = (GUID*)&GUID_USBDevice;

    serviceTrigger.cDataItems = 1;

    serviceTrigger.pDataItems = &deviceData;

 

    SERVICE_TRIGGER_INFO serviceTriggerInfo = {0};

    serviceTriggerInfo.cTriggers = 1;

    serviceTriggerInfo.pTriggers = &serviceTrigger;

 

    if (ChangeServiceConfig2(hService, SERVICE_CONFIG_TRIGGER_INFO, &serviceTriggerInfo) == FALSE)

    {

        DWORD dwLastError = GetLastError();

        CloseServiceHandle(hService);

        CloseServiceHandle(hSCManager);

        throw Marshal::GetExceptionForHR(dwLastError);

    }

 

    CloseServiceHandle(hService);

    CloseServiceHandle(hSCManager);

}

Summary

Clearly, developing trigger-start services might be more difficult than a resident process that runs all the time from boot to shutdown.  However, the positive effect on the user’s environment from the reliability, security, performance and power consumption perspectives often outweighs this minor inconvenience.  If you’re developing a background service for Windows today, see if you can make it a scheduled task or a trigger-start service instead.

Windows 7 Taskbar: Custom Destinations

We’ve already seen how to use the recent and frequent jump list categories in our Windows 7 taskbar jump list, and how to add user tasks to the jump list, enabling additional custom functionality.

However, most file-oriented applications will also benefit from the ability to add custom destinations grouped into categories to the taskbar jump list, providing additional destinations beyond the recent and frequent ones populated by the system.

A custom destination is a categorized entry in the application’s jump list, typically representing an item that the application can open and handle.  For example, Internet Explorer 8 populates a “History” category in the jump list which contains links to pages you have visited recently; an email application could have categories for “Inbox”, “Important”, “To Follow-Up” and whatnot.  There’s no limit on the number and variety of categories that you can create, other than the limits of the screen.

By their nature, custom destinations are nouns – they represent items (usually files) that the application can open and handle.  Therefore, most custom destinations are IShellItem objects, although IShellLink objects can be used as well.

When creating a new jump list and populating it with custom destinations, there are two important things to bear in mind before you start:

  1. There is a limited amount of screen estate for your categories and items.  You can retrieve the maximum number of items that can be placed in the jump list by querying the JumpListManager.MaximumSlotsInList property of the managed wrapper.
  2. The jump list belongs to the user and not to you, and therefore the user may choose to remove items from the jump list, using the right-click context menu on the item, as in the following screenshot.  When your application repopulates the jump list for whatever reason, it’s important that you bear the user’s choices in mind.  Specifically, if you attempt to add an item to the jump list that the user has removed earlier, you will encounter an exception.  For this scenario, the JumpListManager.UserRemovedItems event will help you find out that the user removed items from the list.  (Later in this post we discuss this in detail.)

image

First things first.  Let’s see how we can add a couple of items with different categories to the application’s jump list.  The following code is taken directly from the Windows7.DesktopIntegration.MainDemo project, and is used to initialize the jump list we’ve seen in the previous screenshot:

_jumpListManager.AddCustomDestination(new ShellLink

{

    Path = Path.Combine(_thisAppDataPath, "sonnet.txt"),

    Title = "Shall I compare thee...",

    Category = "My Category",

    IconLocation = shell32DllPath,

    IconIndex = 1

});

_jumpListManager.AddCustomDestination(new ShellLink

{

    Path = Path.Combine(_thisAppDataPath, "mary.txt"),

    Title = "Mary had a little...",

    Category = "My Category",

    IconLocation = shell32DllPath,

    IconIndex = 1

});

_jumpListManager.AddCustomDestination(new ShellLink

{

    Path = Path.Combine(_thisAppDataPath, "hello.html"),

    Title = "Hello World",

    Category = "My Other Category",

    IconLocation = shell32DllPath,

    IconIndex = 5

});

_jumpListManager.AddCustomDestination(new ShellItem

{

    Path = Path.Combine(_thisAppDataPath, "raven.txt"),

    Category = "My Other Category"

});

if (_jumpListManager.Refresh())

{

    statusLabel.Text = "Maximum slots in list: " +

        _jumpListManager.MaximumSlotsInList;

}

This code shows that ShellLink and ShellItem objects can be freely added to the jump list, and also demonstrates the use of the MaximumSlotsInList property that I’ve mentioned earlier.  Again, the jump list initialization statement is missing here, but we have already seen it in a previous post.

Now, what’s with all these items that could potentially be removed by the user?  Well, the jump list is designed to let the application know about removed items only when the jump list is being built.  The managed wrapper exposes this information using the UserRemovedItems event on the JumpListManager class, which your code must register to (or else the wrapper will throw an exception).  This ensures (at least in part) that you’re aware of the requirement to handle removed items.

The suggested flow for a well-behaved application that handles user-removed items is the following:

  1. Register to the UserRemovedItems event;
  2. Build the jump list and call the Refresh method to persist the changes;
  3. If the UserRemovedItems event is invoked, set the CancelCurrentOperation property of the UserRemovedItemsEventArgs object to true, indicating that the current jump list building transaction will be aborted; record the removed items from the RemovedItems property of the same object so that you’ll know not to add them in the next jump list building transaction;
  4. Check the return value of the Refresh method to determine whether the jump list building transaction completed successfully; if it hasn’t, build the jump list again, and this time do not add the items that you previously recorded as removed.

The above process occurs synchronously on the thread that invokes the Refresh operation, so there are nothing to worry about with regard to thread safety.

Now that we’re through with adding custom destinations to the jump list, we can safely say that the grounds of working with Windows 7 taskbar jump list are covered.  We’ve seen how to populate the jump list with recent/frequent items, how to add user tasks to the jump list, and finally how to add categorized custom destinations to the jump list.  In the next posts in this series, we will look at additional aspects of the Windows 7 taskbar, unrelated to jump lists.

Windows 7 Taskbar: User Tasks

In the previous installment in this series, we have seen how the default support for recent and frequent documents provided by the operating system can give our application’s jump list a default vivid appearance without any effort on our part.  We have also seen that in specialized custom scenarios, it’s possible to add items to the recent/frequent categories explicitly.

In this post, we will look into customizing the jump list with user tasks, which are shortcuts to functionality our application can provide.  As you might remember, user tasks are the verbs in our vocabulary: Windows Media Player provides a “Resume last playlist” task and Sticky Notes provides a “New note” task.

A user task is usually an IShellLink object that launches your application (or another application) with specific command line arguments.  Tasks cannot be categorized, but they can be separated using a special separator object.  Here’s an example of a jump list that uses a separator between two tasks:

image

Adding tasks to the application’s jump list is as easy as one-two-three using the managed wrapper library (Windows7.DesktopIntegration).  Here’s all the code required to build the jump list section illustrated in the above screenshot:

_jumpListManager.AddUserTask(new ShellLink

{

    Path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "notepad.exe"),

    Title = "Launch Notepad",

    IconLocation = shell32DllPath,

    IconIndex = 14

});

_jumpListManager.AddUserTask(new Separator());

_jumpListManager.AddUserTask(new ShellLink

{

    Path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "calc.exe"),

    Title = "Launch Calculator",

    IconLocation = shell32DllPath,

    IconIndex = 15

});

_jumpListManager.Refresh();

(There’s also one line of code required to initialize the jump list manager, which we have seen in the previous post.)

Note that for each ShellLink object you are required to provide a path, a title and preferably also an icon location and index (I’m using the icons embedded into shell32.dll in the above example).  Other potentially useful properties on the ShellLink class include Arguments (for command-line arguments), WorkingDirectory and ShowCommand (for determining how the window will be shown).

Of course it is now your responsibility to integrate this functionality into your application.  For example, if you wanted to implement a “Resume last playlist” command in your custom media player, you would need to provide some sort of communication mechanism between the process launched by the user task and the existing media player process, if it were already loaded.  This is outside of the scope of what a framework can provide for you – but various creative solutions are possible.

Windows 7 Taskbar: Jump Lists

imageThe Start Menu has long been with us; it provides a launch surface for applications and recent documents.  The Vista Start Menu revolutionized the accessibility of these applications and documents by giving us the amazing Search box which can enhance productivity in so many ways.  However, in spite of these improvements, it still cannot provide access to tasks and documents that are specific to a single application.  When the application is up, it can show us a menu or offer common tasks – but if the application is not running, the only thing we can do is start it up.

With the consolidation and revolution of launch surfaces that is integral in the design of the Windows 7 desktop, EVERY application now has a start menu.  This start menu, termed the jump list, gives every application a place for categorized destinations and tasks that are specific to that application.

There are two manifestations of the jump list in the current Windows 7 UI: As part of the traditional Start Menu, and as a popup menu from the application’s taskbar button.

Here’s a screenshot of my own start menu, showing the Windows Live Writer and Windows Media Player jump lists:

image image

Alternatively, here’s a screenshot of the popup jump list from the Windows Live Writer and Windows Media Player taskbar buttons:

imageimage

There are several things worth paying attention to in these screenshots (other than the completely fictitious .avi files :-))  – the jump list seems to have many customizable parts that are different across different applications (and we have just two examples to look at!).  Let us examine a sample jump list that demonstrates more functionality.  This is the same jump list you can see by launching the Windows7.DesktopIntegration.MainDemo project, registering the file type (Administrative –> Register File Extensions) and then clicking the Build Jump List button.

image

Let’s start bottom to top.  The three items at the bottom of the menu are the so-called taskbar tasks.  These are items you would see for every application out there, and they give you the option of launching the app, pinning/unpinning it from the taskbar and closing its window (if it’s running).

Going up the list is the Tasks subsection, within which the so-called user tasks reside.  Despite the name, the application (and not the user) provides these tasks.  These are typically shortcuts (IShellLink objects) that provide some general functionality, usually related to the application.  For example, the Windows Media Player user tasks are “Resume last playlist” and “Play all music shuffled”.  These tasks do not depend on any document, and are verbs in our vocabulary.

Next in the list we see the Recent category, which is one of the predefined categories provided by the operating system.  In this category we find a collection of destinations, which are usually documents your application can handle and open.  Destinations are nouns, usually IShellItem objects, but IShellLink objects can be treated as destinations as well.  By the way, as I mentioned in a previous post, if your application doesn’t have a well-defined file type, it doesn’t mean you can’t have virtual items in the list (for example, IShellLink objects that pass a parameter to your application telling it what to do).

The Recent category is not the only system-provided category.  As we’ve seen above in the Windows Media Player example, another category provided by the system is the Frequent category.  It usually doesn’t make sense to include both, but you could if you really wanted to.  The last system-provided category is the Pinned category (not displayed in the above screenshot), which contains items that the user explicitly asked to see on the jump list by right-clicking and selecting “Pin to this list”.  There is no programmatic way to control this category – it’s purely up to the user’s discretion.

Next in the list we see two custom categories, which again contain an assortment of destinations.  Some of them have customized icons – these are the IShellLink destinations.

That’s awesome, you say?  How do we put our goodies in our application’s jump list, you ask?  Well, going down the native Win32 path might be a little unpleasant, so luckily we have the managed wrappers handy.

First things first.  In this post, we will look at how to take advantage of what the operating system can do for us – how to use the Recent and Frequent categories.  In subsequent posts, we will look into custom categories and user tasks.

So let’s get to it.  What does the Windows Shell consider a recent or frequent item?  Well, it’s just a file, that your application is associated with in the registry (not necessarily as the default handler), and that was either:

  • Used to launch your application directly (e.g. double-clicking a file in Windows Explorer)
  • Selected by the user in a common file dialog (open/save)
  • Specified as the parameter to the SHAddToRecentDocs API function

The first two scenarios require you to do … absolutely nothing! – and you get the Recent or Frequent category for free.  The third scenario requires mild interop, which happens to be part of the managed wrapper as well (specifically in JumpListManager.AddToRecent).

Let’s see how it works.  First of all, you need a registered file association with the extension in question.  There are lots of walkthroughs for doing this, including this detailed MSDN page.  For convenience, the Windows7.DesktopIntegration.Registration console application shows how to register a file type association.  It’s nowhere near recommended or thoroughly tested, but it works well enough in the Windows7.DesktopIntegration.MainDemo demo app.

Next, you need to ensure that you let the shell know when you open or save a document that belongs to your application.  The easy way of doing this is to go through the common file dialogs, which in Windows 7 include the concept of Libraries (not covered today…).  If you resort to using your own custom UI for opening/saving files, you should call SHAddToRecentDocs explicitly to let the shell know what you’re doing.

Let’s take a look at the code from the Windows7.DesktopIntegration.MainDemo application which shows how easy it is to add a recent item to the jump list explicitly:

_jumpListManager = this.CreateJumpListManager();

_jumpListManager.UserRemovedItems += (o, e) => {};

_jumpListManager.AddToRecent(Path.Combine(_thisAppDataPath, "mary.txt"));

Let’s do it step by step:

  1. You need an instance of JumpListManager.  It can be created after you have a taskbar button, so as before, wait for the “TaskbarButtonCreated” message and then create an instance.
  2. The default category is Recent, so if you want the Frequent category you need to set the JumpListManager.EnabledAutoDestinationType property to ApplicationDestinationType.Frequent.
  3. You need to register for the JumpListManager.UserRemovedItems event – this event is invoked if, when building the jump list, we detect the user removed an item from the jump list.  (We will discuss this in more detail in a subsequent post.)
  4. You need to call JumpListManager.AddToRecentDocs with the file name you want to add (it’s also possible to provide an IShellItem or IShellLink, but we won’t go there just yet).
  5. If at any time you want to clear the jump list*, you can call JumpListManager.ClearApplicationDestinations and the recent/frequent category will become empty.  (Custom categories are not cleared by this method; the pinned category can’t be cleared programmatically at all.)

* Note that you shouldn’t just go around clearing the jump list randomly or when the application starts.  The reasonable use cases for clearing the recent/frequent category might be if the user requests to do so through the application UI (e.g. “Clear History”), if the user uninstalls the application, etc.

So there we have it – our application is now jump list-enabled, at least in the sense that its recent/frequent documents now show up as part of the jump list!  Note that if you already have a file association in place, and are using the common file dialogs to open/save files, you don’t need to do anything to get this functionality working.

In the next post, we will look at custom destinations and user tasks, which are explicit new mechanisms that aren’t triggered on for you by the system.

Windows 7 Taskbar: Overlay Icons and Progress Bars

Let’s get the easy stuff out of the way before we start building jump lists and customizing live previews.  How do you leverage the cool functionality of having a dynamic overlay icon on your taskbar button or a multi-state progress bar?

For an overview of the fundamental features themselves and the managed wrapper library, you might want to read previous posts in this series.  This post assumes you’re up to speed on the above, and have a copy of the managed wrappers (Windows7.DesktopIntegration) from Code Gallery.

By using overlay icons and progress bars, your application can provide contextual status information to the user even if the application’s window is not shown.  The user doesn’t even have to look at the thumbnail or the live preview of your app – the taskbar button itself can reveal whether you have any interesting status updates.

Overlay Icons

image The native functionality is exposed by the ITaskbarList3 interface, specifically its SetOverlayIcon method.  The method takes a window handle, an icon handle, and an optional description text for accessibility purposes.

The managed wrapper for this feature lives in Windows7Taskbar.SetTaskbarOverlayIcon, which allows you do basically the same thing, but with an Icon class instance instead of an icon handle.  An extension method in the WindowsFormsExtensions.cs file allows you to use a Form instance instead of a window handle.  (It’s equally easy to provide an extension method that does this to a WPF Window.)

The only tricky thing could be that you can set the overlay icon only after the taskbar button for your window has already been created.  This does not happen before the WM_CREATE notification (or, if you’re writing managed code, your form/window’s constructor) – you need to wait for a special message letting you know that it happened.  There is currently no header-file definition for the value of this message, so you must obtain it manually by calling RegisterWindowMessage(L"TaskbarButtonCreated"), or use the Windows7Taskbar.TaskbarButtonCreatedMessage property.  (And handle it in your form/window’s window procedure.)

The IMClient sample shows how to set an overlay icon as a result of changing the selection in the status combo box.

The underlying code is trivial – it’s just a matter of getting the right icon:

private void cmbStatus_SelectedIndexChanged(

    object sender, EventArgs e)

{

    Icon icon = GetIconByStatus(

        (string)cmbStatus.SelectedItem);

    Windows7Taskbar.SetTaskbarOverlayIcon(

        Handle, icon, (string)cmbStatus.SelectedItem);

}

Progress Bars

image The native functionality is again in the ITaskbarList3 interface, this time in the SetProgressState and SetProgressValue methods.  The methods are quite self-explanatory, and allow setting the progress bar’s state (e.g. indetermine, error) and progress value.

The managed wrapper lives in Windows7Taskbar.SetProgressState and Windows7Taskbar.SetProgressValue – the API is simple so the wrapper is thin :-)  Again, there is an extension method in WindowsFormsExtensions.cs that allows you to specify a Form instance instead of a window handle.

Similarly to overlay icons, the taskbar progress bar can’t be manipulated if the window’s taskbar button has not been created yet.

The IMClient sample shows how to change the progress bar’s value and state in response to a “Send File” request that comes from the user.  The code is trivial:

private void sendFileToolStripMenuItem_Click(

    object sender, EventArgs e)

{

    sendFileTimer.Interval = 1000;

    sendFileTimer.Tick += delegate

    {

        _percentFileCompleted += 10;

        if (_percentFileCompleted == 100)

        {

            sendFileTimer.Stop();

            MessageBox.Show("File operation failed!");

            Windows7Taskbar.SetProgressState(

               Handle,

               Windows7Taskbar.ThumbnailProgressState.Error);

            _percentFileCompleted = 0;

        }

        else

        {

            Windows7Taskbar.SetProgressValue(

                Handle,

                (ulong)_percentFileCompleted,

                (ulong)100);

        }

    };

    sendFileTimer.Start();

}

As you see, the code emulates an “error” in order to demonstrate the error state of the progress bar.  As you might expect, the progress bar turns red but still retains the value.

The best part of taskbar progress bars is that you get this functionality FOR FREE if you use the standard progress dialog for file operations (as we advance in this series, you’ll see that you get lots of functionality for free if you follow the standard guidelines of Windows programming).  I.e., if you invoke a file operation using the SHFileOperation API or the new (shipped with Vista) IFileOperation interface, the progress information (including errors) of that operation will be automatically displayed as a progress bar in your taskbar button.  This is what Windows Explorer does, with great success.

Windows 7 Taskbar: Application Id

Before we can address the subtleties of using various features that have to do with taskbar buttons, we must understand how these taskbar buttons are assigned and created in the first place.

image

The most natural inclination is to say this: If you have multiple top-level windows* created by the same process, then they all belong to the same taskbar button.  Furthermore, if you have M instances of that same process (with possibly N windows displayed by each instance), then these MxN instances all belong to the same taskbar button.

This intuitive approach works very well for the vast majority of applications, and this is also the default.  Try launching multiple Notepad windows and see them all grouped under the same taskbar button.

However, some applications might require more sophisticated behavior.  For example, assume that you have a host process that runs various types of plug-in applications (e.g. an “Office” host process that runs “Word”, “Excel” and “PowerPoint”).  This host process doesn’t want a taskbar button at all, while its plug-ins want their own private taskbar button for each application type.

This variety of scenarios is addressed by assigning each window an identifier called the Application ID (app id) which determines the taskbar button to which the window belongs.  The default app id for a window is a default app id generated for the process to which the window belongs, which is in turn a default app id generated for the executable file that the process runs.

These defaults explain very well the default behavior for windows within multiple processes of the same executable.  The customized behavior we discussed above can be obtained by explicitly setting the app id of the various windows to different values – a different value for each plug-in that wants its own taskbar button.

If you wanted to have a separate taskbar button for each process (including all windows  owned by that process), you can also set an explicit app id for the entire process, which affects all windows within that process that did not set an explicit app id.

Setting the explicit process app id is fairly straightforward – it involves a single call to the SetCurrentProcessExplicitAppUserModelID function.  In the managed wrapper, this is exposed by the Windows7Taskbar.SetCurrentProcessAppId method which takes a string and sets the app id (there is also an equivalent Get method).

Setting the app id for a window is slightly less straightforward – it requires calling the SHGetPropertyStoreForWindow function and then manipulating the resulting IPropertyStore object to retrieve the requested property.  While this isn’t rocket science, the managed wrapper is certainly more user-friendly – Windows7Taskbar.SetWindowAppId does the job (it takes a window handle and a string).  There is also an extension method that takes a Form instance – and it’s easy to write another extension method that will take a WPF Window instance.

In the Windows7.DesktopIntegration.MainDemo project you will find a button that changes the window’s app id.  You can experiment with that by running two instances of the demo app and clicking the button in one of them.  This will result in having two taskbar buttons, one for each window, instead of the default single taskbar button that has both windows “behind it”.  (The code is trivial, so I won’t even bother repeating it here.)

By the way, the window app id is dynamic, so it’s entirely possible for a window to show up as part of one taskbar button and then change its app id so that it appears on an entirely different taskbar button.  This has interesting effects: for example, the jump list is attached to a taskbar button (with a specific app id), so the same window might show a different jump list when it is reattached to an entirely different taskbar button.  This has a good potential to confuse users, however, so the recommended practice would be setting the window app id and sticking to it (using the same deterministic process for determining the app id every time the window is shown).

As an aside, there are additional ways to affect the app id of a window: through the IShellLink object (shortcut) that represents the window’s application, through the file association registration in the registry, when constructing a jump list and in other scenarios.  We might return to this later when we discuss jump lists.  The one thing to remember for now is that if you begin using explicit app ids, then you need to do so consistently – if you forget to explicitly specify the app id somewhere, you will likely affect something completely different from what you meant.

* The Windows 7 DWM only recognizes top-level windows.  To display a child window (e.g. a tab from a tab control or an MDI document) as a separate thumbnail in the taskbar, we must resort to custom previews – which will be the subject of a future post.

Windows 7 Taskbar: APIs

After all the introductions (see also the Windows 7 blog), there’s nothing left but to dive into the APIs you need to leverage the new Windows 7 Taskbar functionality.  The SDK contains reasonably good documentation for this stage of the product.  However, the documentation addresses the native code developer - Win 32 API and COM. What about all the managed code developers out there who want to opt into the Windows 7 Taskbar experience?  In the near future, the Windows Bridge (still a code name) project will include Windows 7 features like the Taskbar and Libraries.  However, until that time, we want to provide you with the following managed code wrapper for the Windows 7 Taskbar. This wrapper is based on the Vista Bridge Sample Library V1.3.

But first things first – where is the code?  How do you get the code?  By the time you read this, the code will have been uploaded to the MSDN Code Gallery, and can be downloaded from http://code.msdn.microsoft.com/Windows7Taskbar (direct download link).

By the way, if you want to understand the native side of things, then also take a look at Yochay’s partial overview of the native APIs (and there’s even more content on Channel9).

The general structure of the solution is:

Let’s take a sneak peek at the main classes that provide an entry point to the taskbar functionality (we will look at them in more detail in subsequent posts):

  • The Windows7Taskbar class provides low-level facilities for manipulating the process-wide and window-specific application ids, controlling custom window preview bitmaps, setting the taskbar overlay icon and progress bar and clipping the taskbar thumbnail.
  • The JumpListManager class provides an abstraction of the application jump list, including facilities to manipulate custom destinations, user tasks, recent/frequent lists and items removed by the user.
  • The ThumbButtonManager class provides an abstraction of the taskbar thumbnail toolbar, including the facilities to create thumbnail toolbar buttons and receive a notification when they are clicked.
  • The CustomWindowsManager class provides an abstraction of a customized window thumbnail preview and live preview (peek), including the facilities to receive a notification when a preview bitmap is requested by the Desktop Window Manager (DWM) and to automatically grab the preview bitmap of a window.

Let’s also see what the demo projects demonstrate:

  • The DocumentReader sample demonstrates how thumbnail clipping and thumbnail customization can provide a better user experience when the information displayed in the thumbnail is too small or unreadable by default.

image

  • The IMClient sample demonstrates how taskbar overlay icons and taskbar progress bars can light up an application’s taskbar button instead of relying on an additional dialog or on an icon in the system notification area (tray).

image image image

  • The WebBrowser sample demonstrates how custom window previews can be used to expose a list of child windows (browser tabs) as thumbnail- and peek-enabled windows in the taskbar. (Please note that due to the architecture of the underlying native feature, this managed implementation leaves much to be desired.)

image

  • The MainDemo sample demonstrates a glimpse into nearly each part of the new taskbar functionality, including registering a file type association, building a jump list with custom categories and custom tasks, setting an overlay icon and progress bar and many other features.

image

Curious what goes into the implementation of each feature?  How to use only a few lines of managed code to build a jump list, provide a taskbar progress bar or overlay icon, customize your thumbnail preview and more?  Stay tuned for subsequent posts.

A Spoiled Riddle

Here’s a C riddle: The following program was obviously expected to assign 1 to all the prime indices in the array and 0 to all the others.  However, it enters an infinite loop.  Explain.

#define ARR_SIZE 10

 

IsPrime(n)

int n;

{

    int i;

    for (i = 1; i < n; ++i)

        if (n % i == 0) return 0;

    return 1;

}

 

main()

{

    int arr[ARR_SIZE];

    int it;

 

    for (it = 0; it <= ARR_SIZE; ++it)

        arr[it] = IsPrime(it);

}

My first intuition about this was obviously something like “hey, it’s an off-by-one with the array index”.  But an infinite loop?

Let’s cut to the chase: The obvious intent of the riddle’s authors is that arr[10] is essentially the stack location that holds it, our loop variable.  So basically when we overflow the array we reset the index back to IsPrime(10) which happens to be 0 and the loop restarts, indefinitely.

However, the riddle was totally spoiled for me by the following thoughts:

  • Had I run it in Visual Studio, Debug mode, I’d toggle a runtime check failure when the stack around the arr array is corrupted (debug runtime checks would have a canary set up to catch this).
  • Had I run it in Visual Studio, Debug mode, without runtime checks, it would simply exit doing nothing because in Debug there’s enough stack space around each local variable to accommodate a small family.
  • Had I run it in Visual Studio, Release mode, one (or two) of two things would ruin the riddle:
    • The it loop variable is most likely to sit in a register, so overflowing the stack will have no effect on its value whatsoever.
    • Even if it doesn’t sit in a register, the assignments to arr are likely to be eliminated by the optimizer because the program doesn’t seem to need them.  In fact, a smart-enough optimizer would optimize this whole program into a nop.

Running these tests in Visual Studio verified, indeed, that this programming riddle has failed to amuse me.  :-)

Enable Windows Search Indexing on Folders

The Windows Search, introduced in Vista as part of the core operating system, is an extremely valuable tool in my pocket.  I barely ever use the start menu, ever – and why would I, if every program is just a few keys on the keyboard away?

image

Windows Search indexing (for faster search results) is enabled by default on most of the locations you’d want it to index, but what if your app has a particular scenario in which it uses Windows Search but wants to use it to search non-indexed locations?

The ability to change the indexed locations might come in handy in these scenarios, and fortunately there’s a relatively simple set of COM interfaces that can be used to interact with Windows Search.

The following code can determine whether a location is indexed:

/// <summary>

/// Determines whether the specified location is currently indexed.

/// </summary>

/// <param name="location">The location.</param>

/// <returns>Whether the location is indexed.</returns>

public static bool IsLocationIndexed(string location)

{

    CSearchManager searchManager = new CSearchManager();

    CSearchCatalogManager catalogManager = searchManager.GetCatalog("SystemIndex");

    CSearchCrawlScopeManager scopeManager = catalogManager.GetCrawlScopeManager();

 

    int result = scopeManager.IncludedInCrawlScope(location);

 

    Marshal.ReleaseComObject(scopeManager);

    Marshal.ReleaseComObject(catalogManager);

    Marshal.ReleaseComObject(searchManager);

 

    return result != 0;

}

The following simple code can be used to enable indexing (of course, after asking for the user’s consent!) on a specified folder:

/// <summary>

/// Adds the specified locations to the system index.

/// </summary>

/// <param name="locations">The locations to add.</param>

public static void IndexLocations(params string[] locations)

{

    CSearchManager searchManager = new CSearchManager();

    CSearchCatalogManager catalogManager = searchManager.GetCatalog("SystemIndex");

    CSearchCrawlScopeManager scopeManager = catalogManager.GetCrawlScopeManager();

 

    foreach (string location in locations)

    {

        string url = location;

        if (url[url.Length - 1] != '\\')

            url += '\\';

        if (!url.StartsWith("file:///"))

            url = "file:///" + url;

 

        scopeManager.AddUserScopeRule(url, 1, 0, 0);

    }

    scopeManager.SaveAll();

 

    Marshal.ReleaseComObject(scopeManager);

    Marshal.ReleaseComObject(catalogManager);

    Marshal.ReleaseComObject(searchManager);

}