动态切换采用 CSplitterWnd 静态划分的视图布局(MFC)
标题读起来有些拗口,具体是什么情况,我们来看:
一、问题的提出
一个采用MFC开发的软件,其窗体视图采用CSplitterWnd三分,效果如下图所示:
图1 软件的默认视图布局
该MFC开发的软件功能多,界面非常复杂,上图只是一个demo示意图,但并不妨碍我们理解本文描述的问题。
即视图分为left、top、bottom三部分。该软件的各个视图实际界面比较复杂,bottom视图有时候要显示重要的预览图,但因top视图挤用空间,导致查看预览图的时候,要频繁拖动bottom视图中的滚动条。但是又不能直接将top视图大小拉到最小,因为查看过程中,还要与top视图互动。
希望当bottom视图要显示内容时,可以采取一种方法(比如点击一个按钮切换布局)将top视图移动到left视图的下边,让bottom视图独立占满右边显示。
即要求下图所示效果:
图2 动态切换布局之后
需求是很简的:加一个按钮,点击该按钮,能够使得软件界面视图的布局,在上述两个状态来回切换。
初看该问题很好解决,它仅涉及整体层面的布局,不涉及软件的其它逻辑(创建工具栏、各视图的实现、文档、业务逻辑等); 而该软件的总体布局是非常简单的,我们用vs 2008+(我用的vs 2013)创建一个纯MFC单文档应用程序,通过一些步骤很容易模拟出软件的原始布局。
二、场景重现
重现改布局的步骤简述:
1)VS里新建纯MFC SDI窗体应用程序;
2)添加三个视图,为了方便后面区分,特意将三个视图基类分别设为 CFormView、CListView、CEditView; 本示例为了更进一步区分,响应了CListView的WM_PAINT事件,将背景颜色填充成灰色。
3)创建三分视图。为此,在CMainFrame中添加两个CSplitterWnd对象,如下:
CSplitterWnd m_wndSliter;
CSplitterWnd m_wndSliterR;
之后,在CMainFrame中重写OnCreateClient方法。其实现如下:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
// TODO: 在此添加专用代码和/或调用基类 CRect rect;
GetClientRect(&rect); m_wndSliter.CreateStatic(this, , );
m_wndSliter.CreateView(,, RUNTIME_CLASS(CViewFrom), CSize(, rect.Height()), pContext); m_wndSliterR.CreateStatic(&m_wndSliter, , , WS_CHILD|WS_VISIBLE, m_wndSliter.IdFromRowCol(, ));
m_wndSliterR.CreateView(,, RUNTIME_CLASS(CViewEdit), CSize(rect.Width()-, ), pContext);
m_wndSliterR.CreateView(,, RUNTIME_CLASS(CViewList), CSize(rect.Width()-, rect.Height()-), pContext); return TRUE;
//return CFrameWnd::OnCreateClient(lpcs, pContext);
}
其中,CViewFrom、CViewEdit和CViewList就是第2)步创建的三个视图。需要在MainFrame.h中包含这三个视图的头文件。 上述代码调试运行后,即可得类似图1的三分布局效果。
分析一下上述三分视图的实现:
首先,采用一个CSplitterWnd 将视图划分为左右两部分(1行两列),左边放置 viewleft,右边放置另一个 CSplitterWnd,该CSplitterWnd 又将右边划分为上下两部分(2行1列), 然后,上面部分放置viewtop,下面放置viewbottom。 该划分是在OnCreateClient的时候创建的,并且采用的是CreateStatic静态划分的形式。
那么,这种情况下,能否动态的将viewtop切换到viewleft的下面呢? 软件运行中,各视图上有用户操作的各种状态,切换过程中,是否可以保持各视图的当前状态(即不要重新创建视图对象)呢?
答案是肯定的。
三、分析问题
再来看一下图2所示的效果,其实就是采用一个 CSplitterWnd 将视图划分为左右两部分(1行2列),第0列又采用一个CSplitterWnd进一步划分为两行。然后将各个视图放到对应的位置。 我们不难知道 CSplitterWnd 类提供了GetPane方法可以获取指定行列处的视图,但是没有一个SetPane的方法,动态设置视图。 如何动态的给每个位置设置视图对象,疑惑就在这里了。
Bing查到CodeProject上的一篇文章: http://www.codeproject.com/Articles/178/Switching-to-other-views-in-a-doc-view-application
该文章中描述了采用CSplitterWnd时,如何动态的替换掉其中的一个视图。看到该文章,心里有些低了,原来CSplitterWnd各行列位置的视图,是可以动态替换掉的。
刚才我们知道,有GetPane方法可以获取到各行列位置的视图对象,那么,问题不就变成了取出现有的视图对象放置到CSplitterWnd的指定行列位置吗。
有了该思路,立刻动手,发现事情没有这么简单,中间过程就不说了。
四、解决问题
结果过程也没有多复杂。
图2所示的情况,不就是左边变成两行划分吗,那么我们先增加一个CSplitterWnd,将左边划分为两行。
即,初始状态下,应用程序视图是被划分为左上、左下、右上、右下四部分的。
代码如下(红色部分为新增):
1)在MainFrame.h中:
CSplitterWnd m_wndSliter;
CSplitterWnd m_wndSliterL; // 新增加一个拆分器
CSplitterWnd m_wndSliterR;
2)OnCreateClient实现:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
// TODO: 在此添加专用代码和/或调用基类 CRect rect;
GetClientRect(&rect); m_wndSliter.CreateStatic(this, , );
m_wndSliter.CreateView(,, RUNTIME_CLASS(CViewFrom), CSize(, rect.Height()), pContext); m_wndSliterR.CreateStatic(&m_wndSliter, , , WS_CHILD|WS_VISIBLE, m_wndSliter.IdFromRowCol(, ));
m_wndSliterR.CreateView(,, RUNTIME_CLASS(CViewEdit), CSize(rect.Width()-, ), pContext);
m_wndSliterR.CreateView(,, RUNTIME_CLASS(CViewList), CSize(rect.Width()-, rect.Height()-), pContext); m_wndSliterL.CreateStatic(&m_wndSliter, 2, 1, WS_CHILD|WS_VISIBLE, m_wndSliter.IdFromRowCol(0, 0));
m_wndSliterL.ShowWindow(SW_HIDE); return TRUE;
//return CFrameWnd::OnCreateClient(lpcs, pContext);
}
初始化新增加的拆分器,但是不给它设置视图,并且将其隐藏(本处分开两行写,实际可简化代码一步到位)。 隐藏是很重要的,拆分器划分布局后,如果不会为其创建视图,会导致应用程序运行失败。
即在软件首次创建视图布局的时候,就采用 m_wndSliterL 将左边一列划分为两行,但是又不给它设置视图。而是将左边的视图直接设置在了 m_wndSliter 这个顶层划分中, 并且,非常重要的,要将 m_wndSliterL 隐藏。
上述两步完成后,编译运行,效果和没有修改前一样(如图1)。
下面,我们来添加动态来回切换视图的代码:
1)在CMainFrame.h中添加一个标记,以及一个方法申明:
//...
bool _isCommomViewStyle; //...
void SwitchViewStyle();
2)在CMainFrame的构造函数函数中将_isCommomViewStyle标记初始化为true,即默认状态。
3)添加SwitchViewStyle方法的实现,如下所示:
// 切换视图
void CMainFrame::SwitchViewStyle()
{
if (_isCommomViewStyle)
{
CWnd* viewLeft = m_wndSliter.GetPane(,);
CWnd* viewTop = m_wndSliterR.GetPane(,);
CWnd* viewBot = m_wndSliterR.GetPane(,); viewLeft->SetParent(&m_wndSliterL);
viewTop->SetParent(&m_wndSliterL);
viewBot->SetParent(&m_wndSliter); ::SetWindowLong(viewLeft->m_hWnd, GWL_ID, m_wndSliterL.IdFromRowCol(, ));
::SetWindowLong(viewTop->m_hWnd, GWL_ID, m_wndSliterL.IdFromRowCol(, ));
::SetWindowLong(viewBot->m_hWnd, GWL_ID, m_wndSliter.IdFromRowCol(, )); m_wndSliterL.SetRowInfo(, , );
m_wndSliterL.ShowWindow(SW_SHOW);
m_wndSliterR.ShowWindow(SW_HIDE); m_wndSliterL.RecalcLayout();
m_wndSliter.RecalcLayout(); _isCommomViewStyle=false;
}
else
{
CWnd* viewLeft = m_wndSliterL.GetPane(,);
CWnd* viewTop = m_wndSliterL.GetPane(,);
CWnd* viewBot = m_wndSliter.GetPane(,); viewLeft->SetParent(&m_wndSliter);
viewTop->SetParent(&m_wndSliterR);
viewBot->SetParent(&m_wndSliterR); ::SetWindowLong(viewLeft->m_hWnd, GWL_ID, m_wndSliter.IdFromRowCol(, ));
::SetWindowLong(viewTop->m_hWnd, GWL_ID, m_wndSliterR.IdFromRowCol(, ));
::SetWindowLong(viewBot->m_hWnd, GWL_ID, m_wndSliterR.IdFromRowCol(, )); m_wndSliterL.ShowWindow(SW_HIDE);
m_wndSliterR.ShowWindow(SW_SHOW);
m_wndSliterR.RecalcLayout();
m_wndSliter.RecalcLayout(); _isCommomViewStyle=true;
}
}
即首先判断当前视图的状态,如果是普通状态(图1),则切换到图2所示状态;否则切换到图1所示状态。切换后同时更新状态标志。
在切换状态的时候,主要步骤为:
1)获取各位置的视图对象,采用CSplitterWnd::GetPane(int row, int col);
2) 重新设置各视图的父容器,CWnd::SetParent(CWnd* pNewWnd);
3) 修改视图窗体信息,::SetWindowLong,具体见代码
4)切换SplitterWnd的显示
5)更新SplitterWnd的布局
其中,非常重要的一点是,SplitterWnd对象划分完成后,必须为每一部分设置视图对象(初次创建时采用CreateView方法),否则程序将不可运行。但是,如果该SpliiterWnd不可见则例外。
最后,我们可以在工具栏添加一个按钮,在该按钮的点击事件响应函数中,调用SwitchViewStyle即可完成布局的动态切换。
注:本示例中m_wndSliter*变量的申明命名并不规范,请注意。
全文完
enjoy..
本文最早由 杨志军(ic#,yang.email#qq.com) 发表于 个人备忘录软件,以及博客园。转发请保留本声明。
2014/4/22
动态切换采用 CSplitterWnd 静态划分的视图布局(MFC)的更多相关文章
- 计算机网络之介质访问控制(静态划分信道、FDM、TDM、STDM、WDM、CDM)、(动态划分信道、ALOHA、CSMA、CSMA/CD、CSMA/CA)、令牌传递协议
文章转自:https://blog.csdn.net/weixin_43914604/article/details/104935912 学习课程:<2019王道考研计算机网络> 学习目的 ...
- laravel4通过控制视图模板路劲来动态切换主题
通过控制视图模板路劲来动态切换主题 App::before(function($request) { $paths = Terminal::isMobile() ? array(__dir__.'/v ...
- Spring3 整合Hibernate3.5 动态切换SessionFactory (切换数据库方言)
一.缘由 上一篇文章Spring3.3 整合 Hibernate3.MyBatis3.2 配置多数据源/动态切换数据源 方法介绍到了怎么样在Sping.MyBatis.Hibernate整合的应用中动 ...
- Spring动态切换多数据源解决方案
Spring动态配置多数据源,即在大型应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性.而这样的方案就会不同于常见的单一数据实例的方案,这就要程序在运行时根据当时 ...
- Spring主从数据源动态切换
参考文档: http://uule.iteye.com/blog/2126533 http://lanjingling.github.io/2016/02/15/spring-aop-dynamicd ...
- Quartz.NET 3.0.7 + MySql 实现动态调度作业+动态切换版本+多作业引用同一程序集不同版本+持久化+集群(一)
原文:Quartz.NET 3.0.7 + MySql 实现动态调度作业+动态切换版本+多作业引用同一程序集不同版本+持久化+集群(一) 前端时间,接到领导任务,写了一个调度框架.今天决定把心路历程记 ...
- 【Paddy】如何将物理表分割成动态数据表与静态数据表
前言 一般来说,物理表的增.删.改.查都受到数据量的制约,进而影响了性能. 很多情况下,你所负责的业务关键表中,每日变动的数据库与不变动的数据量比较,相差非常大. 这里我们将变动的数据称为动态数据,不 ...
- Silverlight4中实现Theme的动态切换
Silverlight一般用来开发一些企业的应用系统,如果用户一直面对同一种风格的页面,时间长了难免厌烦,所以一般都会提供好几种风格及Theme供用户选中,下面就来说一下如何在不重新登录系统的情况下, ...
- Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法
一.开篇 这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能.所以在出来数据库方言的时候基 ...
随机推荐
- QTP 场景恢复– 函数调用
创建自动化测试是为了实现无人值守下运行,但也给开发人员带来一些问题.假如你离开办公室前启动测试,想要让它通宵运行.然而,由于不可预见的错误,您的测试会在某一点停止,中断了测试结果.因此QTP中引入场景 ...
- 学习资料 数据查询语言DQL
数据查询语言DQL介绍及其应用: 查询是SQL语言的核心,SQL语言只提供唯一一个用于数据库查询的语句,即SELECT语句.用于表达SQL查询的SELECT语句是功能最强也是最复杂的SQL语句,它提供 ...
- Java中自定泛型方法
泛型用到哪些集合:List Set Map List<String> list=new ArraList<String>(); list.add("美女") ...
- powerdesigner逆向导出oracle数据库结构显示备注
最近接到命令,要将oracle数据库的结构导出为pdm文件供其他同事使用,逆向工程导出数据库结构比较方便,但是发现导出的数据库结构没有注释,这是很郁闷的事情: 查过网上很多资料都是sqlserver的 ...
- DuiLib通用窗口类WindowImplBase封装
.h头文件 class WindowImplBase : public CWindowWnd, public INotifyUI, public IMessageFilterUI, public ID ...
- 在C++中调用DLL中的函数 (3)
1.dll的优点 代码复用是提高软件开发效率的重要途径.一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用.比较常见的例子是各种应用程序框架,ATL.MFC等 ...
- JavaScript设计模式
-->面向对象中的23种设计模式简介 最近看了一本书,推荐给大家<JavaScript设计模式与开发实践>图灵出版社的,讲的非常棒! 详细讲解了js开发中常用的14种设计模式,有很多 ...
- 重载(overload)、重写:覆盖(override)、重定义:遮蔽(redefine)、多态
同一域名空间,函数名相同,签名不同 编译期绑定确定绑定函数,也称为静态多态 重写:覆盖(override) 虚函数 子类空间,函数名相同,签名相同 重定义:遮蔽(redefine) 非虚函数,子类成员 ...
- centos系统自动化安装研究
https://rhinstaller.github.io/anaconda/intro.html https://github.com/rhinstaller/pykickstart/blob/ma ...
- WWF3XOML方式创建和启动工作流 <第十篇>
一.XOML使用工作流的好处 通过Xoml方式使用工作流的好处在于,它能够不重新启动程序的情况下,仅仅通过配置xoml就能够实现改变工作流,非常灵活. 创建一个WinForm程序如下: 代码如下: n ...