为一个文档类对象添加多个视图类对象的方法

为一个文档类对象添加多个视图类对象的方法

方法一:
首先假设一些东西。假定新的视图类CNewView从根视图类CView派生而来。应用程序类名称为CMyWinApp。程序是SDI类型应用。程序使用一个文档对象和两个视图对象。
(1)修改当前的应用程序类
在应用程序类中添加视图变量以及切换视图的函数。
CView* m_pOldView;
CView* m_pNewView;
CView* SwitchView();
在应用程序类的实现文件(.cpp)中添加一个头文件:
#include <AFXPRIV.H>
在该头文件中定义了一个Windows消息(WM_INITIALUPDATE),而切换函数将使用这个消息。

(2)创建和修改新的视图类。
利用类向导建立新的视图类,指定其基类为CView类。需要改变新的视图类中某些成员函数的访问权限:把构造函数和析构函数从protected改为public。这样就允许动态地创建和销毁视图对象,以及改变外观了。

(3)创建和连接新视图类对象
必须修改应用程序类的InitInstance函数。主要是创建新视图类的对象,并对两个视图对象(新的和旧的)都进行初始化。由于新视图对象在InitInstance函数中创建,因而新视图对象和旧视图对象的生命期与应用程序一样。这种方法应该算是一种静态的创建方法。当然可以动态地创建视图对象,在后面提到的方法中有所表现。

在InitInstance函数中ProcessShellCommand一句之后加入下面的代码:
CView* pActiveView = ((CFrameWnd*)m_pMainWnd)->GetActiveView();
m_pOldView = pActiveView;
m_pNewView = (CView*) new CNewView;

CDocument* pCurrentDoc = ((CFrameWnd*)m_pMainWnd)->GetActiveDocument();
// 初始化一个CCreateContext对象来指向这个活动文档
// 利用这个环境(context),新视图在CView::OnCreate()中该视图被创建时。被加入到该文档。
CCreateContext newContext;
newContext.m_pNewViewClass = NULL;
newContext.m_pNewDocTemplate = NULL;
newContext.m_pLastView = NULL;
newContext.m_pCurrentFrame = NULL;
newContext.m_CurrentDoc = pCurrentDoc;

// 最初的活动视图的ID是AFX_IDW_PANE_FIRST,
// 对它加一使新增的视图工作在标准的文档/视图环境下
// 注意这一技巧不能用于切割视图图类CSplitterWnd的情况
UINT viewID = AFX_IDW_PANE_FIRST + 1;
CRect rect(0,0,0,0); // 将在后面重新设置大小

// 创建一个新视图,本例中该视图的生命周期与应用程序相同
// 应用程序关闭时会自动地清除视图对象
m_pNewView->Create(NULL, "AnyWindowName", WS_CHILD, rect, m_pMainWnd, viewID, &newContext);

// 当一个文档模板创建一个视图,消息WM_INITIALUPDATE会被自动地发出,
// 但是本代码必须明确地发出这个消息(这个消息的作用就是让视图对象调用自己的OnInitialUpdate函数进行初始化)
m_pNewView->SendMessage(WM_INITIALUPDATE, 0, 0);
// 加入的新代码到这里结束

(4)实现视图切换函数
在应用程序类实现文件的尾部添加如下的代码:
CView* CMyWinApp::SwitchView()
{
  CView* pActiveView = ((CFrameWnd*)m_pMainWnd)->GetActiveView();
  CView* pNewView = NULL;
  if (pActiveView == m_pOldView)
    pNewView = m_pNewView;
  else
    pNewView = m_pOldView;
  // 交换视图窗口ID,以便RecalcLayout()的工作
  // 从代码可以看出,当前的活动视图窗口的ID总是AFX_IDW_PANE_FIRST
  // 而且RecalcLayout()也只对这个ID的窗口工作
  #ifdef _WIN32
    UINT temp = ::GetWindowWord(pActiveView->m_hWnd, GWW_ID);
    ::SetWindowWord(pActiveView->m_hWnd, GWW_ID, ::GetWindowWord(pNewView->m_hWnd, GWW_ID));
    ::SetWindowWord(pNewView->m_hWnd, GWW_ID, temp);
  #else
    UINT temp = ::GetWindowLong(pActiveView->m_hWnd, GWL_ID);
    ::SetWindowLong(pActiveView->m_hWnd, GWL_ID, ::GetWindowLong(pNewView->m_hWnd, GWL_ID));
    ::SetWindowLong(pNewView->m_hWnd, GWL_ID, temp);
  #endif
  pAcitiveView->ShowWindow(SW_HIDE);
  pNewView->ShowWindow(SW_SHOW);
  ((CFrameWnd*)m_pMainWnd)->SetActiveView(pNewView);
  ((CFrameWnd*)m_pMainWnd)->RecalcLayout();
  pNewView->Invalidate();
  return pActiveView;
}

(5)增加对视图切换函数的调用代码
最后的工作就是在用程序需要的时候切换视图了,当然是通过调用SwitchView函数。有几种方式来调用:增加一个菜单项;或者在某些条件满足时在内部自动地切换。在程序内部任何地方都可以调用AfxGetApp(),返回一个指向CWinApp的指针。这样就可以获得对成员函数SwitchView的调用了。

