Google Desktop
 Google Desktop Helper Framework Plug-In Tutorial

For Users
  Download Plug-ins
  Desktop Search Forum

For Developers
  Plug-in Development
  Download SDK
  Developer Guide
    Index API
    Query API
    Display API
      Script API
      Communication API
      Plug-in Design Guidelines
      Plug-in Tutorials
        Using Wizard
        Using Helper Framework
        Using ActiveX
    Action API
    Event API
    Plug-in Installer
  Submit Software
  Developer Forum
  Desktop Blog

Contents

GoogleDesktopDisplayAPI.idl


Introduction

This is a tutorial on how to create a DLL Google Desktop Sidebar plug-in using the helper framework. This framework simplifies plug-in development by providing functions for doing common Sidebar plug-in tasks. It also ensures a consistent user interface across Sidebar plug-ins. For simple tasks, such as displaying text, developing with the helper framework is much easier than creating an ActiveX control. Note that this is not an API reference, but rather step-by-step instructions for creating an helper library based plug-in. Before reading this, you should be familiar with the Google Desktop SDK/Display API concepts and interfaces described here.

In particular, this tutorial shows how to write a basic plug-in that creates a Sidebar tile that notifies the user when removable media is inserted or removed from their computer. The overall name of our example is MyDiskMonitor, and we indicate example code that you will need to change for your own plug-ins by coloring it red. A Sidebar including the MyDiskMonitor tile is shown to the right.


Creating the Project in Visual Studio

To start with, you need to create your project in Visual Studio, name it, and set a couple of its properties. This results in a basic project template that you'll register with Google Desktop and fill in with code in the following tutorial sections.

  1. Start Visual Studio .NET 2003.
  2. Go to the File menu and select New, Project...
  3. Find ATL Project on the project list (in Visual C++ Projects and then ATL). Now, enter a name for your plug-in in the textbox. For our example, we use the name MyDiskMonitor. Click OK.
  4. The ATL Project Wizard will ask you for settings. On the dialog's left side, click on the Application Settings tab.
  5. Uncheck the Attributed checkbox. Then click the Finish button.
  6. Visual Studio now creates your project. The Solution Explorer will display all of the new project files. The wizard will have created an unneeded second project, called <YourProjectName>PS, in the case of our example project MyDiskMonitorPS. You can delete the second project by clicking on it in the Solution Explorer and hitting the DEL key.
  7. Right click on the main project (MyDiskMonitor) in the Solution Explorer and select Properties.
  8. At the top, beside Configuration, select All Configurations.
  9. On the dialog's left side, select General. Beside Character Set, select Unicode. You should use Unicode in your applications in order to be compatible with non-English languages.
  10. Also in the Properties dialog, go in the Build Events folder and select Post-Build Event.
  11. Clear the value specified for the Command Line parameter. This text automatically registers your plug-in when a successful build is done. Unfortunately, when updating your plug-in you also need to unregister it and this value does not handle unregistration. Later on, we'll see how to set up your plug-in to do both registration and unregistration.
  12. Click the OK button to close this dialog.


Importing Google Desktop Libraries

You must import Google Desktop's constants and helper functions into your application for it to interact with GD. When you unzipped the SDK, its GD_SDK\api\samples\common directory included GoogleDesktopComponentCommon.vcproj, which contains the constants and helper functions.

  1. Import GoogleDesktopComponentCommon into your application by right clicking on your solution (for our example, MyDiskMonitor) and then selecting Add, Existing Project....
  2. Find the GoogleDesktopComponentCommon.vcproj file in the SDK's /api/samples/common area and import it by clicking Open.
  3. Since your project depends on the GoogleDesktopComponentCommon project, you need to specify the dependency. To do so, right click on your project (MyDiskMonitor in our example) and select Project Dependencies.
  4. Put a checkmark in the box that says GoogleDesktopComponentCommon and then click OK.


Creating the Sidebar Display Object

