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

Can anyone point me at an WINAPI example of embedding a property sheet in a dialog 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.

Related

MFC: Freezing on dialog SetParent()

Note: Using SetParent freeze the parent window exists, but does NOT seem to be related to this problem as its in an entirely different framework, in a different language, and seems to be an issue with the message pump (though the message pump here could be the problem, I dont think any solution directly helps the problem I am facing here)
I am trying to create a dialog in MFC and attaching it to a parent window as a modeless dialog. My first attempt at this looks like the following:
// Add window to the segment dialog vector
m_segmentDialogs.emplace_back(std::make_unique<DlgSegmentDatum>(this));
const int tab_number = m_segmentDialogs.size() - 1;
std::string tab_text = "Segment " + std::to_string(tab_number);
m_tabSegments.InsertItem(tab_number, tab_text.c_str());
// Initialize the new dialog
auto& dlg = m_segmentDialogs.back();
dlg->Create(IDD_DIALOG_SEGMENT_DATUM, this);
CRect rc_client, rc_window;
m_tabSegments.GetClientRect(&rc_client);
m_tabSegments.AdjustRect(FALSE, &rc_client);
m_tabSegments.GetWindowRect(&rc_window);
ScreenToClient(rc_window);
rc_client.OffsetRect(rc_window.left, rc_window.top);
dlg->MoveWindow(&rc_client);
displaySegmentTab(tab_number);
This results in the child dialog spawning in the top left corner of the my screen. I assumed that this was because the child dialog didn't associate itself with the parent for some reason. To fix this, I updated the following code segment.
// Initialize the new dialog
auto& dlg = m_segmentDialogs.back();
dlg->Create(IDD_DIALOG_SEGMENT_DATUM, this);
dlg->SetParent(this);
This positions the dialog correctly, but immediately freezes the program.
When you create Dialog resource, it has WS_POPUP style by default. To make it a child of another window, it has to be WS_CHILD.
You can either fix it in your resource file (easy), or, if you use that template elsewhere as a modal dialog, modify its style at run time using ModifyStyle

Get ListBox handle from Window handle

