Google Desktop
 Google Desktop Sidebar ActiveX 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

Introduction

This is a tutorial on how to create a DLL Google Desktop Sidebar plug-in as an ActiveX control. This is different from using the helper framework in that you have more control over the tile, but with added complexity in creating the plug-in. Note that is not an API reference, but rather step-by-step instructions for creating an ActiveX based plug-in. Before reading this, you should be familiar with the Google Desktop SDK/API concepts and interfaces described here.

In particular, this tutorial shows how to create a basic plug-in that creates a Sidebar tile and, when a user clicks within the tile, draws a circle centered on the click point. Our example's name is MyCircles, and we indicate example code that you will need to change for your own plug-ins in red. A Sidebar including the MyCircles 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 functionality code in the following sections.

  1. Start Visual Studio .NET 2003.
  2. Go to the File menu and select New, Project...
  3. In Project Types, go to Visual C++ Projects and then ATL
  4. Find ATL Project on the templates list. Now, enter a name for your plug-in in the textbox. For our example, we use the name MyCircles. Click OK.
  5. The ATL Project Wizard will ask you for settings. On the dialog's left side, click on the Application Settings tab.
  6. Uncheck the Attributed checkbox. Then click the Finish button.
  7. Visual Studio now creates your project. The Solution Explorer will display all of the new project files. The wizard creates an unneeded second project, called <YourProjectName>PS, in the case of our example project, MyCirclesPS is created. You can delete the second project by clicking on it in the Solution Explorer and hitting the DEL key.
  8. Right click on the main project (MyCircles) in the Solution Explorer and select Properties.
  9. At the top, beside Configuration, select All Configurations.
  10. 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.
  11. Also in the Properties dialog, go in the Build Events folder and select Post-Build Event.
  12. 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 which this text does not do. Later on, we'll see how to set up your plug-in to do both registration and unregistration.

Back to top

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 contained GoogleDesktopComponentCommon.vcproj which contains the constants and helper functions.

  1. Import GoogleDesktopComponentCommon into your application by right clicking on your solution (for our example, MyCircles) 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 (MyCircles in our example) and select Project Dependencies.
  4. Put a checkmark in the box that says GoogleDesktopComponentCommon and then click OK.

Back to top

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 (MyCircles for the example) and select Add, Add Class....
  3. Select ATL Control and click Open.
  4. Under Short name, give the control a name. Our example control could be called MyCirclesObj; note that since MyCircles 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 Minimal Control. Click the Finish button. This creates your display object definition and inserts two new files into your project for the new object (for our example, the files are MyCirclesObj.h and MyCirclesObj.cpp).