Now that we've got the project set up and the necessary Desktop libraries imported, it's time to create the object that will be displayed in the Sidebar.

  1. In the Solution Explorer, click on the Class View tab.
  2. Right click on your project (MyDiskMonitor for the example) and select Add, Add Class....
  3. Select ATL Simple Object and click Open.
  4. Under Short name, give the control a name. Our example control could be called MyDiskMonitorObj; note that since MyDiskMonitor is already taken by the project namespace, you can't give your control the exact same name as your project.
  5. From the Options tab select IObjectWithSite. Click the Finish button. This creates your display object definition and inserts two new files into your application for the new object (for our example, the files are MyDiskMonitorObj.h and MyDiskMonitorObj.cpp).
  6. Open the plug-in object's Header file (For our example, MyDiskMonitorObj.h).
    1. To use the Google Desktop helper functions, you must add an include at the top of the file. Make sure this points to the correct location of the .h file:
      
      #include "../common/GoogleDesktopDisplayAPI.h"
            
    2. Next, we have to define the Helper variable (which allows us to access the helper functions) in this file before closing the class' scope.
      
      public: 
      
        CComPtr<IUnknown> helper_;  
      
      }; 
      
    3. In order to simplify plug-in development by giving the helper_ object access to helper functions, add the following code in the file's COM Map:
      
      BEGIN_COM_MAP(CMyDiskMonitorObj)
        COM_INTERFACE_ENTRY(IMyDiskMonitorObj)
        COM_INTERFACE_ENTRY(IDispatch)
        COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND(helper_.p,
          CLSID_GoogleDesktopDisplayPluginHelper)
      END_COM_MAP() 
      
        DECLARE_GET_CONTROLLING_UNKNOWN()
        DECLARE_PROTECT_FINAL_CONSTRUCT() 
      


Registering the Plug-In with Google Desktop

In order for Google Desktop to know about and be able to interact with a plug-in, the plug-in must register itself with GD. The best time to do this is to have it register with GD when Windows registers this DLL and unregister with GD when Windows unregisters this DLL.

  1. Open the main CPP file, <ProjectName>.cpp (for our example, MyDiskMonitor.cpp. Note the difference between MyDiskMonitor.cpp and .h and MyDiskMonitorObj.cpp and .h. MyDiskMonitor.* is for the library itself and takes care of registering the components with Google Desktop. MyDiskMonitorObj.* is the object which actually displays data and interacts with the user).).
  2. Put an include command for the Google Desktop helper functions at the top of this file. Make sure it points to the .h file's correct location, which is based on where you previously imported the GoogleDesktopComponentCommon.vcproj. The command will look like the following, changed to be the actual installed location of the file:
    #include "../common/GoogleDesktopComponentRegistration.h"
  3. Modify DllRegisterServer(void) and DllUnregisterServer(void) to notify Google Desktop when Windows registers or unregisters this DLL. The following code shows the modifications to our MyDiskMonitor sample code. Red text indicates code that should be changed to values correct for your project instead of for MyDiskMonitor.

    RegisterDisplayComponentHelper() registers the plug-in with Google Desktop. Its first parameter is your object's GUID, which identifies your plug-in to Google Desktop. Remember to replace MyDiskMonitorObj with the your project's object name.

    The second and third parameters are the title and description the Google Desktop UI displays for your plug-in.

    The fourth parameter is a path to the plug-in's icon. This isn't actually used for a Sidebar plug-in, so we pass an empty string. The final parameter should be set to true if your plug-in sends notifications to be displayed in the GD notifier area. In the sample code, we use the variable plugin_shows_notifications as the value of this final parameter. Set its value accordingly in its definition. For MyDiskMonitor, plugin_shows_notifications is set to false.


