Drop Bar – An Internet Explorer ‘& Windows Explorer Toolbar





Click here for larger image

Environment: VC6, NT4, 98, Me, 2000, XP

Introduction

Drop Bar is a Windows Explorer / Internet Explorer toolbar that allows the drag and drop of files onto its buttons. User defined actions can then be performed with the files being dropped. A button’s actions can be programmed in your preferred scripting language such as javascript or vbscript.

Design

Drop Bar is COM class that resides in the library Droppolino.dll. It is based upon an ATL “Simple Object” skeleton. The main toolbar functionality is provided by implementing the IDeskBand and the IObjectWithSite interfaces.

I decided not to use MFC to keep minimal dependencies. I decided also keep using familiar classes like CString, CRect, CDC … Therefore a MFC CString replacement (ATL_CString.h ATL_CString.cpp) is included in the project. These files are auto-generated as explained in K. Shane Triem’s CodeGuru article Add MFC’s CString class to ATL with No MFC Dependencies. For comfy handling of structs like RECT, SIZE there exists minimal class wrappers CRect, CSize … (see utility.h, utility.cpp).

Drop Bar uses MSXML for storing its configuration and Microsoft Script Control for scripting functionality. These libraries must be installed in order to successfully build the project.

References are included through the files reference_msxml3.h and reference_msscript.h. These files #import the libraries msxml3.dll, msscript.ocx with usage of COM Support Classes.

Using COM Support Classes is, in my opinion, the preferred way of programming COM with MS C++. Using them has some pitfalls however. It’s almost impossible to have no naming conflicts when using the #import no_namespace attribute and the namespace that is declared in the type library is often long and hard to bear in mind. What I do is rename_namespace to a short and meaningful name that is most often the name of the library file itself. For instance XML or MSScript. See the following snippet from reference_msxml3.h.

#import "msxml3.dll" rename_namespace("XML")

Now typedef most commonly used interfaces into the global namespace.

// typedef default interfaces of coclasses to
// make it more readable
typedef XML::IXMLDOMDocumentPtr IXMLDOMDocumentPtr;
typedef XML::IXMLDOMElementPtr IXMLDOMElementPtr;

For dealing with COM Support Classes there exists several helper functions and macros (comstuff.h). It removes those scaring underscores from classes like _variant_t and _bstr_t and makes it, let’s say, human readable! It uses the _com_util namespace. Versions of Visual Basic functions like isNull(), isEmpty(), isMissing() are also provided.

One major advantage of COM Support Classes is QueryInterface’ing through copy constructors and (of course) no reference counting. The following snippet demonstrates QueryInterface’ing through copy constructor.

variant value =
   IXMLDOMElementPtr(xmlDocument->SelectSingleNode("body"))->
                   getAttribute("bgcolor");

You will never get that on one line using CComPtr, CComQIPtr.

String conversion is efficient and easier because Drop Bar uses UNICODE only. Therefore conversions between BSTR and CString can be achieved by simple type casting. Snippet from comstuff.h.

// casts bstr to CString.
// @param s bstr to convert.
// @return casted string.
#define asCString(s) (BSTR)(bstr)s

// casts CString to bstr.
// @param cs CString to convert.
// @return casted string.
#define asBSTR(cs) (LPCWSTR)cs

The main entry point for Drop Bar is the call to IObjectWithSite::SetSite(). If Drop Bar gets attached to the site it creates its main window, registers itself as a drop target and creates a window of class TOOLTIPS_CLASS for displaying the buttons descriptions (tool tips). Drop Bar then draws its buttons in the CDropBar::OnDraw() function.

When Drop Bar needs to draw its buttons (OnDraw()) it first checks if the configuration file has changed. If it has so it reloads it and displays the changes accordingly. This is done by listening on a FindFirstChangeNotification handle. A FindFirstChangeNotification handle is set up on the users “My Documents” folder (where the configuration file resides).

