分类: C/C++
2008-08-28 12:48:31
This is not an official release. The code is sufficiently stable to be used, but the documentation is incomplete and there are some minor issues to be solved yet. This article is also not completely synchronized with the current code. Please see the end of the article for details about this release.
This class library is an attempt to make the development of resizable windows a little easier for the MFC developer and much more pleasant for the user. A resizable window in this context is a window, not necessarily top-level, that once resized either by the user or the developer, is able to automatically resize and reposition its child controls appropriately.
To make a resizable window you can use this set of "low level" classes:
CResizableGrip Implements a size grip for use in top-level resizable windows CResizableMinMax Implements size constraints and maximized window position CResizableLayout Implements a layout manager to handle child windows resizing and repositioning CResizableState
CResizableWndState
CResizableSheetStateProvide basic save/restore methods and implementation for generic windows and property sheets or wizard dialogs
For the most common MFC classes you can simply replace standard MFC classes with their "resizable" counterparts:
Uses CResizableGrip
,CResizableMinMax
,CResizableLayout
,CResizableState
Uses CResizableLayout
Uses CResizableLayout
,CResizableGrip
,CResizableMinMax
,CResizableState
Uses CResizableLayout
Uses CResizableLayout
,CResizableGrip
,CResizableMinMax
,CResizableState
Uses CResizableLayout
,CResizableGrip
,CResizableMinMax
,CResizableState
Uses CResizableMinMax
,CResizableState
Uses CResizableMinMax
,CResizableState
Uses CResizableMinMax
,CResizableState
The main reason for this choice is to ease and speed up the development of new types of resizable windows, possibly extending my small set, and to avoid the repeated code of the first versions.
I also unified all the classes in a library project that you can easily add to your workspace, in order to use resizable windows. Obviously you can still add just the files you need to your project, but using the library you have a single place to update when new releases come.
If you already have version 1.1 or later, you need only to replace the old files with the new ones and recompile. Some minor incompatibilities should arise, due to changed implementation, please refer to the updated documentation and to the comments in the source code.
To use the class library perform the following steps:
ResizableLib
directory in a place of your choice. I suggest to use the same directory where you create your Projects (e.g. "C:\MyProjects
") or your common path for 3rd-party libraries
Now you are ready to start using the library or to rebuild your project if updating from the previous versions.
Note that this description will probably change for the next releases, because I'd like to distribute the library as standalone compilable project, not to be included in the workspace.
You just have to add the necessary files to your project, paying attention to the class dependencies. For example, if you want to use the resizable dialog class you also have to include the low level classes.
There are some preprocessor directives in the library's "stdafx.h" include file to cope with the various Platform SDK versions of the header files and MFC (6.0). You may need to copy those lines to your project's "stdafx.h" file to make things work properly.
Each archive contains one directory with the VC++ project and workspace. Since each demo's workspace has a reference to the ResizableLib project, you should place all the extracted directories in the same parent directory, so the IDE won't complain about missing files.
A good example of use for the "low level" classes is the CResizableDialog class. However, for the lazy programmer, here is a small guide.
class CMyResizableWindow : public CBaseWnd, public CResizableLayout
GetResizableWnd
pure virtual function this way virtual CWnd* GetResizableWnd() { return this; };
OnSize
message handler to re-arrange child windows void CMyResizableWindow::OnSize(UINT nType, int cx, int cy) { CBaseWnd::OnSize(nType, cx, cy); // ... ArrangeLayout(); }
OnEraseBkgnd
message handler to reduce flickering during resize operations BOOL CMyResizableWindow::OnEraseBkgnd(CDC* pDC)
{
EraseBackground(pDC);
return TRUE;
}
OnDestroy
message handler to clean up the layout if you want to use the MFC object to create the associated window multiple times (because the layout is still valid until object's destruction) void CMyResizableWindow::OnDestroy()
{
RemoveAllAnchors();
CBaseWnd::OnDestroy();
}
AddAnchor
or AddAnchorCallback
to set the child windows' layout in one of the initialization functions, such as OnInitDialog
or OnInitialUpdate
. class CMyResizableWindow : public CBaseWnd, public CResizableMinMax
OnGetMinMaxInfo
message handler to provide min/max information void CMyResizableWindow::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
{
MinMaxInfo(lpMMI);
}
class CMyResizableWindow : public CBaseWnd, public CResizableGrip
GetResizableWnd
pure virtual function this way virtual CWnd* GetResizableWnd() { return this; };
OnCreate
message handler to create the grip int CMyResizableWindow::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CBaseWnd::OnCreate(lpCreateStruct) == -1) return -1; // ... if (!CreateSizeGrip()) return -1; return 0; }
OnSize
message handler to update the grip position void CMyResizableWindow::OnSize(UINT nType, int cx, int cy) { CBaseWnd::OnSize(nType, cx, cy); // ... UpdateSizeGrip(); }
class CMyResizableWindow : public CBaseWnd, public CResizableState
GetResizableWnd
pure virtual function this way virtual CWnd* GetResizableWnd() { return this; };
LoadWindowRect
and SaveWindowRect
void AddAnchor(HWND hWnd, CSize sizeTypeTL, CSize sizeTypeBR = NOANCHOR) void AddAnchor(UINT nID, CSize sizeTypeTL, CSize sizeTypeBR = NOANCHOR)
Adds a child window to the layout list and set the anchor type for the top-left and bottom-right corners of the control. During a resizing operation the control's corners are kept at fixed distance from the dialog point you specify: argument's cx
member is the horizontal position, while cy
is vertical, in percentage.
If you have overlapping controls, you should order calls to this function from the outer controls to the inner ones, to let the clipping routines to work correctly.
A set of useful constant values, provided also for compatibility, is defined:
const CSize NOANCHOR(-1,-1), TOP_LEFT(0,0), TOP_CENTER(50,0), TOP_RIGHT(100,0), MIDDLE_LEFT(0,50), MIDDLE_CENTER(50,50), MIDDLE_RIGHT(100,50), BOTTOM_LEFT(0,100), BOTTOM_CENTER(50,100), BOTTOM_RIGHT(100,100);
NOANCHOR
for top-left corner is not allowed, so it will generate an 'assertion failed' in the Debug version. void AddAnchorCallback(UINT nCallbackID)
Adds a placeholder to the layout with the given callback ID. This is useful when the control you want to add, or its "anchorage", can change at run-time.
ArrangeLayoutCallback
to provide layout information. BOOL RemoveAnchor(HWND hWnd) BOOL RemoveAnchor(UIND nID)
There is not a corresponding function for callback items.
void RemoveAllAnchors()
OnDestroy
. BOOL GetAnchorPosition(HWND hWnd, const CRect &rectParent, CRect &rectChild, UINT* lpFlags = NULL) BOOL GetAnchorPosition(UINT nID, const CRect &rectParent, CRect &rectChild, UINT* lpFlags = NULL)
SetWindowPos
. The return value is TRUE
if the first argument identifies an anchored control, FALSE
otherwise. void ArrangeLayout()
Adjusts the size and position of the child windows you added to the layout list with AddAnchor
according to the size of the parent window. Usually called in your OnSize
handler.
virtual BOOL ArrangeLayoutCallback(LayoutInfo& layout)
Override this function to provide layout information for a given callback ID. When the function is called, the layout
structure contains the callback ID for which layout information is requested. You have to fill in the rest of the structure. The return value is TRUE
if the structure contains valid information, FALSE
otherwise.
nCallbackID
structure member and pass the control to the base implementation if you didn't add that ID. If you don't provide layout information, the default implementation returns FALSE
and no action is taken.
When this function is called, non-callback items are in their new position after resizing, but you can't assume the same for the previous callback items.
virtual void GetTotalClientRect(LPRECT lpRect)
Override this function to provide the client area the class uses to perform layout calculations, both when adding controls and when rearranging layout.
GetClientRect
. It can be useful for windows with scrollbars or expanding windows, to provide the total client area, even those parts which are not visible. void ClipChildren(CDC *pDC)
Not recommended when compatibility with WinXP themes is needed.
See EraseBackground
, GetClippingRegion
.
Excludes child windows from the clipping area of the given DC. Usually called in your OnEraseBkgnd
to have a flicker-free resizing.
void GetClippingRegion(CRgn* pRegion)
Obtains the clipping region for the current layout. Windows that needs the parent to paint the background, such as some types of Static controls or transparent windows, belongs to the region, while the others are clipped out.
You may use this region, for example, in calls to PaintRgn
or FillRgn
to paint the parent's background.
void EraseBackground(CDC* pDC)
Paints the layout's clipping region using the default brush for the parent window. Usually called in your OnEraseBkgnd
to have a flicker-free resizing.
virtual void InitResizeProperties(CResizableLayout::LayoutInfo& layout)
Used to set the initial resize properties of an anchored control, that are stored in the properties
member of the LayoutInfo
structure.
// wether to ask for resizing properties every time BOOL bAskClipping; BOOL bAskRefresh; // otherwise, use the cached properties BOOL bCachedLikesClipping; BOOL bCachedNeedsRefresh;
The default implementation sets "clipping" as static, calling LikesClipping
only once, and "refresh" as dynamic, causing NeedsRefresh
to be called every time. This should be right for most situations, but you can override this function if needed.
virtual BOOL LikesClipping(const LayoutInfo &layout)
Used to determine if an anchored control can be safely clipped, that is it's able to repaint its background. The return value is TRUE
if clipping can occur for this window, FALSE
otherwise.
The default implementation tries to identify "clippable" windows by class name and window's style. Override this function if you need more control on clipping. Note that not clipped windows often tend to flicker.
virtual BOOL NeedsRefresh(const LayoutInfo &layout, const CRect &rectOld, const CRect &rectNew)
Used to determine if a child window needs repainting when moved/resized. The return value is TRUE
if this window must be repainted, FALSE
otherwise.
The default implementation tries to identify windows that need refresh by class name and window's style. Override this function if you need different behavior or if you have custom child windows.
BOOL CreateSizeGrip(BOOL bVisible = TRUE, BOOL bTriangular = TRUE, BOOL bTransparent = FALSE)
OnCreate
message handler. You can specify the initial visibility, whether to use a triangular shape or a transparent background. The return value is non-zero if the grip was created successfully, zero otherwise. void SetSizeGripShape(BOOL bTriangular)
BOOL SetSizeGripBkMode(int nBkMode)
SetBkMode
function: OPAQUE
or TRANSPARENT
. The return value is zero if an error occurred, non-zero otherwise. void SetSizeGripVisibility(BOOL bVisible)
Sets the grip's default visibility. The actual state depends on the current show count, that is modified by calls to ShowSizeGrip
and HideSizeGrip
.
void ShowSizeGrip(DWORD* pStatus, DWORD dwMask = 1)
Increases the current show count, if the visibility status is not already on. Changes to the actual grip's visibility are effective only after a call to UpdateSizeGrip
.
The DWORD
variable pointed to by pStatus
, masked by dwMask
, holds the visibility status with respect to a particular condition meaningful only to the caller. The initial value of this visibility status must be zero (off) to allow to temporarily show the grip, non-zero (on) to allow to temporarily hide the grip.
DWORD
variable can hold up to 32 conditions just by changing the associated mask. void HideSizeGrip(DWORD* pStatus, DWORD dwMask = 1)
Decreases the current show count, if the visibility status is not already off. Changes to the actual grip's visibility are effective only after a call to UpdateSizeGrip
.
Every call to ShowSizeGrip
should be matched by a call to HideSizeGrip
, according to the condition the visibility status represents.
BOOL IsSizeGripVisible()
void UpdateSizeGrip()
OnSize
handler or after a call to ShowSizeGrip
or HideSizeGrip
. void MinMaxInfo(LPMINMAXINFO lpMMI)
MINMAXINFO
structure according to current min/max settings. void SetMaximizedRect(const CRect& rc)
Sets the rectangular area that the window will occupy when maximized. Default is the standard size and position set by the system (the workspace area of the screen).
void ResetMaximizedRect()
Reverts the effect of a previous call to SetMaximizedRect
, that is maximizing the window will produce the standard behavior.
void SetMinTrackSize(const CSize& size)
Sets the minimum size of the window when resized. This setting does not affect the behavior of a minimize operation, which always produce the expected result.
void ResetMinTrackSize()
Reverts the effect of a previous call to SetMinTrackSize
, that is the standard minimum size is set by the system.
void SetMaxTrackSize(const CSize& size)
Sets the maximum size of the dialog when resized. Default is the standard size set by the system (the workspace area of the screen). Note that this setting affects the behavior of a maximize operation and maximized size is clipped to this value by the system.
void ResetMaxTrackSize()
Reverts the effect of a previous call to SetMaxTrackSize
, that is the standard maximum size is set by the system.
BOOL LoadWindowRect(LPCTSTR pszSection, BOOL bRectOnly)
Loads the window's size, position and state from the given section in the application's profile settings (either the registry or a INI file). If bRectOnly
is TRUE
the window's minimized/maximized state is not restored.
BOOL SaveWindowRect(LPCTSTR pszSection, BOOL bRectOnly)
Saves the window's size, position and state to the given section in the application's profile settings (either the registry or a INI file). You should use the same value you use in the LoadWindowRect
function for the bRectOnly
argument.
I've been working on the 1.4 version for months and yet I'm not able to release anything. This is very disappointing! I had to do something... that's why I'm releasing this incomplete alpha version. There are so many improvements in the library that I don't even remember - thank god I used CVS - and I believe it's better to release something instead of waiting another six month or worse.
I passed the last weeks to start documenting the code using Doxygen, but there are still so many things to do:
It takes too long to do all of this by myself. I would really appreciate any little help the most experienced users of this library could offer me. Otherwise you'll just have to wait longer...
I try to do my best to fix bugs and/or to find work-arounds for new problems, but there's surely something I forgot or I didn't have the time to look at.
Some controls, such as some types of Static, the GroupBox and transparent windows, relies on the parent window to paint the background. This causes the controls to be over-painted with the background color before they can redraw themselves. And that's precisely what flickering is.
Improvements in version 1.4a include a new feature that reduces flickering when resizing occurs on the left or top edges of a window. This may cause issues in projects that used version 1.3, because controls that would stay on the top-left corner could be left not anchored. Now you have to call AddAnchor for all the child windows.
Since version 1.1 and before I added a Docs directory to the project, with a basic Doxygen configuration file. But the source code needs Doxygen compliant comments to actually produce something more than the class hierarchy.
In version 1.4a I started using the Doxbar addin for VC++ 6 and now some of the core classes are documented. Just run Doxygen in the library project directory.
Oz Solomonovich helped me to solve the problems with WinXP themes and clipping regions. He also made me aware of a trick you can use to force windows to draw on a specific DC, possibly enabling a double-buffered repainting (the hard way).
In version 1.4a I was trying to use XP native double-buffering, but it leads to many issues that need testing and time to be solved. This is disabled with a preprocessor macro by default, so that the code can still be used safely.
I tried to keep backward compatibility with all Win32 platforms, but I don't have old Windows versions installed anymore, so I can't be sure everything is ok.
In version 1.4a I also introduced additional code and preprocessor directives to help tailor the code to specific platforms. The principle is, when you choose a target Windows platform with the usual "WINVER and friends" macros, the library produces code that enables features specific to that platform, but degrades gracefully when run on older platforms.
Version 1.4a includes manifest files for XP visual styles.
Version 1.4a includes Unicode build configurations. It needs testing though.
Well, this has been started in version 1.4a and the official 1.4 version won't be released until it's complete.
I'm not sure about this, but I guess it would be easier to just use the library in some project. Also help in this direction is welcome.
Alexander D. Alexeev has contributed a WTL port of an old version, I think 1.1, that gave me some ideas for the library. I'd like to update his port to the new version and possibly avoid repeated code. This makes me thinking about an SDK port to use as a base implementation for both the MFC and WTL versions.
I made experiments with a template version of the library and proved the above idea is feasible, but it's surely a long-term task. Definitely after the official 1.4 version is released.
This is on it's way for the next official release.
This class library is an effort to make a more reusable solution to the problem of resizable windows. And it's a bigger and bigger an effort as development goes on. I still have ideas to improve this library, but definitely lack the time to do it. I hate to see those fixed size dialogs, or worse those resizable windows that flicker badly, so I really hope to see more resizable windows out there and that look good!
I don't want to say "use my library", because the same things can surely be done in a better way, but at least use something that works as good as ResizableLib.
Thanks to all the happy users and especially to all those people who sent fixes or bug reports. As you can see, there are always many things to do... so contributions are definitely welcome!
This library is now distributed under the terms of , that allows for use in commercial applications. You just can't sell this work as part of a library and claim it's yours. This also means that credits are not required, but they would be nice!
The CVS tree is now hosted on .