Release Notes

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.

ResizableLib - What is it & Why?

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:

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.

Using the library

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.

As a stand alone project

To use the class library perform the following steps:

Unzip the library source and put the 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

Go to the FileView pane and right click on the root element, or choose the "Project" menu, then select "Insert Project into Workspace..."

Navigate to the place where you put the library, select the library Project file and check "Dependency of" specifying which Project will make use of the library

Re-activate your Project in the FileView and open the Settings dialog (also from the "Project" menu)

Make sure your Project is selected in the tree view and that you have selected "All Configurations" in the combo box, then click on the "C/C++" tab

In the "Category" combo box choose "Preprocessor" and add the library path to the "Additional include directories" edit box

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.

As single files, part of your project

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.

Creating a resizable window [out-of-date]

A good example of use for the "low level" classes is the CResizableDialog class. However, for the lazy programmer, here is a small guide.

You may also need to override the 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)

Class Reference [out-of-date]

CResizableLayout

CResizableLayout::AddAnchor

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:

CResizableLayout::GetAnchorPosition

Calculates the size and position of an anchored control for the given parent window's size. The returned flags should be used in a call to SetWindowPos. The return value is TRUE if the first argument identifies an anchored control, FALSE otherwise.

CResizableLayout::ArrangeLayout

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.

CResizableLayout::ArrangeLayoutCallback

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.

You should test the value of the 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.

CResizableLayout::GetTotalClientRect

virtualvoid 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.

This function is used by the class, and should be used by derived classes too, in place of the standard 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.

CResizableLayout::ClipChildren

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.

CResizableLayout::GetClippingRegion

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.

CResizableLayout::EraseBackground

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.

The various flags are used to specify whether the resize properties (clipping, refresh) can change at run-time, and a new call to the property querying functions is needed at every layout adjustment, or they are static properties, and the cached value is used when needed.

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.

Note: The default implementation of this and the following overridable functions also sends a registered message to the anchored control, giving it the opportunity to specify its resize properties, which takes precedence if supported. See the files ResizableMsgSupport.* for details.

CResizableLayout::LikesClipping

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.

CResizableGrip

CResizableGrip::CreateSizeGrip

You call this function to create the size grip, usually in your 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.

CResizableGrip::SetSizeGripShape

void SetSizeGripShape(BOOL bTriangular)

Changes the grip's shape to a triangle or a rectangle.

CResizableGrip::SetSizeGripBkMode

BOOL SetSizeGripBkMode(int nBkMode)

Sets the background mode of the size grip. Accepts the same values as the SetBkMode function: OPAQUE or TRANSPARENT. The return value is zero if an error occurred, non-zero otherwise.

CResizableGrip::SetSizeGripVisibility

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.

CResizableGrip::ShowSizeGrip

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.

Note: A single DWORD variable can hold up to 32 conditions just by changing the associated mask.

CResizableGrip::HideSizeGrip

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.

CResizableGrip::IsSizeGripVisible

BOOL IsSizeGripVisible()

Used to tell if the size grip should be actually visible, according to the current show count.

CResizableGrip::UpdateSizeGrip

void UpdateSizeGrip()

Recalculates the grip's position when the containing window is resized and shows or hides the grip as needed. You usually call this function in your OnSize handler or after a call to ShowSizeGrip or HideSizeGrip.

CResizableMinMax

CResizableMinMax::MinMaxInfo

void MinMaxInfo(LPMINMAXINFO lpMMI)

Updates the MINMAXINFO structure according to current min/max settings.

CResizableMinMax::SetMaximizedRect

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).

CResizableMinMax::ResetMaximizedRect

void ResetMaximizedRect()

Reverts the effect of a previous call to SetMaximizedRect, that is maximizing the window will produce the standard behavior.

CResizableMinMax::SetMinTrackSize

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.

CResizableMinMax::ResetMinTrackSize

void ResetMinTrackSize()

