From the July 2001 issue of MSDN Magazine.

MSDN Magazine

Understanding Categories with CatView, Getting Toolbars in 256 Colors

Paul DiLascia

Download the code for this article:CQA0107.exe (93KB)
Browse the code for this article at Code Center:CatView and TBColor

Q I'm building an application framework that supports ActiveX® document plug-ins. At program startup I scan the registry for installed ActiveX components. I want to create a menu of all installed plug-ins. For each ActiveX component, I create an instance and query for a special interface called IMyAppPlugin. If this interface exists then it is reasonable to assume that the component is indeed a plug-in for my application. This seems like a bad way to do it, especially if there are a lot of ActiveX components installed. Is there a better way?

Firoz Mangroe
Netherlands

A Yes indeedy, there is a better way. In fact, Windows has exactly the ticket for this situation: it's called a category. To humans, a category is a kind of ActiveX control. For example "My Acme Plugin" or "Blue Insertable Thingie." To COM, a category is just a GUID—but a GUID that identifies a category is called a CATID, the same way a GUID that identifies a class is called a CLSID.
      How does it all work in real life? First, you generate a new GUID (using GUIDGEN or whatever your favorite mechanism is), call it CATID_AcmePlugin. Next, you use a special COM interface, ICatRegister, to register your category. The normal place to do it is in your component's DllRegisterServer function. To get ICatRegister, call CoCreateInstance or one of its equivalents.

  // in DllRegisterServer
CComPtr<ICatRegister> spcr;
spcr.CoCreateInstance(CLSID_StdComponentCategoriesMgr,
NULL, CLSCTX_INPROC);

 

This snippet uses ATL smart pointers; CComPtr<ICatRegister>::CoCreateInstance even knows to call ::CoCreateInstance with the IID for ICatRegister. Once you have ICatRegister, you can call RegisterCategories. First, fill out a CATEGORYINFO struct with information about your category.

  CATEGORYINFO catinfo;
catinfo.catid = CATID_AcmePlugin;
catinfo.lcid = 0x0409; // locale=english
USES_CONVERSION; // uses A2W
wcscpy(catinfo.szDescription,
A2W("My Acme Plugin."));
pcr->RegisterCategories(1, &catinfo);

 

      Next, how do you tell COM that your COM class is in fact an Acme Plugin? ICatRegister has a method for that, too. It's called RegisterClassImplCategories.

  // Also in DllRegisterServer
CATID catid = CATID_AcmePlugin;
pcr->RegisterClassImplCategories(
CLSID_MyPluginObj, 1, &catid);

 