Back to top

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, MyCircles.cpp. Note the difference between MyCircles.cpp and .h and MyCirclesObj.cpp and .h. MyCircles.* is for the library itself and takes care of registering the components with Google Desktop. MyCirclesObj.* 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 MyCircles sample code. Red text indicates code that should be changed to values correct for your project instead of for MyCircles.

    
    // 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 Circles"));
        CComBSTR plugin_description(_T("Draws a circle where the user clicks the mouse"));
    
        // Register the plug-in with Google desktop
        hr = RegisterDisplayComponentHelper(__uuidof(MyCirclesObj), 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(MyCirclesObj));
    
      // Unregister plug-in from Windows
      HRESULT hr = _AtlModule.DllUnregisterServer();
      return hr;
    }
    

    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 MyCirclesObj with the your project's object name.

    The second and third parameters are the title and description that Google Desktop will have for your plug-in when prompting to register and also when listed in the preferences.

    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 MyCircles, plugin_shows_notifications is set to false.


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\MyCircles\Debug\MyCircles.dll"
    This registers the plug-in with Windows and Google Desktop.
  3. Google Desktop will show you a dialog to confirm the registration. 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\MyCircles\Debug\MyCircles.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 the plug-in object's CPP file (MyCirclesObj.cpp in our example).
  2. Once again, at the top of the file, you need to include a Google Desktop header file. Insert the following below the current #include lines and make sure the path is valid:
    #include "../common/GoogleDesktopDisplayAPI.h"
  3. Insert the following code at the end of the file (This code is written to work with our MyCircles sample code. Red text indicates code that should be changed to values correct for your project instead of for MyCircles):
    
    STDMETHODIMP CMyCirclesObj::SetClientSite(IOleClientSite* site) {
      HRESULT hr = IOleObjectImpl<CMyCirclesObj>::SetClientSite(site);
    
      if (site != NULL) {
        CComQIPtr<IGoogleDesktopDisplaySite> sidebar_site(m_spClientSite);
        
        if (sidebar_site) {
          // Set the tile's title on the GD interface
          CComBSTR tile_title(_T("My Circles"));
          sidebar_site->put_title(tile_title);
          
          // Load the icon bitmap resource
          CComPtr<IPicture> tile_icon;
          HBITMAP bitmap = LoadBitmap(_AtlBaseModule.GetResourceInstance(), MAKEINTRESOURCE(IDB_MYCIRCLESOBJ));
          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 hr;
    }
    
    IOleControl's SetClientSite method sets the Sidebar tile's display title and icon. The value of the tile_title variable should be what you want displayed in the tile's title.

    The final argument to LoadBitmap should be to an existing Bitmap resource that will be the icon displayed in the Sidebar tile. As this returns an HBITMAP value, the next code section converts the it to an OLE Picture Object (IPicture).
  4. You must also declare the SetClientSite method in the plug-in object's Header file. Open that file (MyCirclesObj.h in our example) and insert the following code at the end of the class, but before ending scope:
    
    private:
      STDMETHODIMP CMyCirclesObj::SetClientSite(IOleClientSite* site);
    };
    

Setting the Sidebar Tile Font and Colors

We continue customizing our plug-in tile by setting its display font and colors. It's more esthetic to use the same font and colors as the other tiles in the Sidebar, so the code below does that. Also, similar to setting the title and icon values, we need to edit both your object's CPP and Header files.

  1. Open the plug-in object's Header file (For our example, MyCirclesObj.h).

    1. Remove the constructor method and replace it with a definition such as:
      
      public CComControl<CMyCirclesObj>
      {
      public:
      CMyCirclesObj();
      ~CMyCirclesObj();
      
      DECLARE_OLEMISC_STATUS(OLEMISC_RECOMPOSEONRESIZE |
      
    2. Remove the OnDraw method and replace it with the following definition. The Header file incorrectly comes with actual code in it, so you'll delete that code and move the actual method definition to the CPP file.
      
      public:
      	HRESULT OnDraw(ATL_DRAWINFO& di);
      		
      	DECLARE_PROTECT_FINAL_CONSTRUCT()
      
    3. Add the following variables to the end of the class before ending scope:
      
      private:
        HFONT default_font_;
        COLORREF foreground_color_;
        COLORREF background_color_;
      };
      
  2. Open the plug-in object's CPP file (for our example, MyCirclesObj.cpp).

    1. Go to the SetClientSite method and insert the following code after the put_icon line, replacing red code with appropriate values for your project. The code first gets the Sidebar's current font and loads it into an HFONT. It then retrieves the Sidebar's default background and foreground tile colors. The colors and font are stored in globally accessible variables so that your tile can be similar in appearance to the rest of the Sidebar tiles.
      
            // Set the tile's icon on the GD interface
            sidebar_site->put_icon(tile_icon);
      
            // Get the default font
            CComPtr<IFont> sidebar_font;
            CComDispatchDriver sidebar_disp(m_spClientSite);
            CComVariant sidebar_font_var;
            HFONT sidebar_hfont;
            LOGFONT font_data = {0};
      
            sidebar_disp.GetProperty(DISPID_AMBIENT_FONT, &sidebar_font_var);
            sidebar_font_var.punkVal->QueryInterface(&sidebar_font);
            sidebar_font->get_hFont(&sidebar_hfont);
            ::GetObject(sidebar_hfont, sizeof(font_data), &font_data);
      
            // Create the font
            default_font_ = CreateFontIndirect(&font_data);
      
            // Get the default background color
            CComVariant read_color;
            sidebar_disp.GetProperty(DISPID_AMBIENT_BACKCOLOR, &read_color);
            OleTranslateColor(read_color.lVal, NULL, &background_color_);
      
            // Get the default foreground color
            sidebar_disp.GetProperty(DISPID_AMBIENT_FORECOLOR, &read_color);
            OleTranslateColor(read_color.lVal, NULL, &foreground_color_);
          }
      
    2. Insert the following code at the end of the file. The function is similar to the one automatically generated by the ATL Wizard, except it now uses the Sidebar provided colors and font.
      
      HRESULT CMyCirclesObj::OnDraw(ATL_DRAWINFO& di) {
        // Set Clip region to the rectangle specified by di.prcBounds
        RECT& rc = *(RECT*)di.prcBounds;
        HRGN hRgnOld = NULL;
        if (GetClipRgn(di.hdcDraw, hRgnOld) != 1)
          hRgnOld = NULL;
        bool bSelectOldRgn = false;
      
        HRGN hRgnNew = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);
        if (hRgnNew != NULL) {
          bSelectOldRgn = (SelectClipRgn(di.hdcDraw, hRgnNew) != ERROR);
        }
      
        // Draw the window
        HBRUSH background_brush = CreateSolidBrush(background_color_);
        FillRect(di.hdcDraw, &rc, background_brush);
        
        // Draw the text
        UINT old_text_align = SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE);
        int old_background_mode = SetBkMode(di.hdcDraw, TRANSPARENT);
        COLORREF old_text_color = SetTextColor(di.hdcDraw, foreground_color_);
        HFONT old_font = static_cast<HFONT>(SelectObject(di.hdcDraw,  default_font_));
      
        LPCTSTR center_text = _T("My Circles!");
        TextOut(di.hdcDraw, (rc.left + rc.right) / 2, (rc.top + rc.bottom) / 2, 
          center_text, lstrlen(center_text));
      
        // Restore the previous DC settings
        SetTextColor(di.hdcDraw, old_text_color);
        SetBkMode(di.hdcDraw, old_background_mode);
        SetTextAlign(di.hdcDraw, old_text_align);
        SelectObject(di.hdcDraw, old_font);
        
        // Remove the created objects
        DeleteObject(background_brush);
      
        if (bSelectOldRgn)
          SelectClipRgn(di.hdcDraw, hRgnOld);
      
        return S_OK;
      }
      
      CMyCirclesObj::CMyCirclesObj() {
        default_font_ = NULL;
      }
      
      CMyCirclesObj::~CMyCirclesObj() {
        // Remove the fonts and colors created in SetClientSite
        if (default_font_ != NULL)
          DeleteObject(default_font_);
      }
      
      

