控件布局通用解决方案

BeanJoy 2013-06-21 04:37:15
  你是否遇到过这样的问题:用编译器拖出一些控件放到对话框上,并合理安排好了位置;但编译运行,改变对话框的大小后,所有控件的位置都乱了,让人感觉非常糟糕。如果控件不太多,你可以尝试手写代码定位每个控件的位置,但若是控件数量以十或百为单位计数甚至更多,逐一为每个控件指定位置就非常麻烦了。

  本文提供一个宏,可以按照通常对话框的布局要求快速为每个控件布局,对话框大小改变时,控件的大小和位置都会随之而改变。改变控件位置和大小的代码都在宏内部,你只需了解宏的作用即可达到所需布局。

  注意,编写宏和测试环境为XP + VC++6.0SP6 + MFC,其他编译环境可能需对宏稍加修改,我相信你有这个实力。

// CtrlId : 控件ID
// LeftChangeMode : 0:与窗口客户区左边的距离不变; 1:按比例变化; 2:保持控件宽度不变;
// RightChangeMode : 0:与窗口客户区右边的距离不变; 1:按比例变化; 2:保持控件宽度不变;
// TopChangeMode : 0:与窗口客户区上边的距离不变; 1:按比例变化; 2:保持控件高度不变;
// BottomChangeMode : 0:与窗口客户区下边的距离不变; 1:按比例变化; 2:保持控件高度不变;
#ifndef AUTO_SET_CONTROL_POS

// LeftChangeMode取值宏定义
#define LEFT_CHANGE_MODE_FIXED_LEFTPADDING 0
#define LEFT_CHANGE_MODE_RATIO 1
#define LEFT_CHANGE_MODE_FIXED_WIDTH 2

// RightChangeMode取值宏定义
#define RIGHT_CHANGE_MODE_FIXED_RIGHTPADDING 0
#define RIGHT_CHANGE_MODE_RATIO 1
#define RIGHT_CHANGE_MODE_FIXED_WIDTH 2

// TopChangeMode取值宏定义
#define TOP_CHANGE_MODE_FIXED_TOPPADDING 0
#define TOP_CHANGE_MODE_RATIO 1
#define TOP_CHANGE_MODE_FIXED_HEIGHT 2

// BottomChangeMode取值宏定义
#define BOTTOM_CHANGE_MODE_FIXED_LEFEPADDING 0
#define BOTTOM_CHANGE_MODE_RATIO 1
#define BOTTOM_CHANGE_MODE_FIXED_HEIGHT 2