CDropBar::CDropBar()
   ...
{
   ...
   // listen on config path for changes
   setConfigFileChange(FindFirstChangeNotification(
      getConfigPath(),
      FALSE,
      FILE_NOTIFY_CHANGE_LAST_WRITE));
   ...
}

Check configuration on OnDraw().

HRESULT CDropBar::OnDraw(ATL_DRAWINFO& di)
{
   ...
   // check if a change was made and reset Drop Bar
   checkConfig();
   ...
}

If a change was made in this folder compare the configuration files timestamp with the one previously remembered. If the timestamps are not equal then the configuration file has changed.

void CDropBar::checkConfig()
{
  HANDLE change = getConfigFileChange();

  if (WAIT_OBJECT_0 == WaitForSingleObject(change, 1))
  {
    // something has changed in config path.
    // check if signaled by config file.

    FILETIME time = configTime();

    if (! isLastConfigTime(time))
    {
      reset();

      setLastConfigTime(time);
    }
  }

  FindNextChangeNotification(change);
}

As mentioned above Drop Bar uses an xml file for storing its configuration. This file is named dropBar.xml and resides in the users “My Documents” folder. This folder is obtained with a call to SHGetSpecialFolderPath().

CString CDropBar::getConfigPath() const
{
  CString path;

  SHGetSpecialFolderPath(m_hWnd,
                         path.GetBuffer(_MAX_PATH),
                                        CSIDL_PERSONAL,
                                        FALSE);
  path.ReleaseBuffer();

  return path;
}

The structure of the xml is described in the contained DTD.

