Initializing Class Objects, The TestAD App and Active Desktop, #include Problems

Paul DiLascia

Download the code for this article: Cqa0105.exe (54KB)
Browse the code for this article at Code Center:TestAD

In the printed version of the May issue of MSDN Magazine, several lines of code were inadvertently omitted from Figure 7. The version here is complete.

QHow do I initialize a table of data for a class I've written? In C, I can initialize an array of structures like this:

  struct MYSTRUCT {
  
int x,y,z;
};

MYSTRUCT table[] = {
{ 1,2,3 },
{ 4,5,6 },
... // etc
};

 

But if MYSTRUCT is a C++ class instead of a struct, I get compiler errors. To me, this seems like a deficiency of C++.

Joe Tzu
San Diego

A Well, it all depends on how you look at it. One of the advantages of C++ is that it coerces you into doing the right thing. For example, C++ hates to create objects without calling a constructor. That's why you can't initialize a class object with raw data, whether it's part of an array or by itself. The purpose of constructors is to make sure every object is initialized correctly, whether you allocate it from the program stack, free memory heap, or as an element of a static array. Bypassing constructors with raw data is considered taboo. It's not that you can't create a static array of objects initialized with dataâ€"you just have to call a constructor!

  class CFooble {
  
int x,y,z;
public:
CFooble(int xx, int yy, int zz)
: x(xx),y(yy),z(zz) { ... }
CFooble(int i) { x=y=z=i; }
};
CFooble table[] = {
CFooble(1,2,3),
CFooble(4,5,6),
CFooble(0), // can use any constructor!
};

 

      See Figure 1 for a full sample that you can compile. In C++, you can initialize array elements using any constructor, and C++ even uses the default constructor to initialize extra elements without explicit initializers. To me, that's an improvement, not a deficiency!

Q How can I enable Active Desktop® from my application? Users can right-click on the desktop and select Active Desktop | View As Web Page to turn the Active Desktop feature on or off. Is there any function I can use to implement this operation from my program? Also, how can I tell when the user has enabled or disabled Active Desktop?

Sean Xie
Shanghai, China

