MFC 这个东西虽然说很老了,可是有时候写点小的 Demo 还是有点用处的。然而像我这样的学艺不精的孩纸,每次做出来的窗体布局上那个扯淡啊,缩放窗体的时候控件位置都不变,丑死了。虽然可以自行处理某些 WM 消息来实现自适应布局,但是架不住我懒啊。这次就来说一个简单的方法:使用 EasySize 来进行方便的自适应布局。

EasySize 以一个头文件(EasySize.h)的方式提供,源代码后面会提供。我们先来看看文件中的使用说明。

Usage:
- Insert 'DECLARE_EASYSIZE' somewhere in your class declaration
- Insert an easysize map in the beginning of your class implementation (see documentation) and outside of any function.
- Insert 'INIT_EASYSIZE;' in your OnInitDialog handler.
- Insert 'UPDATE_EASYSIZE' in your OnSize handler
- Optional: Insert 'EASYSIZE_MINSIZE(mx,my);' in your OnSizing handler if you want to specify a minimum size for your dialog

当然了,在进行以上各个步骤之前呢,先得引入他的头文件。大概翻译一下:

  1. 在你的窗体类的定义里面加入 DECLARE_EASYSIZE
  2. 在类的实现里面添加 EasySize 的映射,不要写在任何函数里面
  3. OnInitDialogWM_INITDIALOG 的响应函数) 里面添加 ‘INIT_EASYSIZE;
  4. OnSizeWM_SIZE 的响应函数) 里面添加 UPDATE_EASYSIZE
  5. 如果你想控制窗体缩放的最小值,可以在 OnSizingWM_SIZING 的响应函数)里面添加 EASYSIZE_MINSIZE(mx,my); 来实现

差不多这么个意思,下面我们通过实际演示来举例(使用 VC6)。

首先我们新建一个 MFC 工程,取名叫 EasySizeDemo
新建 MFC 工程

简单起见,我们使用最简单的基本对话框

使用基本对话框

对话框默认是不能改变大小的,但是这样的话我们今天就没得玩了。所以先把对话框的边框属性改成 Resize(图上的调整大小,翻译的好扯)

更改边框属性为 Resize

然后我们先运行看看这个对话框:

自动生成的对话框

拖动边框看看

改变了对话框大小之后

可以看到,默认的对话框大小改变之后,所有控件的位置都没有变化,如果我们就这样去写一个软件的话,出来的效果惨不忍睹。接下来请出我们今天的主角:EasySize 登场。

首先要把 EasySize.h 文件引入工程,然后在代码里引用该文件。

将文件引入工程

代码中引用文件

然后按照最开始说的使用方法,一步步来。

第一步,在你的窗体类的定义里面加入 DECLARE_EASYSIZE

类定义中加入 DECLARE_EASYSIZE

第二步,在类的实现里面添加 EasySize 的映射,这一步我们放到最后,因为有些东西要说明。

第三步,在 OnInitDialogWM_INITDIALOG 的响应函数) 里面添加 ‘INIT_EASYSIZE;

OnInitDialog 中加入 INIT_EASYSIZE;

第四步,在 OnSizeWM_SIZE 的响应函数) 里面添加 UPDATE_EASYSIZE。但是我们一开始在代码中找不到 OnSize 的处理函数,所以需要去类向导添加一下

打开类向导

找到 WM_SIZE 并添加响应

添加好响应函数之后,再添加代码就好了。这里注意,用法里说添加 UPDATE_EASYSIZE,实际上要带上分号 UPDATE_EASYSIZE;

OnSize 中加入 UPDATE_EASYSIZE;

接下来我们说明添加 EasySize 映射的事情。代码如下,其映射包含在 BEGIN_EASYSIZE_MAP()END_EASYSIZE_MAP 之间,均不需要分号,BEGIN_EASYSIZE_MAP() 带一个参数,为要处理的窗体类,在本例中为 CEasySizeDemoDlg

添加 EasySize 映射

中间包含的就是具体的映射代码了,这是一个类似函数调用的东西,接受 6 个参数。第一个是要设置属性的元素 ID,这里我们设置确定按钮也就是IDOK,接下来的四个属性,分别是按钮 左-上-右-下 四个方向在缩放时动作的描述,这里我们使用了 ES_KEEPSIZE(保持尺寸) 和 ES_BORDER(保持对边框距离),其取值还可以是其他控件ID,则表示和该控件对齐。按照上图设置了映射之后,我们再来运行程序,看看效果。

缩放后按钮的位置

缩放后按钮的位置