This registers your COM class, implementing the category CATID_AcmePlugin. Pretty simple. This may all seem rather tedious, but ICatRegister puts the info about which classes implement which categories into the registry, where Windows® can read it quickly without doing what you were doing: namely, instantiating every component to look for IMyAcmePlugin.
      ICatRegister has other methods to unregister as well as register, and methods to register and unregister required (as opposed to implemented) categories, meaning that your COM class requires its container to implement those categories. Required categories are useful when your component requires specific callback interfaces. Figure 1 shows the full ICatRegister interface. For a real-life programming example of registering categories, look at the band object in my article "My Band is Your Band" in the November 1999 issue of MSJ.
      So much for registering. Now suppose you're writing a container and you want to generate a list of Acme Plugins—that is, components that implement CATID_AcmePlugin. Windows provides another interface, ICatInformation (see Figure 2), that lets you enumerate the classes that implement a given category. To show how ICatInformation works, I wrote a little program, CatView, that lets you view the categories in your system. Figure 3 shows it running; Figure 4 shows the relevant source files. As always, you can download the full program from the link at the top of this article.
      CatView is a typical two-paned app with a list of categories in the left pane and—when you click on one—a list of classes that implement that category on the right. (CatView uses CWinMgr from my article "Windows UI: Our WinMgr Sample Makes Custom Window Sizing Simple" in this month's issue to manage its windows.) Figure 3 shows "Active Scripting Engine with Parsing" selected, with the components that implement it on the right: XML, Java, Visual Basic®, and PerlScript scripting engines. The two functions of main interest in CatView are CLeftView::PopulateCategoryList and CRightView::ShowCategory. To make life easy, I implemented some handy helper classes in a file CoolCat.h. The first is CCatInformation, which encapsulates ICatInformation using ATL smart pointers.

  class CCatInformation : public CComPtr<ICatInformation> {
public:
CCatInformation() {
CoCreateInstance(CLSID_StdComponentCategoriesMgr,
NULL, CLSCTX_INPROC);
}
};

 

      With CCatInformation, there's no need to call CoCreateInstance—just instantiate and go.

  CCatInformation spCatInfo;
spCatInfo->SomeMethod(...);

 

      To enumerate the categories in your system, you call ICatInformation::EnumCategories. This function gives back a pointer to an IEnumCATEGORYINFO interface, which you then use to enumerate the categories.

  // IEnumCATEGORYINFO
CCatInformation spCatInfo;
CComPtr<IEnumCATEGORYINFO> spEnumCatInfo;
HRESULT hr = spCatInfo->EnumCategories(
GetUserDefaultLCID(),&spEnumCatInfo);
ASSERT(SUCCEEDED(hr));

// Now use it to enumerate categories
ULONG nRet=0;
CATEGORYINFO catinfo;
while (SUCCEEDED(spEnumCatInfo->Next(1,
&catinfo, &nRet)) && nRet==1) {
// add catinfo to list
}

 

      The COM mechanics are soooo tedious, even with ATL smart pointers, that I encapsulated all this in a helper class, CCatIterator. With CCatIterator all you have to write is:

  CATEGORYINFO catinfo;
CCatIterator it;
while (it.Next(catinfo)) {
// add catinfo to list
}

 

      CLeftView::PopulateCategoryList uses CCatIterator to populate its list view with the name and CATID for each category. Each call to Next fills catinfo with information about the next category. Please folks, whenever you program COM, it's important to write these little helper classes to spare yourself the agony of HRESULTs and interface pointers, angle brackets and Release and—yech, I hate that stuff! Code should not only work right, it should look beautiful!
      Once you have a CATID, you can use ICatInformation to get a list of COM classes that implement that category. For example, all the controls that implement CATID_AcmePlugin. The mechanics are the same as for categories, only the magic method is ICatInformation::EnumClassesOfCategories and the enumerator is IEnumCLSID. As before, I wrote a helper class, CCatClassIterator, to hide the grungies.

  CLSID clsid;
CCatClassIterator it(&catid, 1);
while (it.Next(clsid)) {
// add clsid to list
}

 

      Like ICatInformation::EnumClassesOfCategories on which it rests, CCatClassIterator lets you specify more than one implemented category. For example, "Find all the controls that are Acme Plugins and Blue Insertable Thingies." In that case, you'd pass an array with two CATIDs. You can also specify one or more required categories to find controls that require one or more given categories. CCatClassIterator hides all the extra arguments by giving them NULL defaults.
      That's it as far as categories go. The rest of CatView is just the usual boring Windows and MFC mechanics. CatView is a doc/view app, but CDummyDoc exists only to appease the MFC gods. CMainFrame::OnCreateClient creates the right pane and hooks it to the left one after doing the normal CFrameWnd thing. The only other interesting bit is CLeftView::OnWinMgr, which reports the list view's ideal TOFIT size by adding the widths of its columns. (To learn about WinMgr and TOFIT, read my article "Windows UI: Our WinMgr Sample Makes Custom Window Sizing Simple" in this issue.)
      You can download CatView from the link at the top of this article and build and run it on your own machine to see what categories are registered. If you do, you'll note some pretty obscure categories (Visual InterDev® Web Site Wizards) and some generic ones like Control (which includes almost everything), Automation Objects, and Insertable. As an historical note, the Insertable category is the progenitor of the whole category concept. Way back in the early days, Visual Basic needed a way to know which objects could be inserted into forms, without instantiating every class in the registry to QueryInterface for IOleInPlaceObject. The solution was to add a special key, HKCR\CLSID\{CLSID}\Insertable, which told Visual Basic that the class was insertable. The Redmondtonians later expanded this mechanism to the more general concept of categories described here. Today, the Insertable key is a legacy thing, required for 32-bit objects that can be inserted in 16-bit apps.

Q I have a problem I cannot find the answer for. Please tell me why the MFC toolbars and views (CListView, for example) do not display the 256-color icons and bitmaps I create in my resources. Instead they are all displayed as 16-color icons or bitmaps. Many Windows-based programs including Explorer, Microsoft® Internet Explorer, and others display full-color toolbars and icons. Please help!

Many Readers

A Kind of makes you feel like a second-class citizen, doesn't it? All those other apps have pretty multihued buttons, while your toolbars have a crude, color-challenged toy look. Never fear, it's easy to turn up the volume on your toolbar color dial.
      Toolbars and list views both store their images in image lists. An image list is just what it sounds like: a list of images. Only the "list" isn't a linked list, it's a long bitmap that holds all the images, one after another. Say you have seven 20�20 icons. The image list holds them as one 140�20 bitmap (7�20 = 140). The bitmap can have as many color planes as you want; but you have to tell the image list how many colors to use when you create it. By default, you get 16. And when MFC loads your toolbar bitmap, it uses an internal function, AfxLoadSysColorBitmap, that presumes 16 colors. To get around MFC, you have to replace the bitmap; to do that, you have to get at the image list.

Figure 5 Pretty Toolbar
Figure 5 Pretty Toolbar

      I wrote a little app, TBColor, that shows how. TBColor is a plain, normal, ordinary, boring—did I say pedestrian?—MFC application with a pretty toolbar. Figure 5 tells the story. The buttons don't do much: any one you press brings up the About dialog. But how do the buttons get 256 colors? It all happens in CMainFrame::OnCreate (see Figure 6).

Figure 7 The Bitmap
Figure 7 The Bitmap

      The first step is to make sure you load your toolbar bitmap using ::LoadImage. This handy multipurpose function can load cursors, icons, and bitmaps. In the case of bitmaps, it will even (if you ask nicely) map certain colors in your bitmap to the current screen colors. For example, TBColor's toolbar bitmap (see Figure 7) uses RGB(192,192,192) for the background; LoadImage with LR_LOADMAP3DCOLORS maps this to whatever the actual 3D face color (GetSysColor(COLOR_3DFACE)) happens to be on your system.

  HBITMAP hbm = (HBITMAP)::LoadImage(AfxGetInstanceHandle(),
MAKEINTRESOURCE(IDR_MAINFRAME),
IMAGE_BITMAP,
0,0, // cx, cy
LR_CREATEDIBSECTION | LR_LOADMAP3DCOLORS );

 

      LoadImage returns a handle to the bitmap (HBITMAP), but MFC likes CBitmaps better, so the next thing is to create one and attach your handle to it.

  CBitmap bm;
bm.Attach(hbm);

 

      Now that you have a CBitmap, you can create a new image list from it.

  m_ilToolBar.Create(20,20,ILC_COLOR8,4,4);
m_ilToolBar.Add(&bm,(CBitmap*)NULL);

 

m_ilToolBar is a CMainFrame member and ILC_COLOR8 is the magic flag that says: create an 8-bit paletted bitmap—that is, one with 256 colors. You can even create a bitmap with 24-bit color if you like—but keep in mind LR_LOADMAP3DCOLORS only works with paletted bitmaps, a little secret mommy never told you. In any case, once you've made your image list your way, the final step is to serve it to the toolbar.

  m_wndToolBar.GetToolBarCtrl().
SetImageList(&m_ilToolBar);

 

Pretty simple, once you know the voodoo. I never actually tried it, but the same technique should work with list views and rebars and any other control that uses image lists. Now, go forth and unleash your inner Kandinsky!
Send questions and comments for Paul to cppqa@microsoft.com.**

Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of* Windows*++:* Writing Reusable Windows Code in C*++ (Addison-Wesley, 1992). Paul can be reached at askpd@pobox.com or https://www.dilascia.com.