Welcome to shell: revealed Sign in | Join | Help
in Search

Shell Blog

  • Shell Namespace Extension: Enabling Deep Search

    Overview

    From my past posts about implementing your own Shell Namespace, there have been some great questions posted by readers.  Many of these stem from the fact that the Namespace example is fairly simple in that it does not implement all of the behavior that is possible in Explorer.  This was done to focus on the core steps in getting a working Namespace implemented.  Yet, there are a few extra steps you can take that don’t require too much more coding on your part to add more useful features.  One question in particular that comes up quite often is how to enable deep searching in your Namespace.

    You will notice from the existing Namespace example that if you enter a search term in the search box in Explorer, the search only filters items that are currently in the view.  It does not search into the folders.  In the below images, we try to search for “Two” in the search box which only results in 1 item.  Thus, the sub folders were not included.

    Filter Search

    Filter Results - shallow

    What does a Namespace implementer have to do in order to include sub folders in their namespace search results?  This is actually fairly simple.

    Implementing IShellFolderViewCB and IFolderViewSettings

    In our previous code, we did not implement an IShellFolderViewCB for our Namespace.  This allows your Namespace to be notified of events associated with the view.  An implementation of IShellFolderViewCB can be specified in your call to SHCreateShellFolderView.  This is optional and previously we were just passing NULL for this.   We need to create a class that implements IShellFolderViewCB as well as IFolderViewSettings.  For our IFolderViewSettings implementation, we only need to provide a handler for the GetFolderFlags method.  It is through this method that we notify the Shell that we want to perform deep searches within our Namespace.

    IFACEMETHODIMP CFolderViewCB::GetFolderFlags(__out FOLDERFLAGS *pfolderMask, __out FOLDERFLAGS *pfolderFlags)

    {

        if (pfolderMask)

        {

            *pfolderMask = FWF_USESEARCHFOLDER;

        }

        if (pfolderFlags)

        {

            *pfolderFlags = FWF_USESEARCHFOLDER;

        }

       

        return S_OK;

    } 

    As you can see from the above implementation of GetFolderFlags, we only care to notify the Shell of the FWF_USESEARCHFOLDER flag.  This tells the Shell that our Namespace should use the Search Folder for performing stacking and searching.  You could also specify other flags to modify the appearance and behavior of your namespace.

    The modified code for this sample is linked below.  You will notice that the implementation of IShellFolderViewCB and IFolderViewSettings is rather sparse – most methods just return E_NOTIMPL as we are not using them here.   You can implement these yourself if you see the need to extend your code.
    Now that we have notified the Shell to use the Search Folder, we can perform deep searches within our Namespace.  When we perform the same search we did previously, we now get the following results:

    deep search

    This Namespace simply generates 10 virtual items to a default depth of 5.  The Search enumerates the contents of the Namespace to that depth.  It should also be called out that we had to implement our namespace's ParseDisplayName method in order for our namespace to function in the Search folder.

    *Please note that the method described here only works with the default shell view (Defview).  It is not supported for custom IShellView implementations.

    Building the FolderView SDK Sample

    1. To build the FolderViewImpl sample, be sure to download and install the Windows SDK.
    2. Download the modified FolderView SDK sample
    3. Launch FolderViewImpl.sln in Visual Studio (The solution file is for Visual Studio 2008)
    4. Open the properties for the project
    5. Add a path to the SDK includes to the C/C++ - General page
    6. Add a path to the SDK libs to the Linker – General page
    7. Build

    Installing the FolderView SDK Sample

    1. Once you have built the sample, copy the FolderViewImpl.dll and FolderViewImpl.propdesc to the same directory
    2. From an elevated cmd window, regsvr32 FolderViewImpl.dll
    3. Restart explorer
    4. Open explorer to Computer
    5. There should be a list item named “FolderView SDK Sample”

  • Shell Namespace Extension: Adding Custom Command Module Items

    Overview

    In my earlier post, I showed how to create a Shell Namespace Extension using the default IShellView implementation (also known as DefView).  One thing you might have noticed from the sample is that the Commands Module is void of even the default items such as “Organize” and “Views”. 

    Commands Module - FolderViewImpl

    If you are developing your own Namespace and want to include the default buttons and/or add your own elements to the Commands Module as well, how do you accomplish this?  The answer:   you must implement the IExplorerCommand, IExplorerCommandProvider and IEnumExplorerCommand interfaces.

    Below I describe what each of the interfaces is used for and what you need to do to properly implement them in your code.

    IExplorerCommandProvider

    After your Namespace is loaded, the Shell will ask your Namespace for an instance of an IExplorerCommandProvider (via your CreateViewObject implementation).  This interface exposes two methods:  GetCommand and GetCommands.  The Shell calls the GetCommand method to retrieve a specific IExplorerCommand object that matches the supplied GUID (Each command is identified by a GUID).  The GetCommands method is called to retrieve an IEnumExplorerCommand instance.  This object enumerates all the IExplorerCommand instances for the namespace.

    You Namespace is queried for its implementation of IExplorerCommandProvider via its IShellFolder's implementation of CreateViewObject.  The REFIID passed to CreateViewObject will be IID_IExplorerCommandProvider.  The Shell will then use this interface to retrieve your Commands Module items – which are implementations of the IExplorerCommand interface.

    IExplorerCommand

    The IExplorerCommand interface is used to provide the appearance and behavior of the item to Explorer’s Commands Module.  Most of the methods pass an IShellItemArray which identifies what items are currently selected in the view.  This information can be used by the implementation to customize the behavior for different selections or if no items are selected.  For example, if no items are selected, you may wish to set the state to ECS_DISABLED or ECS_HIDDEN to grey-out or remove the item completely from the Commands Module, respectively.

    The methods exposed by this interface are described below.

    Method

    Description

    EnumSubCommands

    Returns an IEnumExplorerCommand instance used to enumerate sub commands of the current command.  The Shell will only call this method if the GetFlags method returns ECF_HASSUBCOMMANDS .  It is important to note here that while it is possible to do so, sub commands that have sub commands themselves are not encouraged. 

    GetCanonicalName

    Retrieves the globally unique identifier (GUID) associated with this command

    GetFlags

    Retrieves the flags associated with this command.  These flags specify the appearance and behavior of the command on the Command Module.

    GetIcon

    Retrieves an icon resource string.  This string is in the form of “myfile.dll,id”.  For example: “shell32.dll,-101”

    GetState

    Retrieves the state of the command item.  This is the first method of the interface that is called.  The state that is returned affects the appearance and behavior of the command item.  This method also passes a BOOL to let the command implementation know if the slow response rule is in effect.

    GetTitle

    Retrieves the string to display for the command item.

    GetToolTip

    Retrieves the string to use in the ToolTip associated with this command item.

    Invoke

    Called by the Shell when the user has activated a command on the Commands Module.

    The above methods GetState and GetFlags return values that determine the appearance and behavior of the item in the Command Module.  In the table below are screenshots of the command resulting from the combinations of the flags/states values.

     

    ECS_ENABLED

    ECS_DISABLED

    ECS_HIDDEN

    NONE

    ECF_HASSUBCOMMANDS

    ECF_HASSPLITBUTTON

    ECF_HIDELABEL

    ECF_ISSEPARATOR

    N/A

    ECF_HASLUASHIELD

     

    ECS_CHECKBOX

    ECS_CHECKBOX|

    ECS_CHECKED

    NONE

    ECF_HASSUBCOMMANDS

    ECF_HASSPLITBUTTON

    ECF_HIDELABEL

    ECF_ISSEPARATOR

    N/A

    N/A

    ECF_HASLUASHIELD

    *ECS_CHECKBOX and ECS_CHECKED only apply to sub items

    ** ECF_ISSEPARATOR can only be added as a sub item

    Slow Response Rule

    The second parameter of the GetState method is a BOOL (fOkToBeSlow) that lets the implementation of IExplorerCommand know if the “slow response rule” is in effect.  If this value is FALSE, and your implementation performs slow operations (such as I/O, network calls or calls to out of thread COM objects) then your command should return E_PENDING.  This will cause the Commands Module to call the command’s GetState (and other methods) on a background thread with fOkToBeSlow set to TRUE.  This prevents slow operations in your implementation from running on the UI thread, thus preventing performance issues.

    Command Item Ordering

    It is important to note here that the developer of the Namespace Extension has no control over the ordering of command items.  For example, you cannot force your command items before the default View and Organize command items or have the View or Organize command items renamed/removed.  Items are simply appended to the end of the Commands Module in the order returned from the IEnumExplorerCommand instance.

    IEnumExplorerCommand

    The IEnumExplorerCommand is returned to the Shell by the GetCommands method of the IExplorerCommandProvider interface.  As the name suggests, it permits enumeration of the IExplorerCommand instances by the Shell.

    FolderViewImpl Sample Code

    The above interfaces have been implemented in the attached Shell Namespace Extension sample code derived from the FolderViewImpl SDK sample.  The structure of the command items (as well as sub items) is data-driven from an array of structures that define the command items (see the array of FVCOMMANDITEMs in fvcommands.cpp).   This approach was used to make it easy to experiment with the hierarchy of command items as well as their behavior/appearance.   The way you implement commands in your Namespace Extension may differ.

    Commands Module - Modified FolderViewImpl

    The above is a screenshot of the modified SDK sample.  Notice we now have the default command items (Organize and Views) as well as our custom items.  The Display command item performs the same function as right-clicking items in the view and selecting “Display” from the context menu.  The Settings button is used to demonstrate a command item with sub items.  All it does is display a message box with the name of the sub item when the user invokes the specified sub item.

    It should also be noted that if you are using a custom view in your Namespace by implementing your own IShellView (instead of using DefView) you will need to include an implementation of IFolderView::GetFolder.  In your GetFolder implementation you will need to include the ability to QueryService for SID_SFolderView.

    Building the FolderView SDK Sample

    1. To build the FolderViewImpl sample, be sure to download and install the Windows SDK
    2. Download the modified FolderView SDK sample
    3. Launch FolderViewImpl.sln in Visual Studio
    4. Open the properties for the project
    5. Add a path to the SDK includes to the C/C++ - General page
    6. Add a path to the SDK libs to the Linker – General page
    7. Build

    Installing the FolderView SDK Sample

    1. Once you have built the sample, copy the FolderViewImpl.dll and FolderViewImpl.propdesc to the same directory
    2. From an elevated cmd window, regsvr32 FolderViewImpl.dll
    3. Restart explorer
    4. Open explorer to Computer
    5. There should be a list item named “FolderView SDK Sample”
  • Vista Style Menus Part 2 – Custom menu drawing

    Part 1 showed how to avoid owner draw menus by converting icons into bitmaps.  In Part 2 we will show how to use the Visual Style APIs in your owner draw code.

    Sample Custom Menu

    The sample app demonstrates a simple custom behavior: A context menu that highlights the previously selected item.  This was inspired by the "recently installed" highlighting that the Start Menu does.  A dropdown menu lets you choose between standard menu rendering, owner draw with standard rendering, and owner draw using Visual Style APIs and the new Vista menu parts.

    Visual Style API Basics

    Before getting into the menu specifics there are a few concepts to touch on.  A more complete discussion of the Visual Style API can be found here.

    1. Theme handles (HTHEME.)  You call OpenThemeData() with your window handle and the visual class you are interested in (VSCLASS_MENU for our purposes.)  You will get a NULL handle back if the current color scheme does not have visual styling enabled.  You also need to watch WM_SETTINGCHANGE and regenerate your HTHEME.  If you do get back a NULL handle you will need to fall back to non-styled APIs to do your rendering.
    2. Theme parts and states.  A visual element is made up of parts and each part may have multiple states.  For example, a button has a background part and different states to differentiate between pressed and non-pressed states.  The sample code will show how to translate owner-draw fState flags into part and state IDs for menus.  Vssym32.h contains the part and state IDs for every visual class.
    3. Measurement.  You inquire about the size or margins for a part / state pair using GetThemePartSize() and GetThemeMargins() respectively.  You use GetThemeTextExtent() to measure text strings.
    4. Drawing.  You can use DrawThemeBackground() and DrawThemeText() to render according to the current color scheme.

    Overall structure of Sample App

    The MENUITEMDATA structure holds our owner draw data and the GetMenuItem() function returns a MENUITEM structure that contains all the info we need:

    struct MENUITEM

    {

        MENUITEMINFO    mii;

        MENUITEMDATA    mid;

        WCHAR           szItemText[256];

    };

    The test app has a dropdown to switch between standard, owner draw, and owner draw using Visual APIs.  To make this easier an interface is defined and two implementors of the interface are provided (CClassicOwnerDrawMenu and CVistaOwnerDrawMenu):

    interface IOwnerDrawMenu

    {

        virtual HRESULT Initialize(HWND hwndParent) = 0;

        virtual void MeasureItem(__in MENUITEM *pmi, __inout MEASUREITEMSTRUCT *pmis) = 0;

        virtual void DrawItem(__in MENUITEM *pmi, __in DRAWITEMSTRUCT *pdis) = 0;

        virtual void SelectedItem(int id) = 0;

        virtual HRESULT SettingChange() = 0;

        virtual void Release() = 0;

    };

    The SelectedItem() method tells the class which item should receive special highlighting (The test app will call this with the previously selected menu item.)

    CShellSimpleApp implements the outer shell of the program and is essentially a dialog box.  The WM_MEASUREITEM, WM_DRAWITEM, and WM_SETTINGCHANGE message handlers are the relevant portion of this class, along with the _SetMenuType() method which switches between menu types.

    The remainder of this article will focus on CVistaOwnerDraw menu and it’s helper class CMenuMetrics.  CClassicOwnerDraw is left as an exercise to the reader.

    Modifying owner-draw code – Initialization

    Before you are able to measure or draw with the Visual APIs you need an HTHEME and, as I said earlier, the user may not be using a color scheme that uses Visual APIs.  The Initialize method will attempt to get an HTHEME based on the parent window and return failure if no HTHEME is available.  The test harness will fall back to one of the other menu types in this case.

     

    The heart of the initialization is done here (this is not the complete function, it’s been pared down for illustration):

    HRESULT CMenuMetrics::Initialize()

    {

        HRESULT hr = E_FAIL;

       

        hTheme = OpenThemeData(hwndTheme, VSCLASS_MENU);

        if (hTheme)

        {

            GetThemePartSize(hTheme, NULL, MENU_POPUPCHECK, 0, NULL, TS_TRUE, &sizePopupCheck);

            GetThemeInt(hTheme, MENU_POPUPITEM, 0, TMT_BORDERSIZE, &iPopupBorderSize);

            GetThemeMargins(hTheme, NULL, MENU_POPUPCHECK, 0, TMT_CONTENTMARGINS, NULL, &marPopupCheck);

               

            hr = S_OK;

        }

        return hr;

    }

    An HTHEME is requested from the parent window which maps to the menu class.  If it succeeds we then ask for the metrics we will need to properly measure and layout our menu items.

    Modifying Owner-Draw code – Measurement

    There are several “Get” functions that return information on a part/state:

    • GetThemePartSize() will give you the dimensions of a part/state pair.
    • GetThemeMargins() will give you the spacing around a part/state pair.
    • GetThemeInt(…, TMT_BORDERSIZE, …) will give you the size of the border around a part/state pair
    • GetThemeTextExtent() will give you the dimensions of the text you specify using the correct font for the part/state pair.  The parameters are similar to the DrawText API, including a parameter that accepts DT_* flags.

    This information will enable you to make the appropriate measurement and layout calculations.  In the test app CMenuMetrics caches these metrics and provides some helper functions, like ToMeasureSize() which applies the specified margins to the tight bounding box you calculated for the menu item.

    Modifying Owner-Draw code – Drawing

    The first thing you need to do is convert the DRAWITEMSTRUCT’s itemState field into the correct state id (POPUPITEMSTATES for popup menus.)  For example, ODS_HOTLIGHT gets translated to MPI_HOT and ODS_INACTIVE can get translated to MPI_DISABLED.  See CMenuMetrics::ToItemStateId() for details.

    The next thing you do is layout the items according to the metrics you calculated during WM_MEASUREITEM and draw the menu in layers using DrawThemeBackground(), starting from the bottom layer:

    • MENU_POPUPBACKGROUND (if the background contains transparency)
    • MENU_POPUPGUTTER (if you want a gutter)
    • MENU_POPUPSEPARATOR (if the item is a separator)
    • MENU_POPUPITEM
    • MENU_POPUPCHECKBACKPGROUND (if you are rendering a checkmark)
    • MENU_POPUPCHECK (if you are rendering a checkmark)
    • DrawThemeText(…, MENU_POPUPITEM, …)

    CVistaOwnerDraw::_DrawMenuItem() demonstrates this process.

    Special considerations

    Allowing the test app to switch between owner-draw and non-owner draw menus presented an interesting issue: USER does not issue new WM_MEASUREITEM messages when the MFT_OWNERDRAW bit is toggled so it continues to use the old metrics.  This may be old news (it appears to have always worked this way) but it was a surprise to me.

    Fortunately there is a simple workaround: make sure fMask has MIIM_BITMAP set when you call SetMenuItemInfo() and this will cause new WM_MEASUREITEM messages to be sent.  The ResetMenuMetrics() helper function will clear out all the menu items of the specified hmenu.  A more efficient method would be to set this bit when flipping the MFT_OWNERDRAW bit but I wanted to call this out clearly in the sample code.  The MakeOwnerDraw() helper function is used by the test app to change between owner-draw and non-owner-draw.

    Important details not covered in article

    The sample code presents a simplified version of menu rendering in order to explain the basic concepts.  It does not use every part and state (no submenu rendering), it does not cover the menu bar, and it will not necessarily line up to the pixel with the system’s menu rendering.  If you are in the business of custom rendered menus then I don’t believe any of these simplifications will be an issue for you. 

    At this point someone may say “Why didn’t you make an API that would render portions of the menu to make all this easier?”  The answer to that is a familiar one: time and resources.  Vista was a big undertaking and we had plenty to do in order to get it wrapped up and out the door. 

    Conclusion

    The Visual Style APIs provide all the mechanisms needed to measure, layout and render many of our visual elements, and Vista added menu visuals with VSCLASS_MENU.  In spite of this owner-draw menus are still a significant amount of work to develop and maintain.  Now that Windows draws nicer looking menus, and there’s a way to get good looking icons with standard menus, I hope that the amount of owner-draw menu code reduces dramatically, leaving all of you with more time for innovation of your core products.

  • IFileOperation – Part 2: Using the IFileOperationProgressSink Interface

    In the first part of my posts for the IFileOperation interface, I wanted to give an overview of what it is used for and leave the real meat for later.  Now it’s time for the fun stuff.  In this post I want to dig into the use of the IFileOperationProgressSink.   In the MSDN docs for IFileOperation, you will notice that some of the methods take an instance of this interface as a parameter.  What is this interface and what is it used for?

    The IFileOperationProgressSink was created to provide a rich notification system for callers who want to know the details of the operation they are performing.  This was a common request of developers who were using the old SHFileOperation API, with which this was simply not possible.  The only feedback mechanism used with SHFileOperation was the rarely used SHNAMEMAPPING structure – a source of many headaches for developers.

    The below table highlights the methods you must implement for IFileOperationProgressSink:

    Method

    Description

    StartOperations

    Called when the operation has begun

    FinishOperations

    Called when the operation has completed

    UpdateProgress

    Called when progress of operation is updated (total items involved in the operation and total items completed so far)

    PauseTimer

    Called when the progress timer is paused (currently not used)

    ResetTimer

    Called when the progress timer is reset (currently not used)

    ResumeTimer

    Called when the progress timer is resumed (currently not used)

    PreCopyItem

    Called before a copy operation begins for an item

    PostCopyItem

    Called after a copy operation completes for an item

    PreDeleteItem

    Called before a delete operation begins for an item

    PostDeleteItem

    Called after a delete operation completes for an item

    PreRenameItem

    Called before a rename operation begins for an item

    PostRenameItem

    Called after a rename operation completes for an item

    PreMoveItem

    Called before a move operation begins for an item

    PostMoveItem

    Called after a move operation completes for an item

    PreNewItem

    Called before a new operation begins for an item

    PostNewItem

    Called after a new operation completes for an item

    Not only can you get notified of the beginning, end, and overall progress of the entire operation, but you can listen for details of each item involved in the operation.  These details include (depending on the operation type): source and destination, new name in the destination, operation flags associated with the operation, as well as the resulting HRESULT of that particular operation.  This allows developers to know the specific point where an operation failed.  

    Using the IFileOperationProgressSink

    The IFileOperation interface provides multiple methods which take an instance of an IFileOperationProgressSink as a parameter.  To be notified of all progress notifications from the copy engine, you should call IFileOperation::Advise which takes an IFileOperationProgressSink instance and produces a cookie that identifies the sink which can be used in a subsequent call to IFileOperation::Unadvise to remove the sink.  If all you are interested in is a particular component of an operation, you can specify the IFileOperationProgressSink instance in call to one of the specific IFileOperation operation methods listed in the table below.

    IFileOperation methods that take an IFileOperationProgressSink:

    Method

    Description

    Advise

    Sets the IFileOperationProgressSink implementation to be called into for all sink notifications.  The caller must call Unadvise to remove the sink.

    CopyItem

    The IFileOperationProgressSink is called into for PreCopyItem and PostCopyItem sink notifications for this operation only.

    MoveItem

    The IFileOperationProgressSink is called into for PreMoveItem and PostMoveItem sink notifications for this operation only.

    RenameItem

    The IFileOperationProgressSink is called into for PreRenameItem and PostRenameItem sink notifications for this operation only.

    DeleteItem

    The IFileOperationProgressSink is called into for PreDeleteItem and PostDeleteItem sink notifications for this operation only.

    NewItem

    The IFileOperationProgressSink is called into for PreNewItem and PostNewItem sink notifications for this operation only.

    * If you add your sink in a call to both Advise and any of the other previously mentioned methods, you will receive duplicate progress sink notifications.

    Documentation Corrections

    At the time of this writing the documentation for this interface on MSDN was not accurate.  The documentation writers have been notified and are making the necessary corrections. 

    The below methods are NOT INCLUDED in the IFileOperationProgressSink interface:

    PreLinkItem

    PostLinkItem

    Building the IFileOperationProgressSink sample

    The included sample code shows how to implement the IFileOperationProgressSink and use it with IFileOperation.  For simplification, the sample only performs a copy operation with the given source and destination paths and responds only to StartOperations, FinishOperations, UpdateProgress, PreCopyItem, and PostCopyItem.  You can extend the code provided in the sample if you wish.

    1. Download and install the Windows SDK
    2. Download the IFileOperationProgressSink_Sample
    3. Launch FileOperationProgressSinkSample.sln in Visual Studio
    4. Open the properties for the project
    5. Add a path to the SDK includes to the C/C++ - General page
    6. Add a path to the SDK libs to the Linker – General page
    7. Build

    IFileOperationProgressSink Sample

    In the above, we specified a copy operation to copy notepad.exe to our test directory.  After clicking Copy, the operation ran (via IFileOperation) and our IFileOperationProgressSink was notified with StartOperations, PreCopyItem, PostCopyItem, UpdateProgress, and FinishOperations.  We also show some of the details of the notification in the description column.  Please see the comments in the code on how to extend the provided implementation to respond to more of the methods of IFileOperationProgressSink.

  • IFileOperation – Part 1: Introduction

    Many developers who have used Shell APIs have had at one time or another used SHFileOperation in their code.  SHFileOperation is used to perform copy, move, rename and delete operations on items in the file system.  It is an entry point into the Shell’s copy engine and is the same API used by Windows Explorer to perform file operations.  Callers also leverage its various UI elements such as confirmation, error and progress dialogs.  This API evolved from code in versions of Windows earlier than Windows 95 and is clearly showing its age.  I won’t go into too much detail about SHFileOperation in this post, but you can read more about common issues with this API in my past posts on this subject:

     

    Common Questions Concerning the SHFileOperation API: Part 1

    Common Questions Concerning the SHFileOperation API: Part 2

     

    In Vista, a new entry point into the copy engine was created to allow callers to perform operations on more than just files and folders – but virtual items such as namespace elements and search results.   This is one of the many purposes for the creation of the IFileOperation interface.   Below is a summary of the new features and benefits of this interface over the old SHFileOperation API:

     

    No More String Paths!

    One of the first things developers will notice about the IFileOperation interface is that they do not specify their source and destination of operations using string-based paths.  Instead it uses IShellItem instances.  Thus, callers are responsible for creating their own IShellItem instances, but at the tradeoff of not having to deal with the ambiguity that the copy engine encounters when passed string buffers (wild