// DllRegisterServer - Adds entries to the system registry
STDAPI DllRegisterServer(void) {
  // Register object, typelib and all interfaces in typelib
  HRESULT hr = _AtlModule.DllRegisterServer();

  if (SUCCEEDED(hr)) {

    // Set the plug-in's default settings
    bool plugin_shows_notifications = false;
    CComBSTR plugin_title(_T("My Disk Monitor"));
    CComBSTR plugin_description(_T("Monitors removable drives and alerts you when a storage device is
	inserted or removed"));

    // Register the plug-in with Google desktop
    hr = RegisterDisplayComponentHelper(__uuidof(MyDiskMonitorObj), plugin_title,
      plugin_description, _T(""), plugin_shows_notifications);
  }

  return hr;
}


// DllUnregisterServer - Removes entries from the system registry
STDAPI DllUnregisterServer(void) {
  // Unregister plug-in from Google Desktop
  UnregisterComponentHelper(__uuidof(MyDiskMonitorObj));

  // Unregister plug-in from Windows

  HRESULT hr = _AtlModule.DllUnregisterServer();
  return hr;
}

Building and Testing the Plug-In

Now that the plug-in is registered and has access to the Google Desktop libraries, it's time to build it and do basic tests before customizing it.

  1. Create the DLL by going to the Build menu and selecting Build Solution.
  2. Open the Windows command prompt and run the following command:
    regsvr32 "<PathToBuiltDLL>"
    PathToBuiltDLL is the path to the DLL created in the previous step. It should look something like:
    regsvr32 "C:\Documents and Settings\Larry\Desktop\MyDiskMonitor\Debug\MyDiskMonitor.dll"
    This registers the plug-in with Windows COM, which immediately calls Google Desktop to register the plug-in there as well.
  3. Google Desktop will prompt you to register the plug-in. Click OK to do so. You now have an active plug-in.
  4. You should test your plug-in to be sure that it is runable and appearing in the Sidebar.
  5. When you're done testing the plug-in, rerun the regsvr32 command, but with a /u flag to unregister the plug-in instead of registering it. For example:
    regsvr32 /u "C:\Documents and Settings\Larry\Desktop\MyDiskMonitor\Debug\MyDiskMonitor.dll"


Setting the Sidebar Tile Title and Icon

Now that we have defined and built the plug-in object, we can start customizing it. First, we'll add code to let it display a tile-specific title and icon when it appears in the Sidebar.

  1. Open your plug-in object's Header file (MyDiskMonitorObj.h in our example).
    1. Define the SetClientSite method by inserting the following code at the end of the class, but before ending scope. SetSite is a method from the implemented ObjectWithSite. It is commonly used by ActiveX objects to set their title and icon.
      
      public:
        STDMETHOD(SetSite) (IUnknown* site);
      
  2. Open the plug-in object's CPP file (MyDiskMonitorObj.cpp in our example).
    1. Insert a BITMAP resource into the project's resource file containing the 16x16 icon you'd like to use for the tile's titlebar. You can either import and existing one or create a new one with the tools provided in the Visual Studio interface. For our example, we'll name the BITMAP resource IDB_MYDISKMONITOROBJ.
    2. Insert the following code at the end of the file (This code is written to work with our MyDiskMonitor sample code. Red text indicates code that should be changed to values correct for your project instead of for MyDiskMonitor).

      We are using IObjectWithSite method SetSite() to set the tile's title and icon (OLE Control) when displayed in the Sidebar. Make sure to change the tile_title variable value to what you want as the tile's title. Also change the IDB_MYDISKMONITOROBJ argument in the LoadBitmap call to an existing Bitmap resource you want to be the title bar displayed icon. The code following the LoadBitmap call converts the returned Bitmap handle (HBITMAP) to an OLE Picture Object (IPicture).
      
      HRESULT CMyDiskMonitorObj::SetSite(IUnknown* site) {
        CComQIPtr<IGoogleDesktopDisplaySite> sidebar_site(site);
      
        if (sidebar_site) {
          // Set the tile's title on the GD interface
          CComBSTR tile_title(_T("My Disk Monitor"));
          sidebar_site->put_title(tile_title);
          
          // Load the icon bitmap resource
      
          CComPtr<IPicture> tile_icon;
          HBITMAP bitmap = LoadBitmap(_AtlBaseModule.GetResourceInstance(),
            MAKEINTRESOURCE(IDB_MYDISKMONITOROBJ));
          if (bitmap == NULL)
            return E_FAIL;
      
          // Do the conversion from a HBITMAP to IPicture
          PICTDESC picture_desc;
          picture_desc.cbSizeofstruct = sizeof(picture_desc);
          picture_desc.picType = PICTYPE_BITMAP;
          picture_desc.bmp.hbitmap = bitmap;
          picture_desc.bmp.hpal = NULL;
          HRESULT hr = OleCreatePictureIndirect(&picture_desc, IID_IPicture, TRUE, 
            (void**)&tile_icon);
          if (FAILED(hr))
            return E_FAIL;
      
          // Set the tile's icon on the GD interface
      
          sidebar_site->put_icon(tile_icon);
      
        }
      
        return S_OK;
      }
      


Setting the Sidebar Tile Appearance and Adding an About Box

We continue customizing our plug-in by setting its display font and colors. Esthetically, these properties should use the same values as the other tiles in the Sidebar. So the code below first gets those values and then sets the relevant properties of our plug-in to them.

We also add an About box to the tile. Similar to setting the title and icon values, we need to edit both your project's CPP and Header files.

  1. Open the plug-in object's Header file (For our example, MyDiskMonitorObj.h).
    1. It's a good idea to move the constructor and destructor methods, FinalConstruct() and FinalRelease(), to the main CPP file from the the Header file. Remove the lines in the header file relating to these methods and replace them with the following code, which can be placed anywhere within the class' scope (replace the text in red with names appropriate to your project's name):
      
      public:
      
        CMyDiskMonitorObj();
        ~CMyDiskMonitorObj();
        HRESULT FinalConstruct();
        void FinalRelease();
      
  2. Open the plug-in object's CPP file (for our example, MyDiskMonitorObj.cpp).
    1. We need to implement the FinalConstruct() and FinalRelease() constructor and destructor methods. Add the following code at the end of the CPP file. Notice that the helper_ object is manually released in FinalRelease(). Therefore, to keep code out of the header file, we move these methods to the CPP file.
      
      HRESULT CMyDiskMonitorObj::FinalConstruct() {
        return S_OK;
      }
      
      void CMyDiskMonitorObj::FinalRelease() {
        helper_.Release();
      }
      
      
      CMyDiskMonitorObj::CMyDiskMonitorObj() {
      }
      
      CMyDiskMonitorObj::~CMyDiskMonitorObj() {
      }
      
    2. Now, we'll set your tile's display options and implement its About dialog. The Helper is useful for this since it assists you in being consistent with other plug-ins' appearance. Insert the following code at the end of the SetSite() method, just before the return S_OK line. Inserting it there causes the code to run during your plug-in's creation.

      In this code, we first enable access to the helper, then set the tile's display options. In our example, we do not use any modifers, which can be used to do things like add back and forward arrows to the titlebar. See the API documentation or IDL file for the available options for these two parameters.

      Next, we set the About dialog's text, with the \n causing a new line in the dialog. Per the convention for all plug-ins, the first two dialog lines are displayed at the top beside the icon, with the remaining lines displayed below.

      Finally, we call SetIcons() to set the icons for both the About dialog and alerts window. We use the same icon as the one beside the tile title, but you can use any icon you wish. You might want to use a 32x32 icon for tile_icon and let the libraries rescale it to the needed size.
      
      
          // Set the tile's icon on the GD interface
          sidebar_site->put_icon(tile_icon);
      
          // Create the helper object
          CComQIPtr<IGoogleDesktopDisplayPluginHelper> helper(helper_);
          if (helper_ == NULL)
            return E_FAIL;
      
      
          // Set the display options for the tile
          UINT content_flags = GDD_CONTENT_FLAG_HAVE_DETAILS;
          UINT plugin_flags = GDD_PLUGIN_FLAG_NONE;
      
          helper->SetFlags(static_cast<GoogleDesktopDisplayPluginFlags>(plugin_flags),
            static_cast<GoogleDesktopDisplayContentFlags>(content_flags));
      
          // Set the maximum amount of items possible to display to be 10. Anything 
          // more will be automatically removed
          helper->put_max_content_items(10);
      
      
          // Set the about dialog text
          helper->put_about_text(_T("My Disk Monitor\nCopyright (C) 2005\nMonitors ")
            _T("disk changes on your computer\n\nAs removable media is inserted or removed in ")
            _T("computer, a notification will be displayed as an item in the plug-in's tile."));
      
          // Set the plug-in's icons (also used in About dialog)
          helper->SetIcons(tile_icon, tile_icon);
      
        }
      
        return S_OK;
      


Using the Helper to Insert Items

In this section, we show how to add items to our Sidebar tile via the Helper object.

  1. Open the plug-in object's Header file (for our example, MyDiskMonitorObj.h).
    1. Insert the following code at the end of the file. Red text indicates code that should be changed to apply to your project rather than our example.

      The code is simpler than it might at first appear to be. It creates a class to provide callbacks that you can use to further customize the items that are added to the tile's contents. For example, when the user clicks on a tile item, you could have it throw one of the listed events (DrawItem, OpenItem, etc.). While you could create new tile items without having to manage a new class, you wouldn't be able to easily handle their callback messages.
      
      class CMyDiskMonitorContentItem :
        public CComObjectRootEx<CComSingleThreadModel>,
        public IDispatchImpl<IGoogleDesktopDisplayContentItemHandler,
        &IID_IGoogleDesktopDisplayContentItemHandler, &LIBID_MyDiskMonitorLib,
        /*wMajor =*/ 1, /*wMinor =*/ 0> {
      
      public:
        BEGIN_COM_MAP(CMyDiskMonitorContentItem)
          COM_INTERFACE_ENTRY(IGoogleDesktopDisplayContentItemHandler)
          // the next statement causes the content item to be aggregated from the 
          // ContentItemHelper object that is exposed by the API. The variable 
          // 'm_contentItemHelper' will hold the helper object.
          COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND(helperitem_.p,
          CLSID_GoogleDesktopDisplayContentItemHelper)
        END_COM_MAP()
      
      public:
        DECLARE_GET_CONTROLLING_UNKNOWN()
      
        CMyDiskMonitorContentItem() {
        }
        void FinalRelease() {
        }
      
        // IGoogleDesktopDisplayContentItemHandler
        STDMETHOD(DrawItem)(GoogleDesktopDisplayTarget target, HDC dc,  const RECT *bounds);
        STDMETHOD(GetHeight)(GoogleDesktopDisplayTarget target, HDC dc, long width, long *height);
        STDMETHOD(OpenItem)();
        STDMETHOD(ToggleItemPinnedState)();
        STDMETHOD(GetIsTooltipRequired)(GoogleDesktopDisplayTarget target, 
          HDC dc, const RECT *bounds, VARIANT_BOOL *is_required);
        STDMETHOD(OnDetailsView)(BSTR* title,
          GoogleDesktopDisplayDetailsViewFlags* flags,
          IUnknown** details_control,
          VARIANT_BOOL* cancel);
        STDMETHOD(ProcessDetailsViewFeedback)(GoogleDesktopDisplayDetailsViewFlags flags);
        STDMETHOD(OnRemoveItem)(VARIANT_BOOL* cancel);
      
      
      public:
        CComPtr<IUnknown> helperitem_; // aggregated helper object exposed by the API
      };
      typedef CComObject<CMyDiskMonitorContentItem> CMyDiskMonitorContentItemImpl;
      
  2. Open the plug-in object's CPP file (for our example, MyDiskMonitorObj.cpp)
    1. Insert the following code at the bottom of the file. It is a placeholder to provide for when you want to add actions that occur when a user clicks on an item or want to do more advanced item editing. As always, red text indicates wording that should be changed to match your project rather than our example.
      
      STDMETHODIMP CMyDiskMonitorContentItem::OpenItem() {
        return E_NOTIMPL;
      }
      
      STDMETHODIMP CMyDiskMonitorContentItem::GetIsTooltipRequired(
        GoogleDesktopDisplayTarget target, HDC dc, const RECT *bounds,
          VARIANT_BOOL *is_required) {
        return E_NOTIMPL;
      }
      
      STDMETHODIMP CMyDiskMonitorContentItem::DrawItem(
          GoogleDesktopDisplayTarget target, HDC dc, const RECT *bounds) {
        return E_NOTIMPL;
      }
      
      STDMETHODIMP CMyDiskMonitorContentItem::GetHeight(
          GoogleDesktopDisplayTarget target, HDC dc, long width, long *height) {
        return E_NOTIMPL;
      }
      
      STDMETHODIMP CMyDiskMonitorContentItem::ToggleItemPinnedState() {
        return E_NOTIMPL;
      }
      
      STDMETHODIMP CMyDiskMonitorContentItem::OnDetailsView(BSTR* title,
          GoogleDesktopDisplayDetailsViewFlags* flags, IUnknown** details_control,
          VARIANT_BOOL* cancel) {
        return E_NOTIMPL;
      }
      
      STDMETHODIMP CMyDiskMonitorContentItem::ProcessDetailsViewFeedback(
          GoogleDesktopDisplayDetailsViewFlags dv_flags) {
        return E_NOTIMPL;
      }
      
      STDMETHODIMP CMyDiskMonitorContentItem::OnRemoveItem(VARIANT_BOOL* cancel) {
        return E_NOTIMPL;
      }
      
      
    2. Insert the following code at the end of the SetSite() method and before the success checking scope. Red text should be changed to match your project, rather than our example.

      This is where we actually insert an item. Creating the item requires many parameters, but once it's created and added to the tile Google Desktop will take care of properly displaying it.

      Specifically:
      1. The current system time is used as the entry time value. This can be modified to be any value you might prefer.
      2. The item header will display the item's number to show that items are inserted into the list such that the newest item is at the top.
      3. All of the put_ calls set different properties of the item. Note that we do not set all the possible properties; see the IDL file and Display API documentation for the complete set of properties that you can manipulate.
      4. Finally, the item is added to the tile and displayed by Google Desktop.
      
          // Set the plug-in's icons (also used in About dialog)
          helper->SetIcons(tile_icon, tile_icon);
      
          // Insert 10 items. Notice that they will get inserted with #10 being shown at the top
          for (int cur_item_num = 1; cur_item_num <= 10; ++cur_item_num) {
            // Create an object for the new item
      
            CMyDiskMonitorContentItemImpl *item_object = NULL;
            CMyDiskMonitorContentItemImpl::CreateInstance(&item_object);
            CComPtr<IGoogleDesktopDisplayContentItemHelper> item_helper;
            item_object->GetUnknown()->QueryInterface(IID_IGoogleDesktopDisplayContentItemHelper,
              reinterpret_cast<void**>(&item_helper));
      
            // Get the system time for a time value
            SYSTEMTIME system_time;
      
            GetSystemTime(&system_time);
            DATE time;
            SystemTimeToVariantTime(&system_time, &time);
      
            // Format the string to display for the heading
      
            CString heading_str;
            heading_str.Format(_T("Heading #%d"), cur_item_num);
            CComBSTR heading(heading_str);
      
            // Set the properties for the new item
            item_helper->put_heading(heading);
            item_helper->put_tooltip(_T("Tooltip"));
            item_helper->put_source(_T("Source"));
            item_helper->put_time_created(time);
            item_helper->put_flags(static_cast<GoogleDesktopDisplayContentItemFlags>(
              GDD_CONTENT_ITEM_FLAG_TIME_ABSOLUTE));
            item_helper->put_image(NULL);
            item_helper->put_layout(static_cast<GoogleDesktopDisplayContentItemLayout>(
              GDD_CONTENT_ITEM_LAYOUT_NEWS));
      
            // Create the item that will be added
      
            CComQIPtr<IGoogleDesktopDisplayContentItem> item(item_helper);
            GoogleDesktopContentItemDisplayOptions item_flags =
              static_cast<GoogleDesktopContentItemDisplayOptions>(GDD_ITEM_DISPLAY_IN_SIDEBAR);
           
            // Add the item with the helper
            helper->AddContentItem(item, item_flags);
          }
        }
      
        return S_OK;
      }
      
      
  3. Open the stdafx.h file, which was automatically generated by the ATL Wizard as part of your project.
    1. Since CString was used in the CPP file, we need to do an #include for the CString methods. Insert the following code below the existing #include statements:
      
      #include "resource.h"
      #include <atlbase.h>
      #include <atlcom.h>
      #include <atlstr.h>
      
      using namespace ATL;
      