(6)一些注释
CFrameWnd有一个CView对象指针成员m_pViewActive。CFrameWnd经由它与视图交互,激活视图或使之沉寂,该成员指针可通过CFrameWnd::GetActiveView()获取,通过CFrameWnd::SetActiveView()设置,通过CFrameWnd::SetActiveView()设置。
结构体CCreateContext的定义如下:
struct CCreateContext
{
  CRuntimeClass* m_pNewViewClass;
  CDocument * m_pCurrentDoc;
  CDocTemplate *m_pNewDocTemplate;
  CView *m_pLastView;
  CFrameWnd *m_pCurrentFrame;
  CCreateContext();
};
CView::OnInitialUpdate()由框架窗口调用,调用时机是视图第一次与文档挂接之后,但是尚未显示之前。这个函数将引起OnUpdate()的调用,因此程序员可以覆盖OnUpdate来做一些自己的工作。

方法(二)
假定一些基本情况。所有视图类从CFormView类派生,本例中常出现的一个视图类为CStringListView类。SDI类型应用。下面的叙述均根据MSDN上的一个例子程序。

在派生的文档类中(假定是CMyDoc)加入一个存储数据的容器类对象:
CStringList m_stringList;
当选择“新建”菜单时,将调用OnNewDocument函数。在这里对容器类对象进行初始化:
CString strFirst;
strFirst.LoadString(IDS_INITIAL_STRING);
m_stringList.AddTail(strFirst);
在退出应用程序时,要删除文档的内容。删除工作一般在DeleteContents()函数中进行,例如:
m_stringList.RemoveAll();
在文档的序列化与存储时,使用下面的代码:
m_stringList.Serialize(ar); // 注意CStringList派生自CObject,故而可以使用这个函数

在框架类对象中加入一个记录当前正在使用的视图的ID的变量:
UINT m_nCurrentExample;
另外还要在框架中加入切换视图的菜单命令函数以及命令界面更新函数:
void OnExample(UINT nCmdID);
void OnUpdateExampleUI(CCmdUI *pCmdUI);

在MainFrm.cpp的消息映射宏如下:
BEGIN_MESSAGE_MAP(CMAINFRAME, CFrameWnd)
ON_COMMAND_RANGE(ID_STRINGLIST, ID_MAP, OnExample)
ON_UPDATE_COMMAND_UI_RANGE(ID_STRINGLIST, ID_MAP, OnUpdateExampleUI)
//...
END_MESSAGE_MAP

如何完成视图的切换的?OnExample函数给出了详细的例子代码。
void CMainFrame::OnExample(UINT nCmdID)
{
  if (nCmdID == m_nCurrentExample)
    return; // 所选择的视图已在使用中
  CView* pOldActiveView = GetActiveView();
  ::SetWindowLong(pOldActiveView->m_hWnd, GWL_ID, m_nCurrentExample);
  CRuntimeClass* pNewViewClass;
  switch(nCmdID)
  {
   case ID_STRINGLIST:
      pNewViewClass = RUNTIME_CLASS(CStringListView);
      break;
   // ... ...
   case ID_MAPDWORDTOMYSTRUCT:
      pNewViewClass = RUNTIME_CLASS(CMapDWordToMyStructView);
      break;
   default:
      ASSERT(0);
      return;
   }// switch
  //下面创建一个新的视图
  CCreateContext context;
  context.m_pNewViewClass = pNewViewClass;
  context.m_pCurrentDoc = GetActiveDocument();
  CView* pNewView = STATIC_DOWNCAST(CView, CreateView(&context));
  if (pNewView != NULL)
  {
    // 已经创建了视图对象,但是尚不可见,也未激活
    pNewView->ShowWindow(SH_SHOW);
    pNewView->OnInitialUpdate();
    SetActiveView(pNewView);
    RecalcLayout();
    m_nCurrentExample = nCmdID;
    // 最后销毁旧的视图的窗口
    pOldActiveView->DestroyWindow();
  }
}

上面代码中用到一个宏函数:
STATIC_DOWNCAST(类名, 对象指针)
作用就是进行转型,把指针的类型转换到类名上来。

注意下面这条语句
::SetWindowLong(pOldActiveView->m_hWnd, GWL_ID, m_nCurrentExample);
把一个自定义的UINT型整数作为视图窗口的ID使用,而第一个方法中使用了一个系统定义的常量来作为视图窗口的ID。从此可以得到启发,在有多个视图的时候,视图窗口的ID是允许自定义的。

下面的就是命令更新函数了。
void CMainFrame::OnExampleUI(CCmdUI* pCmdUI)
{
  pCmdUI->SetCheck(pCmdUI->m_nID == m_nCurrentExample);
}

ON_COMMAND_RANGE把多个连续的一个范围内的命令ID映射到一个处理函数上,语法如下:
ON_COMMAND_RANGE(id1, id2, fn)
命令ID起于id1,止于id2。这个宏不能自动生成,必须手工编写。
看下面的例子代码:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
  // ... ...
  ON_COMMAND_RANGE(ID_FILE_MENUITEM1, ID_FILE_MENUITME2, OnFileMenuItems)
END_MESSAGE_MAP

void CMainFrame::OnFileMenuItems(UINT nID)
{
  CMenu* mmenu = GetMenu();
  CMenu* submenu = mmenu->GetSubmenu(0);
  submenu->CheckMenuRadioItem(ID_FILE_MENUITEM1, ID_FILE_MENUITEM3, nID, MF_BYCOMMAND);
}
要注意,处理函数的声明要放到头文件中DECLARE_MESSAGE_MAP宏语句的前一行,如下所示:
afx_msg void OnFileMenuItems(UINT nID);
DECLARE_MESSAGE_MAP()
命令用户界面更新函数的参数与普通的一样,只有一个参数(CCmdUI *pCmdUI)。因为CCmdUI有一个成员m_nID能自动记住当前的命令ID。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值