效果如下图
参考及源码下载:https://www.codeproject.com/Articles/125068/MFC-C-Helper-Class-for-Window-Resizing#CreateFlowLayoutPanel
In MFC, resizing or repositioning controls could be quite bothersome. If you are familiar with the .NET platform, things are much more simplified with the use of theAnchor
and Dock
properties of the Control
class and design-time support for adding child controls to container controls. I tried to mimic some of these features of .NET, but the C++ way.
There are other solutions available online (also on CodeProject.com) for this purpose. I think my solution stands out on its design, simplicity, and feature richness.
This solution allows you to do the following:
SetParent
API)Let's take a brief look at how .NET does its resizing of child controls:
Anchor
- It allows a child control to be anchored to the left, top, right, or bottom edge, or any combination of these four options.Dock
- It allows a child control to be docked to the left, top, right, and bottom edges.SplitContainer
- Creating splitter windows has never been this simple since the invention of this control. It has two panels which can host other controls inside. FlowLayout
- Represents a panel that dynamically lays out its contents horizontally or vertically.So in .NET, all controls are children or grandchildren of the Form
; this creates a hierarchical structure of controls. When a parent is resized or repositioned, all its children are resized or repositioned according to theirAnchor
or Dock
property settings.
In my solution, I create a hierarchical structure of rectangles (CRect
) instead. I have implemented theAnchor
, Panel
, SplitContainer
, Dock
, andFlowLayout
concepts.
There are several classes in this solution, but CWndResizer
is the only class that a developer will work with.
Typically, you will design your dialog template in the Visual Studio Resource Editor, and then in the dialog class implementation, you will have a member variable like this:
private:
CWndResizer m_resizer;
The samples included with this article uses the CDialog
class to demonstrate many features of this class. But this class can be used with any class derived fromCWnd
(CDialog
, CPropertyPage
, CPropertySheet
,CFrmWnd
, CFormView
, etc.).
Before this class can do anything, you must call the Hook
method like this:
BOOL CExample1Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
BOOL bOk = m_resizer.Hook(this);
ASSERT(bOk == TRUE);
}
In this article, I will refer to this window (that is passed to the Hook
method) as "hooked-window".
By calling this method, it places a window procedure hook in the WndProc
chain.
When you call the Hook
method, it stores the client area of the hooked-window in a structure calledCPanel
. A panel is mainly a rectangle area given in client coordinates of the hooked-window. A panel can have zero or more panels as children. During the creation of a panel, you assign a unique name for the panel. The name is used to refer to the panel or find a panel. The client area of the hooked-window is the root of the hierarchy, and it is named_root
.
Each panel has the Anchor
, MinSize
, and MaxSize
properties (along with some other properties). But you cannot directly set or get the properties of a panel; instead, you will use member methods of theCWndResizer
class.
The idea is that when a CPanel
is resized or repositioned, all its children are also resized and repositioned relatively.
Now, let's look at some code snippets.
The following code will anchor the OK and Cancel buttons of your dialog to the bottom-right corner:
BOOL CExample1Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
BOOL bOk = m_resizer.Hook(this);
ASSERT(bOk == TRUE);
bOk = m_resizer.SetAnchor(IDOK, ANCHOR_RIGHT | ANCHOR_BOTTOM);
ASSERT(bOk == TRUE);
bOk = m_resizer.SetAnchor(IDCANCEL, ANCHOR_RIGHT | ANCHOR_BOTTOM);
ASSERT(bOk == TRUE);
}
If you want to create a panel and set its Anchor
property to ANCHOR_HORIZONTALLY
, you will do this:
BOOL CExample1Dlg::OnInitDialog() { CDialog::OnInitDialog(); BOOL bOk = m_resizer.Hook(this); ASSERT(bOk == TRUE); CRect rc(40, 40, 240, 240); bOk = m_resizer.CreatePanel(_T("MyNewPanel"), &rc); bOk = m_resizer.SetAnchor(_T("MyNewPanel"), ANCHOR_HORIZONTALLY); ASSERT(bOk == TRUE); ASSERT(bOk == TRUE); }
This solution cannot clip any part of a dialog control or any part of a panel. ACPanel
object does not have any window (HWND
) associated with it in general. If a child panel is larger than its parent panel, the child's area that is outside the parent will be visible since neither the parent panel nor the child panel is actually a window. To circumvent this issue, we should set a reasonable minimum size of the parent panel by calling theSetMinimumSize
method so that the parent is never smaller than the child's minimum size. You should also consider setting the minimum size of the hooked-window as well such that it can contain all panels within its client area.