A Before I answer, let me give you a big warning. If you plan to turn Active Desktop on or off, please make sure you get your user's permission! A good way to do this would be to display a 72-point message that says, "I AM ABOUT TO TURN ACTIVE DESKTOP ON, DO YOU REALLY WANT TO DO THIS????" Anything less is just plain rude. Users don't want some program deciding it wants Active Desktop, which makes everything run like molasses, especially if they've gone to the trouble of turning it off. Likewise, users don't want to lose their Active Desktop if they've elected to endure the creeping performance in order to fill their screen with Web widgets and other cool content. Sheesh!
      OK, so much for the stern admonition. Let's assume you have a good reason for turning Active Desktop on or off. Perhaps you're writing a new shell. (Good luck!) To enable or disable Active Desktop, you need IActiveDesktop, the COM interface to Active Desktop. Figure 2 lists the methods of this interface. IActiveDesktop lets you add and remove desktop items (HTML pages, pictures, URLs, or ActiveX® controls), set and retrieve the wallpaper (only the Active Desktop wallpaperâ€"for normal mode, use SystemParametersInfo), and other useful functions. The function that lets you turn Active Desktop on or off is SetDesktopItemOptions. But firstâ€"how do you get IActiveDesktop? By creating an instance in the normal COM way.

  IActiveDesktop* pAD;
  
HRESULT hr = ::CoCreateInstance(
CLSID_ActiveDesktop,
NULL, // no outer unknown
CLSCTX_INPROC_SERVER,
IID_IActiveDesktop,
(void**)&pAD);

 

      Don't forget to call CoInitialize somewhere in your startup code, like in your app's InitInstance function if you're using MFC. Once you have a pointer to IActiveDesktop, you can call its methods.

  // enable ActiveDesktop
  
COMPONENTSOPT opt;
opt.dwSize = sizeof(opt);
opt.fActiveDesktop =
opt.fEnableComponents = TRUE;
HRESULT hr = pAD->SetDesktopItemOptions(&opt,0);

 

      Now Active Desktop is enabled. Or is it? When I first tried this at home, nothing happened. After scratching my head and checking my code, I discovered that the change doesn't take effect until you take care of a little undocumented detail:

  pAD->ApplyChanges(AD_APPLY_REFRESH);
  

 

      Sigh. Don't forget to Release your interface when you're done! (Of course, you shouldn't be using raw interface pointers, you should be using ATL smart pointersâ€"you are using them, aren't you?) To find out if Active Desktop is on or off, there's a corresponding get function, GetDesktopItemOptions, that uses the same COMPONENTSOPT structure. There's also a shell function that does the same thing.

  // Is Active Desktop on or off?
  
SHELLFLAGSTATE shfs;
SHGetSettings(&shfs,SSF_DESKTOPHTML);
BOOL bADEnabled = shfs.fDesktopHTML;

 

      No need for COM, CoCreateInstance, IActiveDesktop, or any of that. Just call the function. You can use SHGetSettings to find other nifty shell settings; Figure 3 gives the full poop. The settings correspond more or less to the options in the View | Folder Options | View tab in Windows® Explorer (see Figure 4). Alas, there's no corresponding set function. Maybe that's because it would have to be called SHSetSettings, which sounds stupid.

Figure 4 View | Folder Options
Figure 4 View | Folder Options

      If there are two ways to find out if Active Desktop is enabled (SHGetSettings and IActiveDesktop::GetDesktopItemOptions), inquiring minds cannot help but wonder: which is better? Does it matter? To answer that, let's move on to the second part of your question: how do you know when Jane User turns her Active Desktop on or off, either from the desktop menu or the properties dialog shown in Figure 5?

Figure 5 Selecting Active Desktop
Figure 5 Selecting Active Desktop

      When Jane turns Active Desktop on or off, Windows broadcasts a WM_SETTINGCHANGE to all top-level windows, with wParam = 0 and lParam = "ShellState". So all you have to do to trap this event is handle WM_SETTINGCHANGE.

  // in top-level frame window!
  
void CMainFrame::OnSettingChange(UINT
uFlags, LPCTSTR pszSection)
{
if (lpszSection &&
_tcscmp(pszSection,_T("ShellState"))==0) {
// do what you want
}
CFrameWnd::OnSettingChange(uFlags,
pszSection);
}

 

      WM_SETTINGCHANGE is a general-purpose message that Windows broadcasts when a program alters a SystemParametersInfo setting. In this case, wParam/uFlags is the SPI_SETXXX code of the setting that was changed. But WM_SETTINGCHANGE is used more generally in other situations, too. In the general case, wParam/uFlags is 0 and lParam/pszSection is the name of the WIN.INI section or registry key (only the name of the final key, not the whole string) of the section that changed. In fact, WM_SETTINGCHANGE used to be called WM_WININICHANGE, and the two symbols are still #defined to the same value! When IActiveDesktop broadcasts a setting change, it passes "ShellState" as the section name because the Active Desktop settings are stored in a registry key

  \HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\ShellState 
  

 

      By the way, if you ever need to broadcast your own global setting change, you can use WM_SETTINGCHANGE, too. You should broadcast it using SendMessageTimeout(HWND_BROADCAST, ...).

Figure 6 TestAD
Figure 6 TestAD

      To put everything together, I wrote a little program called TestAD (see Figure 6 and Figure 7). TestAD displays a message when it gets WM_SETTINGCHANGE. It uses a class I wrote, CActiveDesktop, to get and set the state of Active Desktop. To use it, all you have to do is write

  CActiveDesktop ad;
  
if (!ad.IsEnabled())
ad.Enable(TRUE);

 

      CActiveDesktop hides all the COM grungies. It uses ATL smart pointers for totally brainless, error-free interface manipulation. (If you're not using CComQIPtr by now, get with the programâ€"when the End Of The World comes, anyone who ever typed AddRef or Release goes to the ninth bitbucket.)
      CActiveDesktop doesn't encapsulate all of IActiveDesktop, only the functions I needed to write TestAD. If I ever feel like writing a shell for Windows (not), I'll add the missing methods. Meanwhile, you're on your own. CActiveDesktop is totally straightforward, so I leave you to peruse the source by your lonesome.
      When implementing CActiveDesktop and TestAD, I encountered some unexpected anomalousness. First, there was the already-mentioned ApplyChanges surprise. Then, I discovered what appears to be a synchronization bug with IActiveDesktop. When I first implemented TestAD, IActiveDesktop seemed to report the wrong state. It said Active Desktop was off when really it was on, and vice versa. I assumed the fault lay in my code, but when I investigated, I discovered IActiveDesktop::GetDesktopItemOptions did in fact report the wrong state! Here's the scenario:

  • TestAD calls CActiveDesktop::Enable(TRUE).
  • CActiveDesktop calls IActiveDesktop::SetDesktopItemOptions, then ApplyChanges.
  • ApplyChanges broadcasts WM_SETTINGCHANGE to all top-level windows.
  • CMainFrame gets WM_SETTINGCHANGE, and calls IActiveDesktop::GetDesktopItemOptions to get the on/off stateâ€"but IActiveDesktop reports the state is still off!

      Apparently IActiveDesktop doesn't update its internal state until the broadcast is complete. Meanwhile, GetDesktopItemOptions reports the old on/off state. What's a programmer to do? I tried fixing the problem by posting a message to myself, to append the "Active Desktop is ON/OFF" message after my main window finished handling WM_SETTINGCHANGE. This fixed the problem when TestAD was the program turning Active Desktop on/off, but when I used the Desktop context menu, the same problem occurred. No doubt Windows was still broadcasting WM_SETTINGCHANGE to the next top-level window even while TestAD was processing the posted message.
      What to do? Wait half a second before displaying the status message? Yuk. This is where SHGetSettings comes in. Does SHGetSettings report the true state? In fact, it does! SHGetSettings reports the true on/off state, even when GetDesktopItemOptions reports the opposite stateâ€"amazing! Evidently ApplyChanges updates the registry before broadcasting WM_SETTINGCHANGE, before updating its own internal state. It's the kind of thing that makes you want to laugh and weep at the same time.
      And now I can answer the question posed earlier: which is better for getting the on/off state of Active Desktop? It would appear the winner is SHGetSettings.

Q I am trying to #include a header file in my project's resource file. I added a line in my .rc file like this:

  #include "MyFile.h"
  

 

      This works fine, but every time I open the resources in Visual Studio®, it erases my line. I need to #include a third-party file that has ID symbols that I need to use for menu items.

Stan Geller
Boston

A Funny how that pesky IDE sometimes clobbers your code, isn't it? Well, never fear, there's always a way to circumvent the slings and arrows of outrageous software. When it opens your .rc file, App Studio (the old name for the Visual Studio resource editor) does a little preprocessing of its own. And when it saves your file, it doesn't so much save as regenerate it, using its infinite resource wisdom. In particular, App Studio likes to have one and only one header file through which all resource symbols are #included. Normally, it's called resource.h, but you can change the name by right-clicking on MyApp resources and selecting Resource Includes. When App Studio writes your resource file, it generates one and only one #include statement. Any extra #includes you typed manually are sent to the aforementioned ninth bitbucket.

Figure 8 Dialog Margins
Figure 8 Dialog Margins

      Is there anything you can do about it? Yes. You may have noticed that App Studio embeds its own special information in your resource file. For example, you've probably created a dialog with margins (see Figure 8) at least once in your life. Did you ever wonder where App Studio saves the margins? It's not in the DIALOG statement, which has no MARGINS option. No, App Studio saves the margin info in a special section:

  #ifdef APSTUDIO_INVOKED
  
GUIDELINES DESIGNINFO DISCARDABLE
BEGIN
IDD_MYDIALOG, DIALOG
BEGIN
LEFTMARGIN, 8
RIGHTMARGIN, 502
TOPMARGIN, 8
BOTTOMMARGIN, 273
END
END
#endif // APSTUDIO_INVOKED

 

      APSTUDIO_INVOKED is #defined when App Studio processes your .rc file, but not when the normal resource compiler compiles it. The resource compiler never sees GUIDELINES; only App Studio does. So why am I talking about GUIDELINES when you asked about #include files? Because the answer to your question lies with APSTUDIO_INVOKED. All you have to do is enclose your #include like so:

  #ifndef APSTUDIO_INVOKED // if NOT defined
  
#include "MyOtherHeader.h"
#endif

 

      App Studio ignores your #include since APSTUDIO_INVOKED is defined when it runs. But when RC compiles your file, APSTUDIO_INVOKED is not defined, so the #include happens. Moreoverâ€"and here's the key pointâ€"App Studio preserves your #include statement when it saves your .rc file. It realizes: "well, gosh, this stuff in this here #ifndef block is not for me, so I guess I better not erase it...." Inquiring minds might wonder why App Studio would ever think of erasing your code in the first placeâ€"but that's another story. Sayonara!
Got a question? Send questions and comments to cppqa@microsoft.com.**

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

From the May 2001 issue of MSDN Magazine