#define AUTO_SET_CONTROL_POS(CtrlId, LeftChangeMode, RightChangeMode, TopChangeMode, BottomChangeMode)\
{\
static int nFirstCtrlId = -1;\
static CRect rectLastClientWindow;\
static CRect rectLastClientWindowTmp;\
if (-1 == nFirstCtrlId)\
{\
nFirstCtrlId = CtrlId;\
}\
if (nFirstCtrlId == CtrlId)\
{\
rectLastClientWindowTmp = rectLastClientWindow;\
}\
rectLastClientWindow.right = cx;\
rectLastClientWindow.bottom = cy;\
CWnd *pWndDlgItem = GetDlgItem(CtrlId);\
if (pWndDlgItem)\
{\
CRect rectDlgItem;\
pWndDlgItem->GetWindowRect(rectDlgItem);\
ScreenToClient(rectDlgItem);\
\
static int nDefaultLeftPadding_##CtrlId = rectDlgItem.left;\
static float fDefaultLeftRatio_##CtrlId = (float)rectDlgItem.left\
/ rectLastClientWindowTmp.right;\
static int nDefaultRightPadding_##CtrlId = rectLastClientWindowTmp.right\
- rectDlgItem.right;\
static float fDefaultRightRatio_##CtrlId = (float)rectDlgItem.right\
/ rectLastClientWindowTmp.right;\
static int nDefaultCtrlWidth_##CtrlId = rectDlgItem.right - rectDlgItem.left;\
int nXChangeMode = (LeftChangeMode << 8) + RightChangeMode;\
\
static int nDefaultTopPadding_##CtrlId = rectDlgItem.top;\
static float fDefaultTopRatio_##CtrlId = (float)rectDlgItem.top\
/ rectLastClientWindowTmp.bottom;\
static int nDefaultBottomPadding_##CtrlId = rectLastClientWindowTmp.bottom\
- rectDlgItem.bottom;\
static float fDefaultBottomRatio_##CtrlId = (float)rectDlgItem.bottom\
/ rectLastClientWindowTmp.bottom;\
static int nDefaultCtrlHeight_##CtrlId = rectDlgItem.bottom - rectDlgItem.top;\
int nYChangeMode = (TopChangeMode << 8) + BottomChangeMode;\
\
if (0x0000 == nXChangeMode)\
{\
rectDlgItem.right = cx - nDefaultRightPadding_##CtrlId;\
}\
else if (0x0001 == nXChangeMode)\
{\
rectDlgItem.right = fDefaultRightRatio_##CtrlId * cx;\
}\
else if (0x0002 == nXChangeMode)\
{\
/*这里不用更改*/\
}\
else if (0x0100 == nXChangeMode)\
{\
rectDlgItem.left = fDefaultLeftRatio_##CtrlId * cx;\
rectDlgItem.right = cx - nDefaultRightPadding_##CtrlId;\
}\
else if (0x0101 == nXChangeMode)\
{\
rectDlgItem.left = fDefaultLeftRatio_##CtrlId * cx;\
rectDlgItem.right = fDefaultRightRatio_##CtrlId * cx;\
}\
else if (0x0102 == nXChangeMode)\
{\
rectDlgItem.left = fDefaultLeftRatio_##CtrlId * cx;\
rectDlgItem.right = rectDlgItem.left + nDefaultCtrlWidth_##CtrlId;\
}\
else if (0x0200 == nXChangeMode)\
{\
rectDlgItem.right = cx - nDefaultRightPadding_##CtrlId;\
rectDlgItem.left = rectDlgItem.right - nDefaultCtrlWidth_##CtrlId;\
}\
else if (0x0201 == nXChangeMode)\
{\
rectDlgItem.right = fDefaultRightRatio_##CtrlId * cx;\
rectDlgItem.left = rectDlgItem.right - nDefaultCtrlWidth_##CtrlId;\
}\
else if (0x0202 == nXChangeMode)\
{\
/*这里不用更改*/\
}\
\
\
if (0x0000 == nYChangeMode)\
{\
rectDlgItem.bottom = cy - nDefaultBottomPadding_##CtrlId;\
}\
else if (0x0001 == nYChangeMode)\
{\
rectDlgItem.bottom = fDefaultBottomRatio_##CtrlId * cy;\
}\
else if (0x0002 == nYChangeMode)\
{\
/*这里不用更改*/\
}\
else if (0x0100 == nYChangeMode)\
{\
rectDlgItem.top = fDefaultTopRatio_##CtrlId * cy;\
rectDlgItem.bottom = cy - nDefaultBottomPadding_##CtrlId;\
}\
else if (0x0101 == nYChangeMode)\
{\
rectDlgItem.top = fDefaultTopRatio_##CtrlId * cy;\
rectDlgItem.bottom = fDefaultBottomRatio_##CtrlId * cy;\
}\
else if (0x0102 == nYChangeMode)\
{\
rectDlgItem.top = fDefaultTopRatio_##CtrlId * cy;\
rectDlgItem.bottom = rectDlgItem.top + nDefaultCtrlHeight_##CtrlId;\
}\
else if (0x0200 == nYChangeMode)\
{\
rectDlgItem.bottom = cy - nDefaultBottomPadding_##CtrlId;\
rectDlgItem.top = rectDlgItem.bottom - nDefaultCtrlHeight_##CtrlId;\
}\
else if (0x0201 == nYChangeMode)\
{\
rectDlgItem.bottom = fDefaultBottomRatio_##CtrlId * cy;\
rectDlgItem.top = rectDlgItem.bottom - nDefaultCtrlHeight_##CtrlId;\
}\
else if (0x0202 == nYChangeMode)\
{\
/*这里不用更改*/\
}\
pWndDlgItem->MoveWindow(rectDlgItem);\
}\
}
#endif

  使用时,只需在对话框的WM_SIZE消息处理函数中加上“AUTO_SET_CONTROL_POS(CtrlId, LeftChangeMode, RightChangeMode, TopChangeMode, BottomChangeMode)”即可,各个参数含义见宏定义的头部注释。对话框大小改变,就会自动修改指定控件的位置和大小。

  上图是测试中MFC控件初化的位置,下图为修改控件大小后的布局:

  代码中所作的处理如下:

void CDialogApplicationDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);

AUTO_SET_CONTROL_POS( IDC_EDIT1, LEFT_CHANGE_MODE_FIXED_LEFTPADDING,
RIGHT_CHANGE_MODE_FIXED_RIGHTPADDING,
TOP_CHANGE_MODE_FIXED_TOPPADDING,
BOTTOM_CHANGE_MODE_FIXED_HEIGHT);
AUTO_SET_CONTROL_POS(IDC_RICHEDIT1, LEFT_CHANGE_MODE_FIXED_LEFTPADDING,
RIGHT_CHANGE_MODE_FIXED_RIGHTPADDING,
TOP_CHANGE_MODE_FIXED_TOPPADDING,
BOTTOM_CHANGE_MODE_FIXED_LEFEPADDING);
AUTO_SET_CONTROL_POS( IDOK, LEFT_CHANGE_MODE_FIXED_WIDTH,
RIGHT_CHANGE_MODE_FIXED_RIGHTPADDING,
TOP_CHANGE_MODE_FIXED_TOPPADDING,
BOTTOM_CHANGE_MODE_FIXED_HEIGHT);
AUTO_SET_CONTROL_POS( IDCANCEL, LEFT_CHANGE_MODE_FIXED_WIDTH,
RIGHT_CHANGE_MODE_FIXED_RIGHTPADDING,
TOP_CHANGE_MODE_FIXED_TOPPADDING,
BOTTOM_CHANGE_MODE_FIXED_HEIGHT);
AUTO_SET_CONTROL_POS( IDC_STATIC, LEFT_CHANGE_MODE_FIXED_LEFTPADDING,
RIGHT_CHANGE_MODE_FIXED_WIDTH,
TOP_CHANGE_MODE_FIXED_HEIGHT,
BOTTOM_CHANGE_MODE_FIXED_LEFEPADDING);
AUTO_SET_CONTROL_POS( IDC_BUTTON1, LEFT_CHANGE_MODE_FIXED_WIDTH,
RIGHT_CHANGE_MODE_FIXED_RIGHTPADDING,
TOP_CHANGE_MODE_FIXED_HEIGHT,
BOTTOM_CHANGE_MODE_FIXED_LEFEPADDING);
}

  每个控件调用一次AUTO_SET_CONTROL_POS,各个参数以宏的形式提供,非常容易了解,比起手写代码非常清晰。当然,如果想重新设计各个控件的布局,只需重新指定布局参数即可。
...全文
363 18 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
向立天 2013-07-24
  • 打赏
  • 举报
回复
您好 我是本版版主 此帖已多日无人关注 请您及时结帖 如您认为问题没有解决可按无满意结帖处理 另外本版设置了疑难问题汇总帖 并已在版面置顶 相关规定其帖子中有说明 您可以根据规定提交您帖子的链接 如您目前不想结帖只需回帖说明 我们会删除此结帖通知 见此回复三日内无回应 我们将强制结帖 相关规定详见界面界面版关于版主结帖工作的具体办法
q1q2q3q4ln 2013-06-24
  • 打赏
  • 举报
回复
1恩,确实是布局管理器的一种思路, 2不过,虽然通常情况下布局管理器都很方便 但是对初学的人或者一些很特殊的布局,却很不友好,甚至由于知识不够的原因 实现不了该布局 3所以,我觉得,程序语言在提供布局管理器的同时,也要提供DIRECTUI 就是I Control,我来管理一切,才是程序的核心!否则,不就乱套了?
BeanJoy 2013-06-24
  • 打赏
  • 举报
回复
引用 15 楼 schlafenhamster 的回复:
我只是 说 控件 多时 movewindow 可能会 闪烁, BeginDeferWindowPos; 。。。 就是为此 而 设的。
OK,学习了,以后遇到闪烁时就知道解决方案,而不会着急了。
schlafenhamster 2013-06-24
  • 打赏
  • 举报
回复
我只是 说 控件 多时 movewindow 可能会 闪烁, BeginDeferWindowPos; 。。。 就是为此 而 设的。
BeanJoy 2013-06-24
  • 打赏
  • 举报
