开发者

property sheet in dialog using WINAPI example (without using MFC)

Can anyone point me at an WINAPI example of embedding a property sheet in a 开发者_如何学Godialog box using WINAPI (not MFC)?


For the record, here are some of the things I learned while investigating this question. I obtained most/all of this from asking questions here or by searching the web. Thanks to all for your help!

I have written a class to encapsulate everything I've discovered; if interested in getting a copy send me an email mdorl@wisc.edu.

Pass the HWND of the parent window for your property sheet in .hwndParent of the PROPSHEETHEADER when creating the property sheet with the PropertySheet page function.

I used a picture control on the dialog as the parent of the property sheet so that's the HWND I used as the hwndParent.

Change the style of the property sheet using the call back pfnCallback field in the header, don't forget to include PSH_USECALLBACK in dwFlags. See PropSheetProc Function in the MSDN. In the PSCB_PRECREATE handler of the callback, adjust the window style of the property sheet by removing WS_POPUP and adding WS_CHILD. I did not want a border or title bar on the property sheet so I also removed WS_CAPTION, WS_SYSEMNU, & DS_MODALFRAME

LONG L = ((LPDLGTEMPLATE)lParam)->style;
L &= ~WS_POPUP; 
L &= ~WS_CAPTION;       // gets rid of title bar
L &= ~WS_SYSMENU;       
L &= ~DS_MODALFRAME;        // gets rid of border
L |=  WS_CHILD;         // 40000000
((LPDLGTEMPLATE)lParam)->style = L;

Using a child window instead of a popup window neatly solves some problems such as moving the property sheet when the parent moves, keeping the blue/gray state of the parent window properly colored, and clipping issues. I.e. you don't have to worry about these things.

If you stopped here, you'd face another problem. The dialog support code in WIN32 would get in a cpu loop trying to find the property sheet and controls in its Windows. See

What is WS_EX_CONTROLPARENT and DS_CONTROL For?

for a pretty good explanation of this problem. The solution is to add the WS_EX_CONTROLPARENT extended style to the property sheet. You can find information on this on the web by searching for WS_EX_CONTROLPARENT. What you won't find caused me much grief. If you use a control on the dialog as the parent, you must also set its WS_EX_CONTROLPARENT.

I do not know what the difference between WS_EX_CONTROLPARENT and DS_CONTROL is nor do I know if you could substitute DS_CONTROL for WS_EX_CONTROLPARENT. From the web I gather that DS_CONTROL has something to do with tabs. My test app works the same with respect to tabs with or without DS_CONTROL.

I took care of the WS_EX_CONTROLPARENT business after calling the PropertySheet function to create the property sheet. I do not know if you could do this in the call back routine or not. I suppose you could use GetParent in the call back to get the HWND of the control.

LONG S;

S = GetWindowLong (hwndPS, GWL_EXSTYLE) | WS_EX_CONTROLPARENT;
SetWindowLong (hwndPS, GWL_EXSTYLE, S);

S = GetWindowLong (hwndPS_Area, GWL_EXSTYLE) | WS_EX_CONTROLPARENT;
SetWindowLong (hwndPS_Area, GWL_EXSTYLE, S);

After creating the property sheet with the PropertySheet function, you have to position it properly:

SetWindowPos(hwndPS, HWND_TOP, 2, 2, -1, -1,
             SWP_NOSIZE | SWP_NOACTIVATE); 

I allowed a couple of pixels for the border.

If you want to get rid of all the property sheet buttons and the space they take:

RECT rectWnd;
RECT rectButton;

GetWindowRect(hwndPS, &rectWnd);
HWND hWnd = ::GetDlgItem(hwndPS, IDOK);

if(!hWnd)
{
    DebugBreak();
}

GetWindowRect(hWnd, &rectButton);

SetWindowPos (hwndPS, NULL, 0, 0,
          rectWnd.right - rectWnd.left, rectButton.top - rectWnd.top,
          SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);

Individual buttons can be eliminated:

hwndOk = GetDlgItem (hwndPS,IDOK);              // Hide the OK button
ShowWindow (hwndOk, SW_HIDE);
EnableWindow (hwndOk, FALSE);

You can get rid of the APPLY button using the PSH_NOAPPLYNOW dwFlags in the PROPERTYSHEETHEADER when you create the property sheet.

The property sheet pages will NOT be created until the user first activates them. I wanted them all to be created when the property sheet was created.

unsigned int iP;
for (iP=0; iP<Header.nPages; iP++)
{
    if (iP != Header.nStartPage)
        SendMessage (hwndPS, PSM_SETCURSEL,iP,NULL);
}
SendMessage (hwndPS, PSM_SETCURSEL,Header.nStartPage,NULL);

There's a PROPSHEETPAGE flag that does the same thing (from the MSDN)

PSP_PREMATURE Version 4.71. Causes the page to be created when the property sheet is created. If this flag is not specified, the page will not be created until it is selected the first time.

But I was worried about the Version 4.71 business so I did it myself.

You can change the text on any of the buttons:

SetDlgItemText (hwndPS,ButtonID,Text);

where ButtonID is one of IDOK IDCANCEL IDHELP IDAPPLYNOW

IDAPPLYNOW is undefined on my system so

#define IDAPPLYNOW     0x3021   

Here's how I intend to use my property sheet. I'm going to put all the initialization and termination code in the dialog containing the property sheet and eliminate all the property sheet buttons. I can set all the initial values there and retrieve all the final values there when the user pushes some DO IT button. I notice that the act of setting, for example, a text box causes the containing page dialog to get a WM_COMMAND/EN_CHANGE. If you use this message to enable the APPLY button, you may need to disable the change flag for the page after setting any list boxes. I solved this problem by clearing all the changed flags after setting the initial value. (I suspect the property sheet clears the changed flag after all the INITDIALOG messages have been sent. In any case APPLY is not enabled if the text boxes are set in the page INITDIALOG handlers.) If I find an error I will select that property sheet page and put some red text in the parent dialog describing the error. The only thing the property sheet dialog procedures need to do is manipulate their controls.

One of the things that puzzles me is how to determine if all pages have returned PSNRET_NOERROR from their WS_NOTIFY/PSN_APPLY messages. I've had some success counting the number of successive PSNRET_NOERROR replies setting the count to zero when a PSNRET_INVALID is reported or a PSN_KILLACTIVE is received. If the count reaches the number of pages in the property sheet header, I assume all pages have returned PSNRET_NOERROR. But I worry about things like disabled and invisible pages.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