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.
- Start Visual Studio .NET 2003.
- Go to the File menu and select New,
Project...
- In Project Types, go to Visual C++ Projects and then ATL
- 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.
- The ATL Project Wizard will ask you for settings. On the dialog's
left side, click on the Application Settings tab.
- Uncheck the Attributed checkbox. Then click the
Finish button.
- 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.
- Right click on the main project (MyCircles) in the
Solution Explorer and select Properties.
- At the top, beside Configuration, select
All Configurations.
- 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.
- Also in the Properties dialog, go in the Build
Events folder and select Post-Build Event.
- 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.
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.
-
Import GoogleDesktopComponentCommon into your application
by right clicking on your solution (for our example, MyCircles) and then selecting Add,
Existing Project....
- Find the GoogleDesktopComponentCommon.vcproj file in
the SDK's /api/samples/common area and import it by
clicking Open.
- 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.
- 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.
- In the Solution Explorer, click on the Class View
tab.
- Right click on your project (MyCircles for the
example) and select Add, Add Class....
- Select ATL Control and click Open.
- 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.
- 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).
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.
- 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).
- 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"
- 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.
- Create the DLL by going to the Build menu and
selecting Build Solution.
- 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.
- Google Desktop will show you a dialog to confirm the
registration. Click OK to do so. You
now have an active plug-in.
- You should test your plug-in to be sure that it is runable
and appearing in the Sidebar.
- 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.
- Open the plug-in object's CPP file (MyCirclesObj.cpp
in our example).
- 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"
- 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).
- 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.
- Open the plug-in object's Header file (For our example, MyCirclesObj.h).
- Remove the constructor method and replace it with a definition such as:
public CComControl<CMyCirclesObj>
{
public:
CMyCirclesObj();
~CMyCirclesObj();
DECLARE_OLEMISC_STATUS(OLEMISC_RECOMPOSEONRESIZE |
- 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()
- Add the following variables to the end of the class before ending scope:
private:
HFONT default_font_;
COLORREF foreground_color_;
COLORREF background_color_;
};
- Open the plug-in object's CPP file (for our example, MyCirclesObj.cpp).
- 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_);
}
- 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_);
}
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.
- Open the plug-in object's Header file (for our example,
MyCirclesObj.h)
- 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()
- 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);
- 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_;
- Open the plug-in object's CPP file (for our example,
MyCirclesObj.cpp).
- 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;
}
- 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;
}
- 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
- 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);
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.
- 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);
- 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;
}
|