注意看,这个时候我们已经调整了对话框的大小,确定按钮相对于上方和右方边框的距离没有变,相对于左方和下方的边框,保持了自己的大小,我们的代码达到了想要的效果。

使用说明中还有一个用法,就是设置窗体最小尺寸,下面我们来试试。

根据说明,要在 OnSizing 里面添加一句话,按照上述的去类向导里,可能找不到 WM_SIZING 消息,我们需要切换到类视图,在窗体类上右键选择添加 Windows 消息处理函数,在弹出的窗口中,选择我们的对话框, Filter 设置为 Window,然后就可以在左边的消息栏找到 WM_SIZING 了,找到后再点击 Add and Edit,将直接跳转到代码里面。

从类视图中添加消息处理

找到 WM_SIZING

我们在代码里添加 EASYSIZE_MINSIZE(mx,my);,其中 mxmy 分别表示窗体最小的宽和高。这里需要注意,说明中提到的参数并不完整,我们还需要把 OnSizing 方法的两个参数一并传递过去,这样就可以了。

OnSizing 代码

最后来看看效果,窗体缩放到这里就不能再缩小了,图上看不到效果,大家可以自己动手试试。

OnSizing 代码

最后说明下,EasySize 其实是一系列的宏定义,感兴趣的话可以看下源码,不感兴趣可以直接使用。上述并不是其全部用法,其余的用法大家自己发觉咯~~

最后附上 EasySize.h 代码

/*===================================================*\
|                                                     |
|  EASY-SIZE Macros                                   |
|                                                     |
|  Copyright (c) 2001 - Marc Richarme                 |
|      devix@devix.cjb.net                            |
|      http://devix.cjb.net                           |
|                                                     |
|  License:                                           |
|                                                     |
|  You may use this code in any commersial or non-    |
|  commersial application, and you may redistribute   |
|  this file (and even modify it if you wish) as      |
|  long as you keep this notice untouched in any      |
|  version you redistribute.                          |
|                                                     |
|  Usage:                                             |
|                                                     |
|  - Insert 'DECLARE_EASYSIZE' somewhere in your      |
|    class declaration                                |
|  - Insert an easysize map in the beginning of your  |
|    class implementation (see documentation) and     |
|    outside of any function.                         |
|  - Insert 'INIT_EASYSIZE;' in your                  |
|    OnInitDialog handler.                            |
|  - Insert 'UPDATE_EASYSIZE' in your OnSize handler  |
|  - Optional: Insert 'EASYSIZE_MINSIZE(mx,my);' in   |
|    your OnSizing handler if you want to specify     |
|    a minimum size for your dialog                   |
|                                                     |
|        Check http://devix.cjb.net for the           |
|              docs and new versions                  |
|                                                     |
\*===================================================*/

