Description
There are times when it might be useful to provide a user the ability to reorganize the layout of a dialog or window. This submission provides a number of MFC classes which, when compiled into an application, can enable a user to reorganize the layout of controls in the application's dialogs and windows.
Design
We want to give the user the ability to reorganize, at runtime, the layout of a dialog/window. We will accomplish this by using the window subclassing mechanism. Window subclassing essentially allows us to catch and process all of a window's messages before they are dispatched to the original window message handler(s). We will be using window subclassing to catch and process a window's mouse and paint messages. The intercepted mouse messages will be used to toggle the control/window in and out of the layout editing mode and to provide a means to actually edit the control's layout. While in the layout editing mode, the control/window will be highlighted in orange and will have six orange control points to allow the user to configure the control's layout. One control point will be used to move the control. Another control point will be used to size the control. The remaining four control points will be used to specify how the control should be repositioned and resized when its parent window is resized.
We want it to be fairly trivial to include the new layout editing mechanism into existing code. The simplest way to include the new mechanism is to add an object into the dialog or window and have the object "automatically" include all the controls in that dialog or window with one method call on the object - ideally in the OnInitDialog
or OnInitializeView
handlers. Once the call has been made to subclass the controls, it is up to the user to do the rest.
What good would it be allowing the user to customize a dialog or window layout if we had no way of persisting the user's layout? So, we will add methods to save and load the layout to and from an XML-formatted string. The developer will need to decide how and where the XML-formatted layouts will be stored.
Classes
CPositionWnd
CPositionWnd
is the primary class used to subclass and manipulate the layout of dialog or window controls. The CPositionWn
d class only exposes a few methods. Ideally, you could include a CPositionWnd
object in your dialog or window, and have it recursively subclass all of the controls/windows in the dialog or window, with a single call to SubclassWindowEx
. Layout persistence can be achieved with calls to LoadConfigFromString
and SaveConfigToString
. The methods of interest include:
void SubclassWindowEx(CWnd* pWindow = NULL, BOOL bIncludeChildren = TRUE)
- This method allows the developer to setup a window for customizable layouts.
void LoadConfigFromString(LPCSTR lpszConfig)
- This method allows the developer to load the layout for subclassed windows.
CString SaveConfigToString(void)
- This method allows the developer to save the layout for subclassed windows.
CPositionWndContainer
CPositionWndContainer
provides a more flexible way of configuring only specific controls/windows for layout editing. You can create the CPositionWndContainer
object and attach specific controls/windows to the object. It will then handle persistence and subclassed layouts for the developer. The methods of interest include:
BOOL Create(LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext = NULL)
- This method creates a window that acts sort-of like a container window. The developer can then attach the customizable controls/windows to the object. When this window is resized, it will update all of its attached windows.
void LoadConfigFromString(LPCSTR lpszConfig)
- This method allows the developer to load the layout for subclassed windows.
CString SaveConfigToString(void)
- This method allows the developer to save the layout for subclassed windows.
void AttachWindow(CWnd* pWnd, BOOL bIncludeChildren = TRUE)
- This method allows a developer to assign various controls/windows to the container.
CPositionWndXMLConfigParser
CPositionWndXMLConfigParser
is a worker class that aids CPositionWnd
and CPositionWndContainer
in the parsing of XML-formatted layouts. The developer should not really need to worry about this class, as it is used internally by CPositionWnd
and CPositionWndContainer
.
Note: The developer will need to include xmlfile.cpp/.h in their project in order to use the CPositionWndXMLConfigParser
class.
Example of Developer Usage
Utilizing the CPositionWnd
class is fairly simple. Add a CPositionWnd
object to your dialog or window class, and have the object subclass your dialog or window in the OnInitDialog
or OnInitializeView
method.
BOOL CMyTestDialog::OnInitDialog()
{
CDialog::OnInitDialog();
m_posWnd.SubclassWindowEx(this);
m_posWnd.LoadConfigFromString(m_strLayoutConfiguration);
int nWidth = m_posWnd.GetLayoutWidth();
int nHeight = m_posWnd.GetLayoutHeight();
SetWindowPos(NULL, 0, 0, nWidth, nHeight, SWP_NOMOVE | SWP_NOZORDER);
return TRUE;
}
Example of User Usage
A user can edit any subclassed control by holding the Ctrl key and clicking the right mouse button. Once in layout edit mode, the control will be highlighted in orange, and will have six orange control points.
The control point in the upper-left corner of the control is the move control point. If the user hovers over the move control point, the cursor will change to a move cursor. The user can left mouse button click and hold the control point to move the control to another location.
The control point in the lower-right corner of the control is the size control point. If the user hovers over the size control point, the cursor will change to a sizing cursor. The user can left mouse button click and hold the control point to change the size of the control.
The control points to the right and below the move control point dictate how the control should behave when its parent is resized. When checked-on, they essentially anchor/dock the top and/or left side(s) of the control to the location where the user placed the control - relative to the parent window's top and left sides.
The control points to the left and above the size control point dictate how the control should behave when its parent is resized. When checked-on, they essentially anchor/dock the bottom and/or right side(s) of the control to the location where the sized control is - relative to the parent window's bottom and right sides.
Using different combinations of the anchor/docking control points, the user is given a certain amount of control over how the control will reposition and resize with a change in the parent window's size.
The user can right mouse button click the control, while it is in layout edit mode, to get access to a menu that can aid the user in the layout. For example, some controls have child controls that obscure the parent control. Once you toggle the child control for layout editing, right mouse button click the control and use the "Select Parent" menu option to put the parent into layout editing mode.
Caveats
There are times when a control in layout mode might leave "tracers" behind when being moved. A repaint of the parent will clean-up the tracers, but it is an issue that needs to be addressed.
It is possible to move a control outside of the parent window's visible range and lose access to the move control point. Another issue that will need to be addressed.
Some controls may have borders, frames, scrollbars, etc., that throw-off the returned size of the control, and it can make it challenging to position the said controls.
The time required for becoming proficient at setting-up a layout might be long, depending on the user.