Reverts the effect of a previous call to SetMinTrackSize, that is the standard minimum size is set by the system.

CResizableMinMax::SetMaxTrackSize

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.

CResizableMinMax::ResetMaxTrackSize

void ResetMaxTrackSize()

Reverts the effect of a previous call to SetMaxTrackSize, that is the standard maximum size is set by the system.

CResizableState

CResizableState::LoadWindowRect

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.

CResizableState::SaveWindowRect

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.

Present and Future - Alpha Release 1.4a

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:

Complete the Doxygen documentation

Update the articles and talk about technical details, since the class reference will have its place in the Doxygen generated files

Test some of the most problematic new features (disabled in 1.4a) for the full release

Make better demo projects to show the features and help test them

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...

Known Issues

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.

Wizard97-style resizable dialogs still have some minor drawing issues, especially with WinXP themes enabled, but I think I got them working pretty well.

Flickering has been greatly reduced but it's still there.

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.

Standalone documentation not ready yet.

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.

To Do:

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.

Test the new features with old Windows platforms.

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.

Update the demo projects to use XP visual styles and possibly make them nicer.

Version 1.4a includes manifest files for XP visual styles.

Test the library with Unicode and add Unicode build configurations to the project.

Version 1.4a includes Unicode build configurations. It needs testing though.

Start using Doxygen style comments and convert existing ones, also using the documentation in this page.

Well, this has been started in version 1.4a and the official 1.4 version won't be released until it's complete.

Make the library project actually produce a standalone compiled library or possibly a DLL, just like any good class library, with different output names for the Debug and Release versions.

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.

Plans:

A WTL, and possibly SDK, version of the library

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.

A standalone reference guide for the library (using Doxygen).

This is on it's way for the next official release.

Conclusion

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 Artistic License, 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! https://www.codeproject.com/script/images/smiley_smile.gif

Share

About the Author

Paolo began programming at the age of 9 with a glorious Olivetti M24 (i8086) and GW-BASIC, then he played a bit with Turbo C, Turbo Pascal and Assembly (using the MS-DOS Debug). Quick BASIC and Visual Basic shortly followed, until he learned C++ in College. He tought himself MFC and Windows programming, along with some DHTML and Javascript.

Always attracted by low-level programming and Assembly, he started to appreciate the joys of templates and STL while working for his Master Thesis. For seven months he was playing with airplanes and automatic control at the Unversity of Illinois at Urbana-Champaign, where he first met QNX and embedded systems.

In his job experience he learned Java to develop user interfaces and graphical editors, and re-discovered the Eclipse IDE that he had used in its early versions with the QNX SDK. He also deepened his knowledge of Linux and embedded systems, microcontrollers firmware and embedded voice recognition, while also practicing electronics design.

He graduated in Computer Engineering (Ingegneria informatica) at the University of Pisa, Italy, in December 2003. Currently working for an edutainment robotics company (www.robotechsrl.com).

I admire you for the wonderful lib. I have used the CResizablePage ?CResizableSheet in my project. Now I have a trouble when I put an AfxMessageBox in the OnSetActive().

Take the "ResizableWizard97" you have offered as example.BOOL CInterior1::OnSetActive(){ CPropertySheet* pSheet = (CPropertySheet*)GetParent(); ASSERT_KINDOF(CPropertySheet, pSheet);

AfxMessageBox( _T("There is a puzzle.") ); ///This line is added by me.

pSheet->SetWizardButtons( PSWIZB_BACK | PSWIZB_NEXT); return CResizablePageEx::OnSetActive();}You compile the "ResizableWizard97" again, then run it. When the first page is appeared,you drag this page to make it a large page.Then you push "Next Page" button to the 2nd page.Here you can push "previous page" return to the first page.Now , when you push to the 2nd page again ,you will find that all the controls in the 2nd page have lost their anchors.This is a puzzle for me. How to resolve the problem?

