Does winapi supports something like Control.TabIndex? if not, how is this usually implemented? Do I have to process the key tab press from a WM_KEYDOWN message and set focus on the control accordingly or is there something native to do that? I have no code to show yet because I'm trying to figure out how begun to do that.
Tab order is based o z-order (order in which children are painted). Initially z-order is based on creation order, so controls are painted and tabbed in same order as creation order.
When creating controls this could be changed by reordering creation sequence (in code or *.rc file).
After creation use SetWindowPos( hwnd, other_hwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE ); to rearrange controls in z-order.
Related
I want a window which behaves identically to normal Overlapped window, except it does not have capton bar when maximized (to make more room for the client area).
I can remove WS_CAPTION|WS_SYSMENU from the window style.
However, I cannot find way to get window position and size right:
A normal window is maximized by expanding work area rectangle with border width. This makes border to "hang" outside. When I remove WS_CAPTION, the border is different (3 vs 4 pixels in my case) so I have to reposition the window somehow.
What I have tried:
First change style, then maximize: this does not maximize to work area, but to full screen instead. Looks like this is a feature of window manager that relies on WS_CAPTION style. Other than that, the border is "hanging" correctly.
First maximize, then change style, position and size:
I can't find API to get the maximized size and position. However, work area of closest monitor looks good to me.
I can't find API to expand window rect with appropriate "hanging border". AdjustWindowRectEx is almost there, however non-client area is not the same as border (obviously it also includes caption and menu). I also tried to do the math myself using GetSystemMetrics values, but it seems too unpredictable. The border could be SM_CXSIZEFRAME, or SM_CXSIZEFRAME+SM_CXPADDEDBORDER, or SM_CXFIXEDFRAME, and maybe this depends on OS version, theme and whatever.
Is it possible to do this in a robust, "official" way?
I put my own answer below but it is too hacked.
You are already removing the caption. If you don't want to show the borders, then remove the borders as well. Get ready to restore the border later. Find the desktop rectangle, and position the window in to that rectangle. The full screen window can have WS_OVERLAPPED or WS_POPUP flag with no caption and no borders. Example:
void switch_view()
{
RECT rc;
SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0);
DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE);
if(style & WS_CAPTION)
{
SetWindowLongPtr(hwnd, GWL_STYLE, WS_OVERLAPPED);
SetWindowLongPtr(hwnd, GWL_EXSTYLE, 0);
SetWindowPos(hwnd, NULL, 0, 0, rc.right, rc.bottom,
SWP_SHOWWINDOW | SWP_FRAMECHANGED);
}
else
{
SetWindowLongPtr(hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
}
}
This is what I get after many attempts.
The border problem is solved. As suggested by others, I remove border together with caption. Therefore no need to calculate it. There is also no need to calculate border for captioned window with border, because this is special-cased by window manager.
To obtain and maintain the right size, I use MonitorFromWindow and GetMonitorInfo. The position and size are applied together with style change (both ways) and also in WM_SIZE handler. Watching WM_SIZE allows to recover from external minimize-restore events, as well as TaskBar change etc.
For unknown reason, moving with Win+Arrow keys does not work. This issue only exists when the window is in maximized state and without caption. Using this workaround:
in WM_WINDOWPOSCHANGING set WS_CAPTION style. This allows window to move correctly. Afterwards, the WM_SIZE handler applies the right style/position/size again.
Approach that failed: use restored window rather than maximized. In this case there is too much going wrong in restore-move-maximize-restore lifecycle (either restore or maximize goes to wrong monitor).
Is it possible create MFC form that cold stay on the top of all applications on PC not allowing to do anything else without entering required information.
Win32 doesn't support system-modal dialogs any more, as far as I'm aware. This is a relic from 16 bit Windows versions.
You can try yourself with a MB_SYSTEMMODAL type MessageBox.
Closest thing would be to utilize a screen-sized window to display a dimmed desktop background while your dialog is shown. This simulates the behaviour of the user account control -- except that you still can switch tasks.
You can obtain something similar setting your window on "TopMost". You can do it on property sheet in design mode, or programmatically with this line:
SetWindowPos( pWnd->m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE );
Hope this will fit your needs.
I see in MSDN, it says:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680(v=vs.85).aspx
If the created window is a child window, its default position is at the bottom of the Z-order. If the created window is a top-level window, its default position is at the top of the Z-order (but beneath all topmost windows unless the created window is itself topmost).
However, another documentation says:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms632599(v=vs.85).aspx
When an application creates a window, the system puts it at the top of the z-order for windows of the same type
I tested it like this:
btn1 = ::CreateWindow(L"button", L"OK", WS_TABSTOP|BS_DEFPUSHBUTTON|WS_VISIBLE|WS_CHILD
, 10, 10, 50, 30, hWnd, (HMENU)51, hInst, NULL);
btn2 = ::CreateWindow(L"button", L"Cancel", WS_TABSTOP|WS_CHILD|BS_PUSHBUTTON|WS_VISIBLE
, 20, 20, 70, 30, hWnd, (HMENU)52, hInst, NULL);
I created two buttons in a window and they overlapped, I can see that the button created later is covering the first button created.
Is the first statement in MSDN contradictory to my testing?
The documentation is accurate. You are being tripped up by another problem, you allow the child windows to draw themselves across other child windows. So now the painting order matters.
You fix that by adding the WS_CLIPSIBLINGS style flag to your CreateWindowEx call. You'll now see that the OK button is on top. Fix:
btn1 = ::CreateWindow(L"button", L"OK",
WS_TABSTOP|BS_DEFPUSHBUTTON|WS_VISIBLE|WS_CHILD|WS_CLIPSIBLINGS,
10, 10, 50, 30, hWnd, (HMENU)51, hInst, NULL);
// etc, use it as well on other child windows
You should not rely to much on the how the child windows are displayed and which one is drawn last. If I run your sample code I get an OK button which is overlapped by the Cancel button. If I move the mouse over the buttons then the OK button comes into foreground and draws over the Cancel button.
I once had similar trouble with overlapping child controls. Then I found out that Microsoft says Overlapping Controls Are Not Supported by Windows.
BTW, if you really want to see the Z-order, then use GetTopWindow and GetNextWindow. Or the simpler way: run Microsoft Spy++.
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.
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.