回复
引用 13 楼 schlafenhamster 的回复:
“实际工作中也没有控件多到会闪烁的地步,因此目前为至movewindow还是够用” 100 个控件 还不多 ?
整篇文章中我没有说过我实际工作中有用到过100个控件,你是不是看到另一个帖子去了? 那个帖子说100多个控件位置难以布局,我也是看到那个帖子才写的这个宏。那个帖子楼主好像也没说100个控件会闪烁,只是说很难调整各个控件的位置。
schlafenhamster 2013-06-24
  • 打赏
  • 举报
回复
“实际工作中也没有控件多到会闪烁的地步,因此目前为至movewindow还是够用” 100 个控件 还不多 ?
BeanJoy 2013-06-24
  • 打赏
  • 举报
回复
引用 10 楼 q1q2q3q4ln 的回复:
1恩,确实是布局管理器的一种思路, 2不过,虽然通常情况下布局管理器都很方便 但是对初学的人或者一些很特殊的布局,却很不友好,甚至由于知识不够的原因 实现不了该布局 3所以,我觉得,程序语言在提供布局管理器的同时,也要提供DIRECTUI 就是I Control,我来管理一切,才是程序的核心!否则,不就乱套了?
引用 11 楼 dcmilan 的回复:
简直就是用MFC在讲Qt么 估计微软会考虑加入这个特性的
我写的宏就是把繁复的工作简单化而已,你们仔细看这个宏,其实就是我们平常在OnSize里计算各个控件位置的代码,并没有加其他任何东西,只是用宏来简化控件位置的计算,而不用每次都重复的去写代码。 网上国外人早就有的EasySize类,也与我这个宏思路相同,只不过他那个功能更全。
  • 打赏
  • 举报
回复
简直就是用MFC在讲Qt么 估计微软会考虑加入这个特性的
coolcoffee4051982 2013-06-23
  • 打赏
  • 举报
回复
还好,能看懂。
hurryboylqs 2013-06-23
  • 打赏
  • 举报
回复
谢谢分享
BeanJoy 2013-06-23
  • 打赏
  • 举报
回复
引用 6 楼 schlafenhamster 的回复:
应该使用 : BeginDeferWindowPos
这系列的windows api是防闪烁的吗? 没有用过这系列api,实际工作中也没有控件多到会闪烁的地步,因此目前为至movewindow还是够用。
schlafenhamster 2013-06-23
  • 打赏
  • 举报
回复
应该使用 : BeginDeferWindowPos
BeanJoy 2013-06-22
  • 打赏
  • 举报
回复
引用 3 楼 leeihcy 的回复:
我记得WTL就带这种功能,不过自己用的不多。 如果要说通用方案的话,不知道LZ是否用过WPF,可以了解下布局的概念,你这里的实现只是一种Canvas布局,还有其它布局类型,如Stack,Card等等。这些概念在directui,ios,andriod里面都是通用的.
还没接触过WPF,其实我想说的是MFC下控件布局方案。我写了这个宏后,在网上又看到早有一个老外写的类EasySize,思路与我差不多,不过功能比我这个全。
引用 4 楼 heksn 的回复:
MFC可以借鉴Qt的布局管理器,非常好用
两年前实习的时候,用了一个月Qt,我记得Qt的布局好像是与JAVA的布局差不多,有什么容器之内的,是这样吗?
  • 打赏
  • 举报
回复
MFC可以借鉴Qt的布局管理器,非常好用
leeihcy 2013-06-22
  • 打赏
  • 举报
回复
我记得WTL就带这种功能,不过自己用的不多。 如果要说通用方案的话,不知道LZ是否用过WPF,可以了解下布局的概念,你这里的实现只是一种Canvas布局,还有其它布局类型,如Stack,Card等等。这些概念在directui,ios,andriod里面都是通用的.
BeanJoy 2013-06-21
  • 打赏
  • 举报
回复
引用 1 楼 flydreamGG 的回复:
说到底还是用的MoveWindow,不如我在OnSize里直接Move
你对windows api这么有成见吗?MFC这么强大,还不是在内部封装的api。 你为何会使用MFC,而不直接使用api编程?不就是使着方便吗!
昨夜无风 2013-06-21
  • 打赏
  • 举报
回复
说到底还是用的MoveWindow,不如我在OnSize里直接Move

15,980

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 界面
社区管理员
  • 界面
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