As far as I can recall, this isn't a real leak. It is caused by the #pragma directive I used to move initialization of that static member before any user code, and especially before one could call SetDefaultStateStore to change it.

The same trick is used by MFC's leak detection, but apparently the compiler does not reverses the order of initializations when the program exits (I don't even know if it would be required to do so) and the false leak appears.

You can safely ignore it. But please do initialize that string to something, or you won't be able to save/restore any window state.

Paolo

------Why spend 2 minutes doing it by hand when you can spend all night plus most of the following day writing a system to do it for you? - (Chris Maunder)

I have an app that has a splitter window with one side of the splitter containing one of various FormViews (which FormView depends on other settings in the application). I changed these FormViews to derive from CResizableFormView, and the simple ones work great.

However, in some of the FormViews, I have a property sheet with several property pages. If I leave them as standard CPropertySheet and CPropertyPage, they display fine, but of course they don't resize. I change them to CResizableSheet and CResizablePage, and anchor the controls, but they don't resize, and they don't display in the right location, they are quite a ways down and to the right.

I have tried to do some debugging, but got a little lost in the layout logic. Any ideas for what is going on? Any suggestions about what to look at?

It's hard to tell... but, you could first check that all AddAnchor() calls happen in the right place. In the FormView's OnInitialUpdate you should anchor the child PropertySheet and then in each PropertyPage's OnInitDialog you anchor the controls. It should work that way.

Paolo

------Why spend 2 minutes doing it by hand when you can spend all night plus most of the following day writing a system to do it for you? - (Chris Maunder)

The pages are now displaying correctly, but the sheet's apply and help buttons are slightly to the right and below where they belong, so the overlap the right edge and bottom edge of the formview.

I think the question is, how do I set the initial placement and size of the property sheet, before any of your layout code executes. It looks like the property sheet has the correct starting location, but the size is slightly too large.

I am creating the property sheet dynamically with "new" in the Formview's OnInitialUpdate() function, after the call to CResizableFormView::OnInitialUpdate().

I make the propertysheet have the formview as parent. (I tried using a place holder picture control as the parent. This is a common idiom for putting a property sheet in a formview, but it confuses the resizing logic.)

I set the window position (SetWindowPos) of the property sheet to 0,0 in the Formview's OnInitialUpdate. This sets the property sheet to be located at the top left, before ResizableLib does it's layout. This is essential, otherwise the property sheet is incorrectly located by the ResizableLib layout code.

I anchor the property sheet in the formview's OnInitialUpdate, after the call to the CResizableFormView::OnInitialUpdate, and after I set the window posiiton. I anchor the page controls in the page's OnIniDialog, after the call to CResizablePage::OnInitDialog.

I tried calling SetWindowPos in the sheet's OnInitDialog, before the call to the base class, thinking it would set the placement and size, but this seems to have no effect.

In the latest version of the library I use the OnInitDialog() event to initialize the layout of a FormView, just like ordinary Dialog. You can see updated demo code on the SourceForge project page "Code > CVS Browse".

That was basically to enforce the right order of layout operations. OnInitDialog() is not normally called for a FormView, so I thought I could use it for that purpose.

You put all layout initialization inside OnInitDialog(): sheet or other controls creation, size adjustments... and then call AddAnchor(). This function will be called at the right time (or it should be ).

The buttons on the property sheet must also be initialized (moved to the right place) at the right time, but that is more difficult. It should be inside OnInitDialog() but before the call to the base class implementation, which ends up in CResizableSheet::PresetLayout(). However CPropertySheet::OnInitDialog() must be called at the beginning.

You could just call CResizableSheet::PresetLayout() your self, without calling CResizableSheet::OnInitDialog() in your sheet's OnInitDialog() (after intializing the layout). But you need to patch the library code a little bit, because you need to set the m_layoutDone variable to TRUE at the end.

Hope this helps.

