views:

180

answers:

5

I'm writing an (unmanaged) C++ class to wrap the Windows PropertySheet. Essentially, something like this:

class PropSheet {
    PROPSHEETHEADER d_header;
    public:
     PropSheet(/* parameters */);
     INT_PTR show();
    private:
     static int CALLBACK *propSheetProc(HWND hwnd, UINT msg, LPARAM lParam);
};

The constructor just initializes the d_header member:

PropSheet::PropSheet(/* parameters */) {
    d_header.dwSize = sizeof(PROPSHEETHEADER);
    d_header.dwFlags = PSH_USECALLBACK;
    // ...
    d_header.pfnCallback = &propSheetProc;
    // ...
}

After which I can show it, modally, with:

INT_PTR PropSheet::show() {
    return PropertySheet(&d_header);
}

Now the problem is, because the callback is static, that it cannot access the wrapper class. If this were a normal window, with a WindowProc instead of a PropSheetProc, I could attach some extra data to the window using cbWndExtra in WNDCLASS, in which I could store a pointer back to the wrapper, like in this article. But property sheets do not offer this functionality.

Furthermore, because the property sheet is shown modally, I can execute no code between the creation and destruction of the actual window, except when that code is executed through the callback or one of the sheets's window procedures.

The best solution I've come up with so far is to, right before showing the property sheet, store a pointer to the wrapper class inside a global variable. But this assumes that I'll only be showing one property sheet at a time, and is quite ugly anyway.

Does anyone have a better idea how to work around this?

+1  A: 

As you are showing the property sheet modally, you should be able to use the parent window (i.e. its handle) of the property sheet to map to an instance, using ::GetParent() on the hwndDlg parameter of PropSheetProc().

Georg Fritzsche
Alas, it has no parent. Also, the same problem would occur if the parent spawned multiple property sheets. (Rare, I know, but it could happen.)
Thomas
Or are you suggesting that I cast a `PropSheet*` to a `HWND`? Won't that make Windows's head explode if it tries to access the `HWND`?
Thomas
Am i misunderstanding something here? You set (or should set) a parent window on `PROPSHEETHEADER` (i.e. `hwndParent`). In `PropSheetProc()` you get the handle of the dialog as the first parameter on which you can call `GetParent()`.
Georg Fritzsche
Also `PropSheetPageProc()` gets a `LPPROPSHEETPAGE` that you can use for identification.
Georg Fritzsche
... and i don't see how a window can spawn multiple modal dialogs. If you're creating a new modal dialog from a property sheet, set the parent handle to the dialogs handle.
Georg Fritzsche
I admit it's far-fetched. But maybe I'll just create an invisible dummy window specifically as the parent of the property sheet. Clunky, but at least it's fail-safe.
Thomas
A: 

Awesome, yet another Win32 API that uses callbacks without a user-defined context parameter. It is not the only one, alas. e.g. CreateWindow is bad (it gives you user-defined context, but that context isn't available for the first few window messages), SetWindowsHookEx is even worse (no context at all).

The only "solution" that is general-purpose and effective is to emit a small piece of executable code with a 'this' pointer hardcoded. Something like this: http://episteme.arstechnica.com/eve/forums/a/tpc/f/6330927813/m/848000817831?r=848000817831#848000817831

It's horrible.

DrPizza
CreateWindow() has an lParam parameter that allows you to pass a user-defined value to the new window. That value is available in the CREATESTRUCT structure of the WM_CREATE message, which you can then copy into the HWND, such as with SetWindowLong(GWL_USERDATA) or SetProp(), for use in later messages.
Remy Lebeau - TeamB
That would be fantastic if WM_CREATE was the first message you got. It isn't.
DrPizza
Wow, that is one hack I never thought of. But I dare not go down that road.
Thomas
ATL uses a similar technique (or at least, it used to, it's possible it doesn't any longer) for setting per-object WndProcs, so it's not unprecedented. MFC does something else altogether that might be worth investigating, but I can't remember off-hand what.Another tack; I think .NET can synthesize suitable code for you, so if you define an appropriate delegate type and use managed code, that'd work. It's a rather roundabout way of generating executable code on-the-fly, mind you!
DrPizza
Borland's VCL also uses such executable proxies to handle 'this' pointers for Win32 window procedures.
Remy Lebeau - TeamB
@DrPizza - what window message comes before WM_CREATE that one would need access to the lParam value?
Remy Lebeau - TeamB
@DrPizza - the WM_INITDIALOG message has access to the PROPSHEETPAGE structure for each page, which in turn has a user-defined lParam. Since a property sheet's pages have to be created before the propety sheet itself is, and most operations inside a sheet are manages by each individual page, I don't see why the page callbacks cannot be used to handle Thomas's situation.
Remy Lebeau - TeamB
@Remy: The one that comes to mind is WM_GETMINMAXINFO (there are one or two more). Since typically you want _all_ such messages to be handled by your class instance, it's quite annoying.
DrPizza
A: 

The PROPSHEETPAGE structure has an lParam field available for callbacks. In your PROPSHEETHEADER, you can include the PSH_PROPSHEETPAGE flag to pass an array of PROPSHEETPAGE items describing your pages, or omit the flag to pass an array of preallocated HPROPSHEETPAGE handles instead (which means using CreatePropertySheetPage(), and thus using PROPSHEETPAGE anyway).

Remy Lebeau - TeamB
According to the documentation for `PropSheetProc()`, `lParam` contains 0 for e.g. `PSCB_BUTTONPRESSED`.
Georg Fritzsche
Yes, the window procedures for the individual pages are not a problem. It's the callback for the entire property sheet that is causing my headaches.
Thomas
What do you need to do in the sheet callback that can't be handled by the individual page callbacks?
Remy Lebeau - TeamB
@gf - yes, because the lParam in that callback is not a user-defined value. I was referring to the PropSheetPageProc() callback instead, which does provide access to a user-defined lParam.
Remy Lebeau - TeamB
A: 

You've already admitted "I can execute no code between the creation and destruction of the actual window". It seems that a global variable wouldn't be a terrible hack.

Mark Ransom
A: 

I've found another option: using SetProp to add a property that stores the pointer to the wrapper. Only requires the global variable once, to be able call SetProp from the property sheet callback.

Thomas