#ifndef __EASYSIZE_H_
#define __EASYSIZE_H_
#define ES_BORDER 0xffffffff
#define ES_KEEPSIZE 0xfffffffe
#define ES_HCENTER 0x00000001
#define ES_VCENTER 0x00000002
#define DECLARE_EASYSIZE \
void __ES__RepositionControls(BOOL bInit);\
void __ES__CalcBottomRight(CWnd *pThis, BOOL bBottom, int &bottomright, int &topleft, UINT id, UINT br, int es_br, CRect &rect, int clientbottomright);
#define INIT_EASYSIZE __ES__RepositionControls(TRUE); __ES__RepositionControls(FALSE)
#define UPDATE_EASYSIZE if(GetWindow(GW_CHILD)!=NULL) __ES__RepositionControls(FALSE)
#define EASYSIZE_MINSIZE(mx,my,s,r) if(r->right-r->left < mx) { if((s == WMSZ_BOTTOMLEFT)||(s == WMSZ_LEFT)||(s == WMSZ_TOPLEFT)) r->left = r->right-mx; else r->right = r->left+mx; } if(r->bottom-r->top < my) { if((s == WMSZ_TOP)||(s == WMSZ_TOPLEFT)||(s == WMSZ_TOPRIGHT)) r->top = r->bottom-my; else r->bottom = r->top+my; }
#define BEGIN_EASYSIZE_MAP(class) \
void class::__ES__CalcBottomRight(CWnd *pThis, BOOL bBottom, int &bottomright, int &topleft, UINT id, UINT br, int es_br, CRect &rect, int clientbottomright) {\
if(br==ES_BORDER) bottomright = clientbottomright-es_br;\
else if(br==ES_KEEPSIZE) bottomright = topleft+es_br;\
else { CRect rect2;\
pThis->GetDlgItem(br)->GetWindowRect(rect2); pThis->ScreenToClient(rect2);\
bottomright = (bBottom?rect2.top:rect2.left) - es_br;}}\
void class::__ES__RepositionControls(BOOL bInit) { CRect rect,rect2,client; GetClientRect(client);
#define END_EASYSIZE_MAP Invalidate(); UpdateWindow(); }
#define EASYSIZE(id,l,t,r,b,o) \
static int id##_es_l, id##_es_t, id##_es_r, id##_es_b;\
if(bInit) {\
GetDlgItem(id)->GetWindowRect(rect); ScreenToClient(rect);\
if(o & ES_HCENTER) id##_es_l = rect.Width()/2; else {\
if(l==ES_BORDER) id##_es_l = rect.left; else if(l==ES_KEEPSIZE) id##_es_l = rect.Width(); else {\
    GetDlgItem(l)->GetWindowRect(rect2); ScreenToClient(rect2);\
    id##_es_l = rect.left-rect2.right;}}\
if(o & ES_VCENTER) id##_es_t = rect.Height()/2; else {\
if(t==ES_BORDER) id##_es_t = rect.top; else if(t==ES_KEEPSIZE) id##_es_t = rect.Height(); else {\
    GetDlgItem(t)->GetWindowRect(rect2); ScreenToClient(rect2);\
    id##_es_t = rect.top-rect2.bottom;}}\
if(o & ES_HCENTER) id##_es_r = rect.Width(); else { if(r==ES_BORDER) id##_es_r = client.right-rect.right; else if(r==ES_KEEPSIZE) id##_es_r = rect.Width(); else {\
    GetDlgItem(r)->GetWindowRect(rect2); ScreenToClient(rect2);\
    id##_es_r = rect2.left-rect.right;}}\
if(o & ES_VCENTER) id##_es_b = rect.Height(); else  { if(b==ES_BORDER) id##_es_b = client.bottom-rect.bottom; else if(b==ES_KEEPSIZE) id##_es_b = rect.Height(); else {\
    GetDlgItem(b)->GetWindowRect(rect2); ScreenToClient(rect2);\
    id##_es_b = rect2.top-rect.bottom;}}\
} else {\
int left,top,right,bottom; BOOL bR = FALSE,bB = FALSE;\
if(o & ES_HCENTER) { int _a,_b;\
if(l==ES_BORDER) _a = client.left; else { GetDlgItem(l)->GetWindowRect(rect2); ScreenToClient(rect2); _a = rect2.right; }\
if(r==ES_BORDER) _b = client.right; else { GetDlgItem(r)->GetWindowRect(rect2); ScreenToClient(rect2); _b = rect2.left; }\
left = _a+((_b-_a)/2-id##_es_l); right = left + id##_es_r;} else {\
if(l==ES_BORDER) left = id##_es_l;\
else if(l==ES_KEEPSIZE) { __ES__CalcBottomRight(this,FALSE,right,left,id,r,id##_es_r,rect,client.right); left = right-id##_es_l;\
} else { GetDlgItem(l)->GetWindowRect(rect2); ScreenToClient(rect2); left = rect2.right + id##_es_l; }\
if(l != ES_KEEPSIZE) __ES__CalcBottomRight(this,FALSE,right,left,id,r,id##_es_r,rect,client.right);}\
if(o & ES_VCENTER) { int _a,_b;\
if(t==ES_BORDER) _a = client.top; else { GetDlgItem(t)->GetWindowRect(rect2); ScreenToClient(rect2); _a = rect2.bottom; }\
if(b==ES_BORDER) _b = client.bottom; else { GetDlgItem(b)->GetWindowRect(rect2); ScreenToClient(rect2); _b = rect2.top; }\
top = _a+((_b-_a)/2-id##_es_t); bottom = top + id##_es_b;} else {\
if(t==ES_BORDER) top = id##_es_t;\
else if(t==ES_KEEPSIZE) { __ES__CalcBottomRight(this,TRUE,bottom,top,id,b,id##_es_b,rect,client.bottom); top = bottom-id##_es_t;\
} else { GetDlgItem(t)->GetWindowRect(rect2); ScreenToClient(rect2); top = rect2.bottom + id##_es_t; }\
if(t != ES_KEEPSIZE) __ES__CalcBottomRight(this,TRUE,bottom,top,id,b,id##_es_b,rect,client.bottom);}\
GetDlgItem(id)->MoveWindow(left,top,right-left,bottom-top,FALSE);\
}
#endif //__EASYSIZE_H_