I am incredibly confused by the whole "window" verses "listbox" thing in the WIN32 API. I am simply trying to create a window as a "listbox" and add elements to it. My end goal will be a listbox similar to the on here: http://msdn.microsoft.com/en-us/library/windows/desktop/hh298365%28v=vs.85%29.aspx
I start by create a window with a listbox like so:
hDlg = CreateWindowExA(
WS_EX_CLIENTEDGE,
"ListBox",
"The title of my window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
NULL, NULL, hInstance, NULL);
But from what I understand, hDlg now points to the entire window and not the listbox.
Ideally I want to be able to add items to the listbox similar to this:
int pos = (int)SendMessage(hwndList, LB_ADDSTRING, 0,
(LPARAM) "Test Item1");
However I cannot get the handle of the list in the same way as the tutorial because they use this line:
HWND hwndList = GetDlgItem(hDlg, IDC_LISTBOX_EXAMPLE);
but IDC_LISTBOX_EXAMPLE throws a compilation error because it isn't included anywhere. And for the life of me, I cannot google a correct result for the second parameter int nIDDlgItem.
Can someone please explain to me how I can find the value GetDlgItem() or otherwise find a handle to my listbox from CreateWindowExA()?
The problem is that you're using the predefined ListBox window class to create a top-level window.
hDlg = CreateWindowExA(WS_EX_CLIENTEDGE, "ListBox", ...
This is creating a listbox control. The second parameter to CreateWindowEx specifies the window class - this tells the system what type of window you want to create. Since you're passing "ListBox" for that value, it will be creating an instance of the ListBox class.
The trouble is that you're creating that window to be a top-level window. WS_OVERLAPPEDWINDOW is a window style used for top-level windows (i.e. the one titled "List Box Example" in the above screenshot). Child windows, such as the listbox control, need to have the WS_CHILD style set.
What you really need to do is use RegisterClass to register your own window class for the top-level window. You would use this class name when calling CreateWindowEx to create the main window, and then create the various controls as children of that window.

oddity with edit controls in property sheet

I have a property sheet with several pages. Most of the pages have one or more edit controls.
Most controls are initialized not from the page dialogs but from the dialog that created the property sheet; some however are initialized in the page dialogs and they behave the same.
Everything starts out fine. One can move between the pages. None of the controls have the input focus.
If one clicks on one of the edit controls in a property sheet page establishing input focus one can modify the control. Again all seems in order.
If one then moves to a different property page, the first edit control in that page gets the input focus AND all the text in that control gets selected! This behavior applies to all the pages except one having an edit control with read only style. After that one can move back to other pages and the initial nothing selected no input focus behavior is restored.
All of the pages handle the PSN_QUERYINITIALFOCUS notification and return zero through the SetWindowLong mechanism.
Is this expected behavior?
And why isn't some control given focus initially?
My primary interest here is to somehow kill the selection. I have tried killing the selection with EM_SETSEL in the PSN_SETACTIVE notification to no avail.
The MSDN says the following under PSN_QUERYINITIALFOCUS "Otherwise, return zero and focus will go to the default control." How do I go about setting a control as default?
I find the the actions described above bizarre! I would still like to know
if they are normal.
why no control receives the focus initially.
I was able to kill the selection by adding code to the property sheet pages to handle the WM_COMMAND/EN_SETFOCUS message for any edit controls. I do not know if other controls
send EN_SETFOCUS messages.
case EN_SETFOCUS:
{
char cn[16];
HWND H = (HWND) lParam;
GetClassName (H, cn, 15);
if (strcmp (cn,"Edit") == 0)
{
SendMessage (H, EM_SETSEL, -1, 0);
}
return true;
}
I presume it would be possible to save any selection in an EN_KILLFOCUS handler and restore it
in the EN_SETFOCUS handler but doing so for an unknown number of controls would be tedious.

The MFC ComboBox dropdown button is missing after using SetDroppedWidth

This is an Windows MFC programming issue.
I have a derived CComboBox which implement its own item draw and measurement. I did a little enhancement that the drop down list width is adjustable based on the list content.
I use SetDroppedWidth in OnDropDown message handler to do it.
After I insert strings and click the combo box, the selected string content seized all static/edit area, and drop down button is missing.
I could see the width of the item, which has item ID -1, is changed/reset to the new dropped width in ItemDraw method. I don't think this is the right behavior. I want the new dropped width only take effect on list items, item ID of which is not less than 0.
Any idea?
Try to use this.
In your OnDropDown handler Call CComboBox::GetComboBoxInfo. It will return you the pointer of COMBOBOXINFO structure. This structure among the others contains the HWND of the dropdown listbox hwndList. Use MoveWindow() API on this hwndList directly and see if it works
If that does not work, try posting the CB_SETDROPPEDWIDTH message. As you know, SetDroppedWidth is just a wrapper for CB_SETDROPPEDWIDTH message. It is implemented as a SendMessage call. Try this one:
::PostMessage(m_myComboBox.GetSafeHwnd(), CB_SETDROPPEDWIDTH, nWidth, 0);

Making TAB key work on Windows dialog

I'm working on a Windows project with a simple dialog created with CreateWindowEx() it contains multiple panes loaded with CreateDialog() to load the layout from a resource file. On the child panes there are a number of controls including text boxes and buttons which I would like to use TAB to navigate around but all I get is the Windows 'bing' telling me that the key does not do anything. My message loop looks like this:
while( PeekMessage(&msg, 0, 0, 0, PM_REMOVE) )
{
if( !IsDialogMessage(0, &msg) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
And each control window has WS_TABSTOP set in the style as well as the owner pane having WS_EX_CONTROLPARENT set.
Is there anything else I need to do to make the tab key work?
Thanks,
J
Try this:
http://support.microsoft.com/kb/71450 (How To Use One IsDialogMessage() Call for Many Modeless Dialogs)
You panes are modeless dialogs, and IsDialogMessage is responsible for handling Tab keys. I hope that this article exactly matches your case.
The WS_TABSTOP Style
The WS_TABSTOP style specifies the controls to which the user can move by pressing the TAB key or SHIFT+TAB keys.
When the user presses TAB or SHIFT+TAB, the system first determines whether these keys are processed by the control that currently has the input focus. It sends the control a WM_GETDLGCODE message, and if the control returns DLGC_WANTTAB, the system passes the keys to the control. Otherwise, the system uses the GetNextDlgTabItem function to locate the next control that is visible, not disabled, and that has the WS_TABSTOP style. The search starts with the control currently having the input focus and proceeds in the order in which the controls were createdthat is, the order in which they are defined in the dialog box template. When the system locates a control having the required characteristics, the system moves the input focus to it.
If the search for the next control with the WS_TABSTOP style encounters a window with the WS_EX_CONTROLPARENT style, the system recursively searches the window's children.
An application can also use GetNextDlgTabItem to locate controls having the WS_TABSTOP style. The function retrieves the window handle of the next or previous control having the WS_TABSTOP style without moving the input focus.
Source: MSDN.
if( !IsDialogMessage(0, &msg) )
The first argument should not be NULL, it must be the handle of a dialog. Painful here of course.

Resources