Paolo

------Why spend 2 minutes doing it by hand when you can spend all night plus most of the following day writing a system to do it for you? - (Chris Maunder)

I am using the sourceforge version. Just to make sure, I did a check-out this morning. There were a couple of changes that I didn't have, but they just dealt with XP themes. No change in the behavior I see.

Putting the control init in the Formview's OnInitDialog() doesn't work. OnInitDialog is called before OnInitialUpdate, and that's apparently too early. The property sheet doesn't get properly anchored, and winds up down and to the left (the left anchor seems to be the frame, not the splitter).

The best approach I have found is to make my formview's OnInitialDialog look something like this:

And it is still not right. The apply and help buttons are positioned on top of the pages, not below them. And there is some visual weirdness on the screen, when you size the frame large enough, the bottom portion of the splitter window is a lighter gray than the upper portion.

I clearly don't understand what is going on here. I don't understand how these controls are positioned. I can trace through the resizablelib code, but what windows is doing automagically means I end up having to just experiment. A slow way to debug. Having a Formview inside a splitter is messy even without the resizing, and adding resizing seems even worse.

I'll take another look at this tomorrow, maybe I'll have a fresh perspective. Any suggestions welcome.

This works fine with the latest version of the library, but does not support buttons in the property sheet. I don't know how you got your buttons, however I tried to recover the standard buttons that the MFC CPropertySheet class removes from modeless sheets.

Unfortunately CResizableSheet resizes the tab control to take all the available client area for child sheets, just because there were no buttons (without the hack).I got an experimental version of the library with changes to the code to address that situation. If you'd like to try it out just let me know and I'll mail you a copy.

I could include the above hack in the CResizableSheet class itself, but I'm not sure it's a good idea or how to expose such functionality. Any ideas?

Paolo

------Why spend 2 minutes doing it by hand when you can spend all night plus most of the following day writing a system to do it for you? - (Chris Maunder)

if I set the "Active Window Border" to a size greater 1 in "Display Properties/Appearance/Advanced/Item" and start the demo application "Dialog Demo project v1.4 (alpha)" in XP, the dialog isn't properly displayed. The right and bottom edge of the dialog gets lost.

I commented out the two HandleNcCalcSize calls in function CResizableDialog::WindowProc but that does not help. Maybe it is somewhere in the layout calculation/drawing code. Did you ignore somewhere in your code the system metrics SM_CXFRAME, SM_CXBORDER or something else?

I started the ResizableDialog Demo at home under Vista Ultimate 64 bit (Aero-Style) and got a the following result:The left an bottom dialog frame is too close to the controls as you can see in the following picture.Under XP with changed "Active Window Border" the controls are "eaten".

In this, the window style is modified BEFORE you calc the client rect size, so you will get a smaller rect for the initial client area. That's why the bottom-right controls get eaten by the edges. In XP and before, there has been substantially no difference between the frame widths of DS_MODALFRAME and WS_THICKFRAME. But in Vista, WS_THICKFRAME is REALLY THICK !

I and my friends have been longtime users of the Resizable Library and we are very thankful for your great works.

But I've found that CResizableLayout::HandleNcCalcSize() causes a serious problem in Windows Vista. When you resize a CResizableDialog with the top-left corner, the contents of the dialog are not redrawn correctly. I guess the fine and subtle codes for avoiding flickers might have severe conflicts with Vista's graphics.And, HandleNcCalcSize() causes another problem when you have a CTabCtrl in CResizableDialog. The children of the CTabCtrl are not positioned properly. The problem is not only in Vista, but also in XP and maybe in 2K/98.

So I have decided to comment out the codes in CResizableLayout::HandleNcCalcSize() to make it a stub function, and now I'm satisfied with the results. (And you don't have to AddAnchor() to all the controls anymore.)

It may be a disappointment to you in some way, but I think this is a reasonable solution to make the library more handy to use.