Back to top

Accepting Mouse Events

For MyCircles, we need to define how to handle two mouse events, OnMouseButtonDown and OnEraseBackground. Again, we'll be editing both the plug-in object's Header and CPP files.

OnMouseButtonDown will be called when users click the left mouse button. It will draw a circle at the clicked location.

OnEraseBackground is a dummy erase handler, which when called effectively cancels any background redraw requests. This will prevent most of the flashing effect that occurs when updating the tile.

  1. Open the plug-in object's Header file (for our example, MyCirclesObj.h)

    1. We are adding Windows Message handlers, which in ATL must be added in the message map. Find the BEGIN_MSG_MAP block and add the following code in it:
      
      BEGIN_MSG_MAP(CMyCirclesObj)
        MESSAGE_HANDLER(WM_LBUTTONDOWN, OnMouseButtonDown)
        MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
        CHAIN_MSG_MAP(CComControl<CMyCirclesObj>)
        DEFAULT_REFLECTION_HANDLER()
      END_MSG_MAP()
      
    2. Now we need to declare OnMouseButtonDown and OnEraseBackground. These declarations should be put at the end of the class block with the other method declarations:
      
      private:
        STDMETHODIMP SetClientSite(IOleClientSite* site);
        LRESULT OnMouseButtonDown(UINT msg, WPARAM wp, LPARAM lp, BOOL& handled);
        LRESULT OnEraseBackground(UINT msg, WPARAM wp, LPARAM lp, BOOL& handled);
      
    3. Finally, we need to add the variables that store the circle's position. These also go at the end of the class block with the other variable declarations (Red text indicates code that should be changed to values correct for your project instead of for MyCircles):
      
      private:
        HFONT default_font_;
        COLORREF foreground_color_;
        COLORREF background_color_;
        int circle_x_;
        int circle_y_;
      
  2. Open the plug-in object's CPP file (for our example, MyCirclesObj.cpp).

    1. Modify the constructor as follows. We are setting the circle position in the constructor to -1, which will keep the circle from being shown when the tile is first displayed.

      More importantly, we are setting this tile to create it's own window via the m_bWindowOnly = true statement. This allows for proper mouse positioning and redrawing requests later on (Red text indicates code that should be changed to values correct for your project instead of for MyCircles).
      
      CMyCirclesObj::CMyCirclesObj() {
        default_font_ = NULL;
        circle_x_ = -1;
        circle_y_ = -1;
        m_bWindowOnly = true;     
      }
      
    2. Add the following to the end of the CPP file. Whenever the mouse is clicked, its location will be stored in the variables and the tile redrawn to show the new circle location. OnEraseBackground is a simple handler that prevents Windows from clearing the tile and making it flash during updates.
      
      LRESULT CMyCirclesObj::OnMouseButtonDown(UINT msg, WPARAM wp, LPARAM lp, BOOL& handled) {
        // Set the location of the circle
        circle_x_ = GET_X_LPARAM(lp); 
        circle_y_ = GET_Y_LPARAM(lp); 
      
        // Force a redraw of the tile
        RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
        
        return 0;
      }
      
      LRESULT CMyCirclesObj::OnEraseBackground(UINT msg, WPARAM wp, LPARAM lp, BOOL& handled) {
        return 0;
      }
      
    3. Add the following in the OnDraw method, after the code that clears the draw area. It creates the brush and pen and draws the circle at the clicked location:
      
        // Draw the window
        HBRUSH background_brush = CreateSolidBrush(background_color_);
        FillRect(di.hdcDraw, &rc, background_brush);
      
        // Draw the circle at the last clicked 
        HBRUSH circle_brush = CreateSolidBrush(RGB(255, 0, 0));
        HBRUSH old_brush = static_cast<HBRUSH>(SelectObject(di.hdcDraw, circle_brush));
        HPEN circle_pen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
        HPEN old_pen = static_cast<HPEN>(SelectObject(di.hdcDraw, circle_pen));
      
        if ((circle_x_ != -1) && (circle_y_ != -1)) {
          Ellipse(di.hdcDraw, circle_x_ - 10, circle_y_ - 10, circle_x_ + 10, circle_y_ + 10);
        }
      
        // Draw the text
      
    4. After drawing, we need to delete the brush and pen we created and restore the previous ones. Modify the restore and delete code as follows:
      
        // Restore the previous DC settings
        SetTextColor(di.hdcDraw, old_text_color);
        SetBkMode(di.hdcDraw, old_background_mode);
        SetTextAlign(di.hdcDraw, old_text_align);
        SelectObject(di.hdcDraw, old_font);
        SelectObject(di.hdcDraw, old_brush);
        SelectObject(di.hdcDraw, old_pen);
        
        // Remove the created objects
        DeleteObject(background_brush);
        DeleteObject(circle_brush);
        DeleteObject(circle_pen);
      