<!DOCTYPE dropBar
[
  <!ELEMENT dropBar (script?, button*)>
  <!ATTLIST dropBar version CDATA #FIXED "1.0">

  <!ELEMENT script (#PCDATA)>
  <!ATTLIST script language CDATA "javascript">

  <!ELEMENT button (icon?, script?)>
  <!ATTLIST button description CDATA #IMPLIED>
  <!ATTLIST button separator CDATA "0">

  <!ELEMENT icon EMPTY>
  <!ATTLIST icon file CDATA #REQUIRED>
  <!ATTLIST icon index CDATA "0">
]>

Here is the explanation.

The document elements name is “dropBar”. It must have an attribute “version” with the value set to “1.0”. It may contain a “script” element and any number (0..~) of “button” elements. “script” elements may have an attribute “language” that specifies the scripting language to be chosen (vbscript, javascript…). If no language attribute exists “javascript” will be used. The “script” element of “dropBar” should contain a onCustomize(file) function that gets called whenever the user right-clicks Drop Bar and choses “Customize…” from the context menu. The file parameter is the full path of the configuration file. Snippet from sampleConfig.xml:

<script>
  function onCustomize(file)
  {
    // run notepad to edit configuration file
    new ActiveXObject(
       "WScript.Shell").Run("notepad \"" + file + "\"");
  }
</script>

A “button” element should have a “description” attribute. It is used as the text for the buttons tool tip. If a buttons “separator” attribute is not equals “0” (true) then a separator is displayed in the Drop Bar. If the “separator” attribute is “0” (which is the default value) then the button must contain an “icon” and a “script” element. The “icon” element must have a “file” attribute that specifies the full path of the file containing the icon. The “icon” element can specify an “index” attribute that specifies the index of the icon within the file. If “index” is not specified the default value “0” will be used. The buttons “script” element should contain an onClick() and an onDrop(file) function. onClick() gets called whenever the user clicks on that button. onDrop(file) gets called whenever a file is dropped onto the button. The file parameter specifies the file being dropped.

For an example configuration with buttons for Notepad, Command Prompt, Visual Studio… please refere to the file sampleConfig.xml that is part of this distribution.

If loading of the configuration fails for any reason, may because of malformed xml or wrong structure, Drop Bar loads a default configuration from its resources. CXMLDOMDocument::loadXMLRes() provides this functionality.

// loads xml from custom resource of type "XML"
// @param resId id of resource.
// @param module module where the resource is located.
// @return true if successful.
bool CXMLDOMDocument::loadXMLRes( LPCTSTR resId,
                                  HINSTANCE module)
{
  HRSRC res = FindResource(module, resId, L"XML");

  bool ok = res != 0;

  if (ok)
  {
    HGLOBAL hRes = LoadResource(module, res);

    ok = hRes != 0;

    if (ok)
    {
      DWORD size = SizeofResource(module, res);
      char *data = (char*)hRes;
      bool deleteData = false;

      if (data[size])
      {
        // size bigger than page

        data = new char[size + 1];

        memcpy(data, (char*)hRes, size + 1);

        // terminate
        data[size] = 0;

        deleteData = true;
      }

      bstr xml = data;

      if (deleteData)
      {
        delete data;
      }

      ok = 0 != (*this)->loadXML(xml);
    }
  }

  return ok;
}

When registering/unregistering Drop Bar the Drop Bar class must additionally register/unregister itself for the component category CATID_DeskBand. Additionally Drop Bar must create/delete its key from the registry where Internet Explorer stores all currently installed toolbars (HKLM\Software\Microsoft\Internet Explorer\Toolbar). registerToolbarBand() provides this functionality. Snippet from utility.cpp.

// registers class for necessary component categories
// and Internet Explorer registry entries.
// @param clsid class id that is a Internet Explorer Band.
// @param doRegister true if wants to register, false
// if wants to unregister.
void registerToolbarBand(const CLSID &clsid, bool doRegister)
{
  ICatRegisterPtr cat(CLSID_StdComponentCategoriesMgr);

  CLSID catIds[1];
  catIds[0] = CATID_DeskBand;

  if (doRegister)
  {
    cat->RegisterClassImplCategories(clsid, 1, catIds);
  }
  else
  {
    cat->UnRegisterClassImplCategories(clsid, 1, catIds);
  }

  // create or delete internet explorer toolbar entry
  CRegKey key;

  if (ERROR_SUCCESS == key.Open(HKEY_LOCAL_MACHINE,
        L"Software\\Microsoft\\Internet Explorer\\Toolbar"))
  {
    LPOLESTR clazz;

    if (SUCCEEDED(StringFromCLSID(clsid, &clazz)))
    {
      if (doRegister)
      {
        key.SetValue(L"", clazz);
      }
      else
      {
        key.DeleteValue(clazz);
      }

      CoTaskMemFree(clazz);
    }
  }
}

Additional Links/ Information

Use CString without MFC Add MFC’s CString
class to ATL with No MFC Dependencies
IDropTarget implementation class CDropBar
obtaining HDROP handle from IDataObject CDropBar::dropEffect()
MSXML CDropBar::loadConfig()
Microsoft Script Control CDropBar::execConfig()
Listening on folder for changes CDropBar::CDropBar(), CDropBar::checkConfig(), CDropBar::OnDestroy()
obtaining last write time of a file CDropBar::configTime()
Using TOOLTIPS_CLASS CDropBar::SetSite(), CDropBar::addTools(), CDropBar::delTools(),
CDropBar::onToolsRelayEvent()
flicker free drawing using BitBlt() CDropBar::OnDraw()
registering for COM component categories registerToolbarBand()
get system folders (My Documents, Program Files…) CDropBar::getConfigPath()
get notified when mouse leaves window (TrackMouseEvent()) CDropBar::initMouseLeave()
using SAFEARRAY CDropBar::execScript()
using std::vector class CIcons, class CParams, class CRects
load XML from resource CXMLDOMDocument::loadXMLRes()
another button implementation CDropBar::OnLButtonDown(), CDropBar::OnMouseMove(), CDropBar::OnLButtonUp(),
CDropBar::OnMouseLeave(), CDropBar::OnDraw()
load icons from file CDropBar::getIcons()
XML, DTD dropBar.xml, sampleConfig.xml

Credits

Downloads

Download zip containing Self-Extracting Installer – 391 Kb
Download source – 79 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read