Wrapping a PropertySheet; how to handle callbacks? - c++

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?

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().

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.

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).

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.

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.

Related

C++ Winapi HWND Get element by Name

Is there a way I can get a HWND by it's property "name"? I know that every IDE has its own properties for HWND elements but those properties are applied to the HWND.
I'm not working in Visual Studio, this is just a case. I want to get HWNDs by Name in C++ without VS Libraries.
For example:
HWND button = GetHwndByName("button1"); //Example
Property "name" is button1
I'm going to assume you're either trying to access your GUI controls in code or some other program's GUI controls.
As some people have mentioned, the (name) property in the properties editor is just the variable name used for that control. Your screenshot shows Visual Studio editing a .net program. In the case of .net, the (name) field is the name of the class member of the window class that represents the control. So if (name) is button1 then Visual Studio might generate code like
// pseudo-C++/C#-like
class Form1 : public System.Windows.Forms.Form {
private:
System.Windows.Forms.Button *button1;
...
};
The idea here is that you would have event handlers as part of your Form1 class:
void Form1::onButton1Clicked(void)
{
this->button1->SetText("You clicked me!");
}
As such, the (name) is not an intrinsic property of the window from Windows's point of view.
I don't know what CA Plex's GUI editor looks like, but I would assume, given you said you were using C++, that it either
a) produces a class like the one I pasted above, in which case you would just use the (name) directly as members, or
b) produces a header file with each of those control names as global HWND variables
Either way, you can just use them directly from within your code. Perhaps have something like
void doToAllButtons(void (*f)(HWND, LPARAM), LPARAM lParam)
{
(*f)(button1, lParam);
(*f)(button2, lParam);
(*f)(button3, lParam);
}
and simply write an appropriate function to call via this one.
If you need to interface with another program and want to use its variable names, then you're out of luck. You'll need to find the windows you want some other way, such as with FindWindow().

Nested Dialog Procedures

I'm not sure how to call Dialog Procedures of certain classes and was looking for some help.
I'm aware for a normal Message Procedure you can do this in the MessageProc:
case WM_CREATE:
{
CREATESTRUCT* cs = (CREATESTRUCT*)_lParam;
pApplication = (CApplication*)cs->lpCreateParams;
return pApplication->MessageProc(_Msg, _wParam, _lParam);
}
which will allow you to create a Message Proc independent to the class.
However due to the fact I don't know exactly how the first two lines work (just the definition that they return the 'this' pointer of the application), I can't work out what to do to get my Dialog Procedures to do a similar thing
case WM_INITDIALOG:
{
//How can I get the pointer to the inspector that
//I want to call the dialog proc on?
return pInspector->DlgProc(_hWndDlg, _Msg, _wParam, _lParam);
}
Any help to both get the pointer to the inspector working and also clarify exactly what the other two lines are doing in WM_CREATE would be appreciated
When a window is created, it receives a WM_CREATE message with a pointer to a CREATESTRUCT structure, and in there is a pointer-sized userdata field (lpCreateParams). This value comes from the lpParam argument passed to the CreateWindowEx() function.
This is the general mechanism which lets you associate your own class or data structure with an instance of a window.
This pointer generally needs to be saved somewhere in order to use it later on. One common way of doing this is to store it in a window property:
case WM_CREATE:
{
CREATESTRUCT* cs = (CREATESTRUCT*)_lParam;
pApplication = (CApplication*)cs->lpCreateParams;
SetProp(hWnd, L"my.property", (HANDLE)pApplication);
}
Then to retrieve the value when handling other messages:
pApplication = (CApplication*)GetProp(hWnd, L"my.property");
Dialogs are not exactly like normal windows, so although a similar mechanism exists, it is implemented differently. When a dialog procedure receives the WM_INITDIALOG message, the lParam value is equivalent to the lpCreateParams value in a WM_CREATE message.
In order to save your pInspector value it would need to have been provided as the dwInitParam value when the dialog was created, but assuming that it was, you can handle this in a similar way:
case WM_INITDIALOG:
{
pInspector = (CInspector*)lParam;
SetProp(hWnd, L"my.property", (HANDLE)pInspector);
}
And to retrieve the value when handling other messages:
pInspector = (CInspector*)GetProp(hWnd, L"my.property");

What is the `uIdSubclass` in the third `SetWindowSubclass` Parameter?