Back to top

Saving and Loading Data

Finally, we show how to save and load data for the plug-in. For example, you may want to save the state of your plug-in when the Sidebar is closed. Again, we need to edit both the plug-in object's Header and CPP files.

  1. Open the plug-in object's Header file (For our example, MyCirclesObj.h). Add the following code in the method definitions (Red text indicates code that should be changed to values correct for your project instead of for MyCircles).

    InitNew, Load, and Save are all methods of IPersistStream. InitNew is called the first time your plug-in is loaded to initialize it with its default settings. Load is called when your plug-in is created and the Sidebar has the settings data to initialize your plug-in. Save is called to save the tile's content when the Sidebar is closing. You can save whatever data you would like in the given stream. For our example, we save the circle's x and y positions.
    
    private:
      STDMETHODIMP SetClientSite(IOleClientSite* site);
      STDMETHOD(InitNew)();
      STDMETHOD(Load)(IStream *stream);
      STDMETHOD(Save)(IStream *stream, BOOL clear_dirty);
      LRESULT OnMouseButtonDown(UINT msg, WPARAM wp, LPARAM lp, BOOL& handled);
      LRESULT OnEraseBackground(UINT msg, WPARAM wp, LPARAM lp, BOOL& handled);
    
  2. Open the plug-in object's CPP file (for our example, MyCirclesObj.cpp). Insert the following at the end of the file:
    
    STDMETHODIMP CMyCirclesObj::InitNew() {
      return IPersistStreamInitImpl<CMyCirclesObj>::InitNew();
    }
    
    STDMETHODIMP CMyCirclesObj::Save(IStream *stream, BOOL clear_dirty) {
      HRESULT hr = S_OK;
      if (!stream)
        return E_POINTER;
    
      // Write the x position of the circle
      hr = stream->Write(&circle_x_, sizeof(circle_x_), NULL);
      if (FAILED(hr))
        return hr;
    
      // Write the y position of the circle
      hr = stream->Write(&circle_y_, sizeof(circle_y_), NULL);
      if (FAILED(hr))
        return hr;
    
      if (clear_dirty)
        SetDirty(FALSE);
    
      return hr;
    }
    
    STDMETHODIMP CMyCirclesObj::Load(IStream *stream) {
      HRESULT hr = S_OK;
      if (!stream)
        return E_POINTER;
    
      // Read the x position of the circle
      hr = stream->Read(&circle_x_, sizeof(circle_x_), NULL);
      if (FAILED(hr))
        return hr;
    
      // Read the y position of the circle
      hr = stream->Read(&
    circle_y_, sizeof(circle_y_), NULL);
      if (FAILED(hr))
        return hr;
    
      return S_OK;
    }