Using the Helper to Dynamically Insert Items

  1. Open your project's stdafx.h file.
    1. Since Google Desktop requires Windows 2000 or later, you should set the WINVER compiler variable to be 0x0500. As a first step, remove or comment out the following WINVER and _WIN32_ referencing code:
      
      // Modify the following defines if you have to target a platform prior to the ones specified below.
      
      // Refer to MSDN for the latest info on corresponding values for different platforms.
      #ifndef WINVER    // Allow use of features specific to Windows 95 and Windows NT 4 or later.
      #define WINVER 0x0400  // Change this to the appropriate value to target
      		       //Windows 98 and Windows 2000 or later.
      #endif
      
      #ifndef _WIN32_WINNT  // Allow use of features specific to Windows NT 4 or later.
      
      #define _WIN32_WINNT 0x0400  // Change this to the appropriate value to target Windows 2000 or later.
      #endif
      
      #ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later.
      #define _WIN32_WINDOWS 0x0410 // Change this to the appropriate value to target Windows Me or later.
      
      #endif
      
      #ifndef _WIN32_IE   // Allow use of features specific to IE 4.0 or later.
      #define _WIN32_IE 0x0400  // Change this to the appropriate value to target IE 5.0 or later.
      #endif
                
    2. Insert the following line, which replaces the lines you just removed/commented out:
      #define WINVER 0x0500 // Win2k and above

  2. Open the plug-in object's Header file (for our example, MyDiskMonitorObj.h).
    1. Insert the following code at the top of the file, right below the existing #include statements.

      This code creates a new class for a message-only window. Often, it is very useful for a plug-in to create a new message-only window to receive Windows messages or as a base for timers. In our example, it will receive notifications about removable media being inserted or removed.

      On initialization of the Sidebar tile, this class's StartNotifications() method should be called. When destroying the plug-in, its StopNotifications() method should be called to handle the clean up. In this example code, generally undocumented functions are used to monitor file changes from Explorer, which is why we have to manually define many of the structures and constants.
      
      #include <shlobj.h>
      #include <atlwin.h>
      
      #define WM_SHELLNOTIFY WM_USER+5
      
      typedef struct {
          DWORD dwItem1;
          DWORD dwItem2;
      } SHNOTIFYSTRUCT;
      
      
      class CMyDiskMonitorObj;
      
      class CMyDiskMonitorObjMsgOnlyWindow : 
        public CWindowImpl<CMyDiskMonitorObjMsgOnlyWindow> {
      public:
        bool StartNotifications(CMyDiskMonitorObj *plugin);
        bool StopNotifications();
      
        BEGIN_MSG_MAP(CMyDiskMonitorObjMsgOnlyWindow)
          MESSAGE_HANDLER(WM_SHELLNOTIFY, OnShellNotify);
        END_MSG_MAP()	
      
        LRESULT OnShellNotify(UINT /*uMsg*/, WPARAM
        	/*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
      
      
      private:
        CString GetPathFromPIDL(DWORD pidl);
      
      private:
        CMyDiskMonitorObj *plugin_;
        ULONG notification_;
      };
      
    2. Scroll to the end of the CMyDiskMonitorObj class definition and insert the following code. It creates an instance of the message-only window that receives notifications about the removable media.
      
      public:
        CComPtr<IUnknown> helper_;
      
        CMyDiskMonitorObjMsgOnlyWindow message_window_;
      
      public:
        STDMETHOD(SetSite) (IUnknown* site);
      };
      
  3. Open the plug-in object's CPP file (for our example, MyDiskMonitorObj.cpp)
    1. Go to the SetSite() method. From there, go to where where the dummy items are added as shown previously in this tutorial. This code is located below the SetIcons() call
    2. Remove the loop which was created earlier and replace it with the following code:
    3. 
          // Set the plug-in's icons (also used in About dialog)
          helper->SetIcons(tile_icon, tile_icon);
          
          // Start notifications on for Explorer file creations
          if (message_window_.StartNotifications(this) == false)
            return E_FAIL;
      
        }
      
        return S_OK;
      }
      
    4. Insert the following at the bottom of the file. When the StartNotifications() method is called, the object will create a message-only window. If successfully created, it will then set itself up to receive notifications on all file creations. StopNotifications() also should be called once the plug-in gets unloaded in order to release the resources taken by the notifications.
      
      bool CMyDiskMonitorObjMsgOnlyWindow::StartNotifications(CMyDiskMonitorObj *plugin) {
        plugin_ = plugin;
        
        // create a message-only window
      
        if (Create((HWND)-3) == NULL)
          return false;
      
        // Specify to listen for file change events from all hard drives
        LPITEMIDLIST id_list;
        if (SHGetSpecialFolderLocation(m_hWnd, CSIDL_DESKTOP, &id_list) != NOERROR)
          return false;
      
        SHChangeNotifyEntry notify_settings;
        notify_settings.fRecursive = TRUE;
        notify_settings.pidl = id_list;
      
        // Start listening for file events
      
        notification_ = SHChangeNotifyRegister(m_hWnd, 
          SHCNE_DISKEVENTS, 
          SHCNE_MEDIAINSERTED | SHCNE_MEDIAREMOVED | SHCNE_DRIVEADD | SHCNE_DRIVEREMOVED,
          WM_SHELLNOTIFY, 
          1, ¬ify_settings);
        if (notification_ == 0)
          return false;
        return true;
      }
      
      bool CMyDiskMonitorObjMsgOnlyWindow::StopNotifications() {
        SHChangeNotifyDeregister(notification_);
      
        return true;
      }
      
    5. Insert the following code at the end of the file. This code block inserts an item whenever a notification event happens in the message-only window. Note that a large part of this code is what was in the loop in the previous part of this tutorial where simple dummy items were being inserted. Below, it has been slightly modified both to change the text output and to use the plugin_ object to add the item. GetPathFromPIDL is required in order to do a conversion of the parameter data to a displayable form.
      
      LRESULT CMyDiskMonitorObjMsgOnlyWindow::OnShellNotify(UINT msg, 
          WPARAM wp, LPARAM lp, BOOL& handled) {
      
        // Get the before and after filenames
        SHNOTIFYSTRUCT *notification = (SHNOTIFYSTRUCT *)wp;
        CString fullpath = GetPathFromPIDL(notification->dwItem1);
        
        // Verify that the path is not empty
      
        if (fullpath.IsEmpty())
          return S_OK;
      
        // Create an object for the new item
        CMyDiskMonitorContentItemImpl *item_object = NULL;
        CMyDiskMonitorContentItemImpl::CreateInstance(&item_object);
        CComPtr<IGoogleDesktopDisplayContentItemHelper> item_helper;
        item_object->GetUnknown()->QueryInterface(IID_IGoogleDesktopDisplayContentItemHelper,
          reinterpret_cast<void**>(&item_helper));
      
        // Get the system time for a time value
      
        SYSTEMTIME system_time;
      
        GetSystemTime(&system_time);
        DATE time;
        SystemTimeToVariantTime(&system_time, &time);
      
        // Display the correct information depending on the event data
        if ((lp == SHCNE_MEDIAINSERTED) || (lp == SHCNE_DRIVEADD)) {
          item_helper->put_heading(_T("Media inserted"));
        } else if ((lp == SHCNE_MEDIAREMOVED) || (lp == SHCNE_DRIVEREMOVED)) {
          item_helper->put_heading(_T("Media removed"));
        }
        CComBSTR source(fullpath);
        item_helper->put_source(source);
      
        // Set the properties for the new item
        item_helper->put_tooltip(_T("Tooltip")); 
        item_helper->put_time_created(time);
        item_helper->put_flags(static_cast<GoogleDesktopDisplayContentItemFlags>(
          GDD_CONTENT_ITEM_FLAG_TIME_ABSOLUTE));
        item_helper->put_image(NULL);
        item_helper->put_layout(static_cast<GoogleDesktopDisplayContentItemLayout>(
          GDD_CONTENT_ITEM_LAYOUT_NEWS));
      
        // Create the item that will be added
      
        CComQIPtr<IGoogleDesktopDisplayContentItem> item(item_helper);
        GoogleDesktopContentItemDisplayOptions item_flags =
          static_cast<GoogleDesktopContentItemDisplayOptions>(GDD_ITEM_DISPLAY_IN_SIDEBAR);
      
        // Add the item with the helper
        CComQIPtr<IGoogleDesktopDisplayPluginHelper> helper(plugin_->helper_);
        helper->AddContentItem(item, item_flags);
        
        return S_OK;
      }
      
      CString CMyDiskMonitorObjMsgOnlyWindow::GetPathFromPIDL(DWORD pidl) {
        CString out(_T(""));
        wchar_t path[MAX_PATH];
        
        if (SHGetPathFromIDList((struct _ITEMIDLIST *)pidl, path))
          out = path;
        
        return out;
      }
      
      
    6. Finally, so that resources are properly freed when the plug-in closes, insert the following code in the FinalRelease() method:
      
      void CMyDiskMonitorObj::FinalRelease() {
        if (message_window_.IsWindow()) {
          message_window_.StopNotifications();
          message_window_.DestroyWindow();
        }
      
        helper_.Release();
      }
      


Responding to Double Clicks on Tile Items

We need to handle how the items in our tile respond when users double-click on them. Per the UI guidelines, double-clicking opens the item in a new application window and also selects it in the Sidebar.

  1. Open the plug-in object's CPP file (MyDiskMonitorObj.cpp).
  2. The item class' OpenItem() handles opening the item, so we need to customize it to do the right thing when opening our items. For our example, replace the generated OpenItem() method with the following version:
    
    STDMETHODIMP CMyDiskMonitorContentItem::OpenItem() {
      CComQIPtr<IGoogleDesktopDisplayContentItemHelper> helper(helperitem_);
    
      CComBSTR drive;
      ATLVERIFY(SUCCEEDED(helper->get_source(&drive)));
    
      ShellExecute(NULL, _T("open"), drive, NULL, drive, SW_SHOWNORMAL);
    
      return S_OK;
    }
    


Responding to Single Clicks on Tile Items with Details View

Per the Sidebar UI guidelines, single-clicking a tile's content item opens that item's details view. The following code implements that behavior for our content items.

  1. Open the plug-in object's CPP file (MyDiskMonitorObj.cpp).
  2. OnDetailsView() in the item class handles opening the details view. Customize it to handle your particular items. For our example, the details view is put together depending on the volume information (if it was inserted). The SetContent() method adds text to the details view. When completed, the Detach() method makes the data displayable on the details view window.

    Replace the generated
    OnDetailsView() method with the following version:
    
    STDMETHODIMP CMyDiskMonitorContentItem::OnDetailsView(BSTR* title,
        GoogleDesktopDisplayDetailsViewFlags* flags, IUnknown** details_control,
        VARIANT_BOOL* cancel) {
      // Get the item to be displayed
    
      CComQIPtr<IGoogleDesktopDisplayContentItemHelper> helper(helperitem_);
      ATLASSERT(helper != NULL);
      if (!helper)
        return E_INVALIDARG;
    
      // Here we can create any ActiveX control for displaying the details, and 
      // return it via the details_control parameter. We use our 
      // CMyDiskMonitorDetailsView control to draw the details view content.
      CComPtr<IGoogleDesktopDisplayDetailsViewHelper> details;
      HRESULT hr = details.CoCreateInstance(CLSID_GoogleDesktopDisplayDetailsViewHelper);
      if (SUCCEEDED(hr)) {
    
        // Get source drive that was inserted/removed
        CComBSTR drivePathBSTR(""); 
        helper->get_source(&drivePathBSTR);
        CString drivePath(drivePathBSTR); 
    
        // Get the drive label
        CString driveLabel;
        GetVolumeInformation(drivePath, driveLabel.GetBuffer(255), 255, 
           NULL, NULL, NULL, NULL, NULL);
        driveLabel.ReleaseBuffer();
    
        // Get the drive size total and free space
        ULARGE_INTEGER totalSpace, freeSpace;
        CString totalSpaceOUT, freeSpaceOUT;
        GetDiskFreeSpaceEx(drivePath, NULL, &totalSpace, &freeSpace);
        totalSpaceOUT.Format(_T("%llu"), totalSpace);
        freeSpaceOUT.Format(_T("%llu"), freeSpace);
    
        // Set the text in the details view
        CString output;
        output += _T("Drive: ") + drivePath + _T("\n");
        output += _T("Label: ") + driveLabel + _T("\n");
        output += _T("Total size (Bytes): ") + totalSpaceOUT + _T("\n");
        output += _T("Free space (Bytes): ") + freeSpaceOUT + _T("\n");
    
        // Display the output in the details window
        CComBSTR outputBSTR(output);
        CComBSTR statusBSTR("");
        helper->get_heading(&statusBSTR);
        details->SetContent(statusBSTR, NULL, outputBSTR, VARIANT_TRUE, GDD_CONTENT_ITEM_LAYOUT_NEWS);
        helper->get_source(title);
        
        // Return the data to display
    
        *details_control = details.Detach();
        *flags = static_cast<GoogleDesktopDisplayDetailsViewFlags>(
          GDD_DETAILS_VIEW_FLAG_TOOLBAR_OPEN);
      }
    
      return hr;
    }