According to: http://msdn.microsoft.com/en-us/library/windows/desktop/bb762102(v=vs.85).aspx
the SetWindowSubclass prototype is:
BOOL SetWindowSubclass(
__in HWND hWnd,
__in SUBCLASSPROC pfnSubclass,
__in UINT_PTR uIdSubclass,
__in DWORD_PTR dwRefData
);
Ok, I understand hWnd, pfnSubclass, and dwRefData.
What I can not find good information on is, what do I set uIdSubclass to?
MSDN says:
The subclass ID. This ID together with the subclass procedure uniquely identify
a subclass. To remove a subclass, pass the subclass procedure and this value to the
RemoveWindowSubclass function. This value is passed to the subclass procedure in the
uIdSubclass parameter.
Ok, understood, but still, where do I get this ID? Is it something I create or do I get it someplace? If it something I create, what should it look like?
I am doing this in C++ and Win32 API, nothing else.
Thanks.
Karl E. Peterson writes about the purpose of the uIdSubclass parameter in Visual Studio Magazine 07/16/2009 Subclassing the XP Way and provides an example named HookXP
Topics discussed in Karl E. Peterson's article:
Use uIdSubclass to store a this pointer [or HWND].
Use dwRefData to pass optional data, e.g. pointer to create data.
It is important to handle WM_NCDESTROY in pfnSubclass and call
RemoveWindowSubclass() passing uIdSubclass used in call to SetWindowSubclass().
Quotes from Karl E. Peterson's article
Each subclass is identified two pieces of data along with the window's hWnd -- those being a pointer to the new message-handling procedure and a unique ID that's up to you to generate. You may also select to pass one Long value along as an extra parameter to each callback, for whatever purpose you may desire. The thought that immediately struck me was to use an ObjPtr() as the unique ID for each subclass. What object? The one that will be handling the callback!
About the only undocumented caveat I've run into is that you must be sure to unhook your subclass before a window is destroyed. If you use my technique of embedding the handler in a form or class that's destroyed during the Form_Unload method, this should never be a problem. If you really don't want to take any chances, you can insert a simple branch in your handling routine:
Select Case uiMsg
Case WM_THIS
'
Case WM_THAT
'
Case WM_NCDESTROY
Call Unhook ' !!!
End Select
The Explorer Browser Search Sample also has a example of using uIdSubclass to store a pointer to this.
void CExplorerBrowserSearchApp::_OnInitializeDialog()
{
...
// Register the searchbox icon to receive hover and mouseclick events
SetWindowSubclass(GetDlgItem(_hdlg, IDC_SEARCHIMG), s_SearchIconProc, (UINT_PTR)this, 0);
...
}
Raymond Chen's blog "The Old New Thing" article Safer subclassing also mentions the importance handling WM_NCDESTROY in pfnSubclass for the purpose of calling RemoveWindowSubclass().
Quote from Raymond Chen's article
One gotcha that isn’t explained clearly in the documentation is that you must remove your window subclass before the window being subclassed is destroyed. This is typically done either by removing the subclass once your temporary need has passed, or if you are installing a permanent subclass, by inserting a call to RemoveWindowSubclass inside the subclass procedure itself:
case WM_NCDESTROY:
RemoveWindowSubclass(hwnd, thisfunctionname, uIdSubclass);
return DefSubclassProc(...);
As a side note the Microsoft SetWindowSubclass documentation has the caveat of SetWindowSubclass() being limited to a per thread basis.
Warning You cannot use the subclassing helper functions to subclass a window across threads.

Passing parameters to a window call in Windows

I'm new to programming against Windows calls, and I'm trying to figure out a way to pass a parameter to the lpfnWndProc function. I have the following code:
HWND hwnd;
WNDCLASS wc1 = {0};
wc1.lpszClassName = TEXT( "sample" );
wc1.hInstance = 0;
wc1.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc1.lpfnWndProc = DepthWndProc;
Note the line wc1.lpfnWndProc = DepthWndProc; Am I able to pass DepthWndProc a parameter? If so, what does the syntax look like?
Thanks!
You are assigning a function pointer here, not making a call. Thus no passing of arguments.
Having to store extra state with a HWND isn't unusual, a very common requirement for a C++ class wrapper around a window for example. You should keep a map<> to help you retrieve the wrapper object from the window handle value. Using SetWindowLongPtr() with GWLP_USERDATA is possible too but less ideal if you don't control the window creation.
You can call DepthWndProc directly and pass its parameters, but why on earth would you do that? That's not how windows programming works.
You are giving windows a function to call whenever it has a message to send to your window.

C++/MFC: Handling multiple CListCtrl's headers HDN_ITEMCLICK events

I'm coding an MFC application in which i have a dialog box with multiple CListCtrls in report view. I want one of them to be sortable.
So i handled the HDM_ITEMCLICK event, and everything works just fine .. Except that if i click on the headers of another CListCtrl, it does sort the OTHER CListCtrl, which does look kind of dumb.
This is apparently due to the fact that headers have an ID of 0, which make the entry in the message map look like this :
ON_NOTIFY(HDN_ITEMCLICK, 0, &Ccreationprogramme::OnHdnItemclickList5)
But since all the headers have an id of zero, apparently every header of my dialog sends the message.
Is there an easy way around this problem ?
EDIT: Maybe i wasn't clear, but i did check the values inside the NMHDR structure. The HwndFrom pointer is different depending on which header is clicked, which doesn't help me a lot since it's value is obviously different at each runtime. The idFrom value is 0, for the very reasons i explained above, because that's the id of every header. Thanks
EDIT2: The hwnd pointer values do also not correspond to the CListCtrl, probably because it's coming from a different object entirely.
Check the values of the NMHDR structure.
http://msdn.microsoft.com/en-us/library/bb775514%28VS.85%29.aspx
Ok i found a solution, though i find it a bit dirty but it works, so i'll post it for future reference.
You can get the Header through the GetHeaderCtrl member function of CListCtrl. You can then get it's handler thru m_hWnd. So all you got to do is to test if that handler is the same as the one in the NMHDR structure, so the code looks like this :
void Ccreationprogramme::OnHdnItemclickList5(NMHDR *pNMHDR, LRESULT *pResult)
{
if (pNMHDR->hwndFrom == LC_gen_schedules.GetHeaderCtrl()->mhWnd)
{
// Code goes here
}
*pResult = 0;
}
Thanks all for the help
The LPARAM passed to your message handler is actually a pointer to an NMHEADER structure, which contains an NMHDR structure, which in turn contains the HWND and control ID of the control which sent the message. You may be able to compare that to your list controls' HWNDs to determine which window's header control was clicked.
Alternatively, you could derive a class from CListCtrl and reflect the HDN_ITEMCLICK messages back to the list control. That way, each list control object handles its own header's notifications.