Introduction
Once again, a project I'm working on called for a feature (or characteristic, if you like that word better) which I'd never had to implement - centering the controls in a form view while maintaining their relative positions to one another.
To save time, my first stop was here at CodeProject to see if anyone else had done it before me. Imagine my surprise when I found nothing on this site, or even in MSDN about doing something like this. My search was not what I would call exhaustive, but I spent about 30 minutes looking for something that would serve my purposes. What did I find? Nada. Zero. Zilch. A big old-fashioned goose egg.
So here I am, forcing you to suffer through my bio once more so that I might spread some knowledge amongst those of you looking for code that performs this functionality. For the nit pickers, I even included the source code for a sample project.
How it works
Our project was a SDI kiosk-style application, in that it is full screen with no Titlebar, menu, toolbar, or status bar. It also hides the task bar. The user is forced from screen to screen with no deviation from his intended path. Essentially, each screen is a different CFormView
with a different set of controls on each page. One of the things we're not absolutely sure of is what screen resolution the workstations will be configured for.
To address this concern, we simply created the dialog templates used by the form views to be whatever size we needed them to be, and then wrote some code to center the whole shebang on the view when it's displayed. In order to do that, we needed to be able to move each control, regardless of how many there were or what their ID's were (yes, each form had different controls, so this particular aspect was a must-do).
Fortunately, the Windows API has functions for doing exactly what we need to do. What we need to do is enumerate through the child Windows of the form view.
The cool part is that once you have the control's hWnd
, you can revert back to MFC and manipulate the controls themselves (including their data). Here's the meat of the code, and is all you need to enumerate through all of the view's (or dialog's) controls:
HWND hwnd = ::GetTopWindow(this->GetSafeHwnd());
while (hwnd)
{
hwnd = ::GetNextWindow(hwnd, GW_HWNDNEXT);
}
The sample app provided with this article does a couple of things inside this while
loop - turns the anchor frame on and off, changes the text in a static string, and re-centers the controls each time the window is re-sized.
HWND hwnd = ::GetTopWindow(this->GetSafeHwnd());
while (hwnd)
{
UINT nID = ::GetDlgCtrlID(hwnd);
if (nID == nAnchorID)
{
GetDlgItem(nID)->ShowWindow(GetDlgItem(nID)->
IsWindowVisible()?SW_HIDE:SW_SHOW);
}
else if (nID == IDC_UPLOW_CASE)
{
CString sText;
GetDlgItem(nID)->GetWindowText(sText);
sText = (sText == "all lowercase") ?
"ALL UPPERCASE" : "all lowercase";
SetDlgItemText(nID, (LPCTSTR)sText);
}
CRect rect;
::GetWindowRect(hwnd, &rect);
ScreenToClient(&rect);
::MoveWindow(hwnd, rect.left + nXDiff,
rect.top + nYDiff, rect.Width(),
rect.Height(), TRUE);
hwnd = ::GetNextWindow(hwnd, GW_HWNDNEXT);
}
A tangent - the centering code
While the centering code is not supposed to be the real focus of this article, I figured I'd describe it briefly since I'm already typing like a madman. The first thing I did was to create an intentionally small template, and then create a static frame around the edge of the template. This control is my anchor control, and is what the formview will use as a reference for relocating all of the other controls.
The formview uses the anchor frame to calculate how far the frame is moved in order to keep itself centered as the window is re-sized. This is essential for the relocation function to work. Once the anchor frame's new x/y position is determined, I enumerate the controls of the form view and position each one at the offset calculated earlier.
The result is that the controls will be centered as long as the window is larger than the original template. If the view is smaller than the template, scrollbars will be displayed, and the template will be positioned in such a way as to be oriented in the top/left corner of the parent window.
Interesting findings and caveats
- It's perfectly logical to assume that triggering the relocation code would best be implemented from within
OnSize()
. However, when I did that, I found that even after waiting for the hWnd
to be valid, I was getting a CRect
that was essentially a null rectangle (CRect(0,0,0,0)
). I found that while initializing a MFC SDI app, the view's OnSize()
method is called four times!
The fourth time was after the call to OnInitialUpdate()
, so I put a bool variable (m_bCanBeRelocated
, and set to false
in the constructor) in the class, and then set it to true
at the end of OnInitialUpdate()
. At that point, you can be assured that you'll get valid rectangle info when you call GetClientRect()
.
- When the view was sized smaller than the template, the scrollbars would be displayed as expected. However, if you scroll one of the scrollbars, and then resize again (while keeping the view smaller than the template), the controls would be incorrectly positioned so that they were partially off the left and/or top sides of the view.
To resolve this issue, I simply called SetScrollPos()
to position the scroller at 0,0. I know it's a hack work-around, but it resolved the issue and this article isn't really about the centering code.
Final words
If you're going to vote this article, leave your politics at home and be a grown-up and vote the article on it's own merit. It's not fair to me or others if you pull some puerile voting bullcrap simply because you don't agree with my political views. If you want to talk politics, we can take up as much room as we need in the soapbox forum here on CodeProject.
I've been paid as a programmer since 1982 with experience in Pascal, and C++ (both self-taught), and began writing Windows programs in 1991 using Visual C++ and MFC. In the 2nd half of 2007, I started writing C# Windows Forms and ASP.Net applications, and have since done WPF, Silverlight, WCF, web services, and Windows services.
My weakest point is that my moments of clarity are too brief to hold a meaningful conversation that requires more than 30 seconds to complete. Thankfully, grunts of agreement are all that is required to conduct most discussions without committing to any particular belief system.