【VS开发】CFormView
CFormView是MFC使用无模式对话框的一个典型例子。CFormView是基于对话框模板创建的视,它的直接基类是CSrcollView,CSrcollView的直接基类才是CView。所以,这里先对CScorllView作一个简要的介绍。
CScrollView
CScrollView继承了CView的特性,并且增加了如下的功能:
(1)管理映射模式、窗口尺寸、视口尺寸(Map mode、Window and Viewport size)。Window and Viewport size用来完成页面空间到设备空间的转换。
(2)自动管理滚动条,响应滚动条消息。
为了实现这些功能,CScrollView覆盖CView或者CWnd的一些虚拟函数和消息处理函数,添加了一些新的函数,当然也设计了新的成员变量。
CscrollView新的成员变量
protected:
int m_nMapMode;
CSize m_totalLog; // total size in logical units (no rounding)
CSize m_totalDev; // total size in device units
CSize m_pageDev; // per page scroll size in device units
CSize m_lineDev; // per line scroll size in device units
BOOL m_bCenter; // Center output if larger than total size
BOOL m_bInsideUpdate; // internal state for OnSize callback
CScrollView新的成员函数,用来完成和滚动操作、滚动条等有关的功能
void SetScaleToFitSize(SIZE sizeTotal);
void SetScrollSizes(int nMapMode, SIZE sizeTotal,
const SIZE& sizePage = sizeDefault,
const SIZE& sizeLine = sizeDefault);
这两个函数中的尺寸大小按逻辑单位计算。
SetScaleToFitSize设置视口尺寸为当前的窗口尺寸,这样,在没有滚动条时,逻辑视的内容被放大或者缩小到正好窗口大小。
SetScrollSizes设置窗口的映射模式,窗口尺寸,页和行尺寸。sizeDefualt被定义为(0,0)。
下面几个函数用来实现滚动或者得到滚动条相关的信息
void ScrollToPosition(POINT pt); // set upper left position
void FillOutsideRect(CDC* pDC, CBrush* pBrush);
void ResizeParentToFit(BOOL bShrinkOnly = TRUE);
CPoint GetScrollPosition() const; // upper corner of scrolling
CSize GetTotalSize() const; // logical size
下面两个函数使用了设备坐标单位
CPoint GetDeviceScrollPosition() const;
void GetDeviceScrollSizes(int& nMapMode, SIZE& sizeTotal,
SIZE& sizePage, SIZE& sizeLine) const;
覆盖的消息处理函数
处理WM_SIZE的OnSize;
处理WM_HSCROLL的OnHScroll;
处理WM_VSCROLL的OnVScroll;
覆盖的虚拟函数
CWnd的CalcWindowRect
CView的OnPrepareDC、OnScroll、OnScrollBy
用于DEBUG的Dump和AssertValid
这里,覆盖的消息处理函数和虚拟函数共同完成对滚动条、滚动消息的处理。
在CSrcollView的实现涉及到许多和Windows映射模式、坐标转换等相关的函数的使用。这里,不作具体讨论。
CFormView
CFormView派生于CSrcollView,本身没有增加新的函数,但覆盖了一些基类的虚拟函数,增加了几个成员变量(以下列出的不包含OLE处理)。
增加的成员变量
LPCTSTR m_lpszTemplateName;
CCreateContext* m_pCreateContext;
HWND m_hWndFocus; // last window to have focus
m_lpszTemplateName用来保存创建视图的对话框模板的名称,_pCreateContext用来保存创建上下文,m_hWndFocus用来保存最近一次拥有焦点的控制窗口。在构造CFormView对象时,构造函数把有关信息保存到成员变量中,如下所示:
CFormView::CFormView(LPCTSTR lpszTemplateName)
{
m_lpszTemplateName = lpszTemplateName;
m_pCreateContext = NULL;
m_hWndFocus = NULL; // focus window is font
}
覆盖的虚拟函数
virtual void OnDraw(CDC* pDC); // MFC缺省处理空
virtual BOOL Create(LPCTSTR, LPCTSTR, DWORD,
const RECT&, CWnd*, UINT, CCreateContext*);
virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual void OnActivateView(BOOL, CView*, CView*);
virtual void OnActivateFrame(UINT, CFrameWnd*);
创建基于对话框的视窗口,不同于创建普通视窗口(前者调用CWnd::CreateEx,后者调用CWnd::CreateDlg),故需要覆盖Create虚拟函数。
覆盖PreTranslateMessage是为了过滤对话框消息,把一些消息让CFormView对象来处理。
覆盖了两个消息处理函数:
afx_msg int OnCreate(LPCREATESTRUCT lpcs);
afx_msg void OnSetFocus(CWnd* pOldWnd);
下面,分析几个函数作。Create函数解释了MFC如何使用一个对话框作为视的方法,PreTranslateMessage显示了CFormView不同于CDialog的实现。
CFormView的创建
设计CFormView的创建函数,必须考虑两个问题:
首先,CFormView是一个视,其创建函数必须是一个虚拟函数,原型必须和CWnd::Create(LPSTR…pContext)函数一致,见图5-13视的创建。其次,CFormView使用了对话框创建函数和对话框“窗口类”来创建视,但必须作一些处理使得该窗口具备视的特征。
Create的实现如下:
BOOL CFormView::Create(LPCTSTR ,
LPCTSTR ,
DWORD dwRequestedStyle, const RECT& rect, CWnd* pParentWnd, UINT nID,
CCreateContext* pContext)
{
ASSERT(pParentWnd != NULL);
ASSERT(m_lpszTemplateName != NULL);
m_pCreateContext = pContext; // save state for later OnCreate
#ifdef _DEBUG
// dialog template must exist and be invisible with WS_CHILD set
if (!_AfxCheckDialogTemplate(m_lpszTemplateName, TRUE))
{
ASSERT(FALSE); // invalid dialog template name
PostNcDestroy(); // cleanup if Create fails too soon
return FALSE;
}
#endif //_DEBUG
//若common control window类还没有注册,则注册
VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG));
// call PreCreateWindow to get prefered extended style
CREATESTRUCT cs; memset(&cs, 0, sizeof(CREATESTRUCT));
if (dwRequestedStyle == 0)
dwRequestedStyle = AFX_WS_DEFAULT_VIEW;
cs.style = dwRequestedStyle;
if (!PreCreateWindow(cs))
return FALSE;
//::CreateDialogIndirect间接被调用来创建一个无模式对话框
if (!CreateDlg(m_lpszTemplateName, pParentWnd))
return FALSE;
//创建对话框时,OnCreate被调用,m_pCreateContext的作用结束了
m_pCreateContext = NULL;
// we use the style from the template - but make sure that
// the WS_BORDER bit is correct
// the WS_BORDER bit will be whatever is in dwRequestedStyle
ModifyStyle(WS_BORDER|WS_CAPTION, cs.style & (WS_BORDER|WS_CAPTION));
ModifyStyleEx(WS_EX_CLIENTEDGE, cs.dwExStyle & WS_EX_CLIENTEDGE);
SetDlgCtrlID(nID);
CRect rectTemplate;
GetWindowRect(rectTemplate);
SetScrollSizes(MM_TEXT, rectTemplate.Size());
// initialize controls etc
if (!ExecuteDlgInit(m_lpszTemplateName))
return FALSE;
// force the size requested
SetWindowPos(NULL, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOZORDER|SWP_NOACTIVATE);
// make visible if requested
if (dwRequestedStyle & WS_VISIBLE)
ShowWindow(SW_NORMAL);
return TRUE;
}
从Create的实现过程可以看出,CreateDialog在创建对话框时使用了Windows预定义的对话框“窗口类”,PreCreateWindow返回的cs在创建对话框窗口时并没有得到体现,所以在CFormView::Create调用PreCreateWindow让程序员修改“窗口类”的风格之后,还要调用ModifyStyle和ModifyStyleEx来按PreCreateWindow返回的cs的值修改窗口风格。
回顾视窗口的创建过程,Create函数被CFrameWnd::CreateView所调用,参数nID取值AFX_IDW_PANE_FIRST。由于CreateDlg设置对话框窗口的ID为对话框模板的ID,所以需要调用函数SetDlgCtrlID(nID)设置视窗口ID为nID(即AFX_IDW_PANE_FIRST)。
由于CFormView是从CScrollView继承,所以调用SetScrollSize设置映射模式,窗口尺寸等。
完成上述动作之后,初始化对话框的控制子窗口。
最后,必要的话,显示视窗口。
这样,一个无模式对话框被创建,它被用作当前MDI窗口或者MDI子窗口的视。如同CDialog的消息处理一样,必要时,消息或者事件将传递给视原来的窗口过程(无模式对话框的原窗口过程)处理,其他的消息处理和通常视一样。
由于是调用对话框创建函数创建视窗口,所以不能向::CreateWindowEX传递创建上下文指针,于是把它保存到成员变量m_pCreateContext中,在OnCreate时使用。OnCreate的实现如下:
int CFormView::OnCreate(LPCREATESTRUCT lpcs)
{
//既然不能通过CreateDialog使用参数传递的方法得到创建上下文
//参数,则使用一个成员变量来传递
return CScrollView::OnCreate(lpcs);
}
CFormView的消息预处理
现在,讨论CFormView 的PreTranslateMessage函数。CDialog覆盖函数PreTranslateMessage的主要目的是处理Tooltip消息、Escape键盘消息和Dialog消息。CFormView覆盖该函数的目的是处理Tooltip消息和Dialog消息。CFormView和CDialog不同之处在于CFormView是一个视,故在把键盘消息当Dialog消息处理之前,必须优先让其父窗口检查按下的键是否是快捷键。PreTranslateMessage函数实现如下:
BOOL CFormView::PreTranslateMessage(MSG* pMsg)
{
ASSERT(pMsg != NULL);
ASSERT_VALID(this);
ASSERT(m_hWnd != NULL);
//过滤Tooltip消息
if (CView::PreTranslateMessage(pMsg))
return TRUE;
//SHIFT+F1上下文帮助模式下,不处理Dialog消息
CFrameWnd* pFrameWnd = GetTopLevelFrame();
if (pFrameWnd != NULL && pFrameWnd->m_bHelpMode)
return FALSE;
//既然IsDialogMessage将把窗口快捷键解释成Dialog消息
//所以在此先调用所有父边框窗口的消息预处理函数
pFrameWnd = GetParentFrame(); // start with first parent frame
while (pFrameWnd != NULL)
{
// allow owner & frames to translate before IsDialogMessage does
if (pFrameWnd->PreTranslateMessage(pMsg))
return TRUE;
// try parent frames until there are no parent frames
pFrameWnd = pFrameWnd->GetParentFrame();
}
// 过滤来自子窗口的消息或者给对话框的消息
return PreTranslateInput(pMsg);
}
由于CFormView是一个视,不是模式对话框,所以它首先要把消息给父窗口(MDI子窗口或者MDI窗口)预处理,如果它们不能处理,则调用PreTranslateInput来过滤Dialog消息。
CFormView的输入焦点
CFormView另一个特性是:在和用户交互中,如果用户离开视窗口,则必须保存CFormView视的哪个控制子窗口拥有输入焦点,以便在重新激活视窗口时,原来的那个窗口重新获得输入焦点。所以,CFormView覆盖了虚拟函数OnActivateView和OnActiveFrame,以便在视窗口失去激活时把它的当前输入焦点保存到成员变量m_hWndFocus中。
为了在适当时候恢复输入焦点,CFormView覆盖了消息处理函数OnSetFocus,以便在视获得输入焦点时把输入焦点传递给m_hWndFocus(如果非空)。
至此,MFC实现对话框的处理分析完毕。
CScrollView
CScrollView继承了CView的特性,并且增加了如下的功能:
(1)管理映射模式、窗口尺寸、视口尺寸(Map mode、Window and Viewport size)。Window and Viewport size用来完成页面空间到设备空间的转换。
(2)自动管理滚动条,响应滚动条消息。
为了实现这些功能,CScrollView覆盖CView或者CWnd的一些虚拟函数和消息处理函数,添加了一些新的函数,当然也设计了新的成员变量。
CscrollView新的成员变量
protected:
int m_nMapMode;
CSize m_totalLog; // total size in logical units (no rounding)
CSize m_totalDev; // total size in device units
CSize m_pageDev; // per page scroll size in device units
CSize m_lineDev; // per line scroll size in device units
BOOL m_bCenter; // Center output if larger than total size
BOOL m_bInsideUpdate; // internal state for OnSize callback
CScrollView新的成员函数,用来完成和滚动操作、滚动条等有关的功能
void SetScaleToFitSize(SIZE sizeTotal);
void SetScrollSizes(int nMapMode, SIZE sizeTotal,
const SIZE& sizePage = sizeDefault,
const SIZE& sizeLine = sizeDefault);
这两个函数中的尺寸大小按逻辑单位计算。
SetScaleToFitSize设置视口尺寸为当前的窗口尺寸,这样,在没有滚动条时,逻辑视的内容被放大或者缩小到正好窗口大小。
SetScrollSizes设置窗口的映射模式,窗口尺寸,页和行尺寸。sizeDefualt被定义为(0,0)。
下面几个函数用来实现滚动或者得到滚动条相关的信息
void ScrollToPosition(POINT pt); // set upper left position
void FillOutsideRect(CDC* pDC, CBrush* pBrush);
void ResizeParentToFit(BOOL bShrinkOnly = TRUE);
CPoint GetScrollPosition() const; // upper corner of scrolling
CSize GetTotalSize() const; // logical size
下面两个函数使用了设备坐标单位
CPoint GetDeviceScrollPosition() const;
void GetDeviceScrollSizes(int& nMapMode, SIZE& sizeTotal,
SIZE& sizePage, SIZE& sizeLine) const;
覆盖的消息处理函数
处理WM_SIZE的OnSize;
处理WM_HSCROLL的OnHScroll;
处理WM_VSCROLL的OnVScroll;
覆盖的虚拟函数
CWnd的CalcWindowRect
CView的OnPrepareDC、OnScroll、OnScrollBy
用于DEBUG的Dump和AssertValid
这里,覆盖的消息处理函数和虚拟函数共同完成对滚动条、滚动消息的处理。
在CSrcollView的实现涉及到许多和Windows映射模式、坐标转换等相关的函数的使用。这里,不作具体讨论。
CFormView
CFormView派生于CSrcollView,本身没有增加新的函数,但覆盖了一些基类的虚拟函数,增加了几个成员变量(以下列出的不包含OLE处理)。
增加的成员变量
LPCTSTR m_lpszTemplateName;
CCreateContext* m_pCreateContext;
HWND m_hWndFocus; // last window to have focus
m_lpszTemplateName用来保存创建视图的对话框模板的名称,_pCreateContext用来保存创建上下文,m_hWndFocus用来保存最近一次拥有焦点的控制窗口。在构造CFormView对象时,构造函数把有关信息保存到成员变量中,如下所示:
CFormView::CFormView(LPCTSTR lpszTemplateName)
{
m_lpszTemplateName = lpszTemplateName;
m_pCreateContext = NULL;
m_hWndFocus = NULL; // focus window is font
}
覆盖的虚拟函数
virtual void OnDraw(CDC* pDC); // MFC缺省处理空
virtual BOOL Create(LPCTSTR, LPCTSTR, DWORD,
const RECT&, CWnd*, UINT, CCreateContext*);
virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual void OnActivateView(BOOL, CView*, CView*);
virtual void OnActivateFrame(UINT, CFrameWnd*);
创建基于对话框的视窗口,不同于创建普通视窗口(前者调用CWnd::CreateEx,后者调用CWnd::CreateDlg),故需要覆盖Create虚拟函数。
覆盖PreTranslateMessage是为了过滤对话框消息,把一些消息让CFormView对象来处理。
覆盖了两个消息处理函数:
afx_msg int OnCreate(LPCREATESTRUCT lpcs);
afx_msg void OnSetFocus(CWnd* pOldWnd);
下面,分析几个函数作。Create函数解释了MFC如何使用一个对话框作为视的方法,PreTranslateMessage显示了CFormView不同于CDialog的实现。
CFormView的创建
设计CFormView的创建函数,必须考虑两个问题:
首先,CFormView是一个视,其创建函数必须是一个虚拟函数,原型必须和CWnd::Create(LPSTR…pContext)函数一致,见图5-13视的创建。其次,CFormView使用了对话框创建函数和对话框“窗口类”来创建视,但必须作一些处理使得该窗口具备视的特征。
Create的实现如下:
BOOL CFormView::Create(LPCTSTR ,
LPCTSTR ,
DWORD dwRequestedStyle, const RECT& rect, CWnd* pParentWnd, UINT nID,
CCreateContext* pContext)
{
ASSERT(pParentWnd != NULL);
ASSERT(m_lpszTemplateName != NULL);
m_pCreateContext = pContext; // save state for later OnCreate
#ifdef _DEBUG
// dialog template must exist and be invisible with WS_CHILD set
if (!_AfxCheckDialogTemplate(m_lpszTemplateName, TRUE))
{
ASSERT(FALSE); // invalid dialog template name
PostNcDestroy(); // cleanup if Create fails too soon
return FALSE;
}
#endif //_DEBUG
//若common control window类还没有注册,则注册
VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG));
// call PreCreateWindow to get prefered extended style
CREATESTRUCT cs; memset(&cs, 0, sizeof(CREATESTRUCT));
if (dwRequestedStyle == 0)
dwRequestedStyle = AFX_WS_DEFAULT_VIEW;
cs.style = dwRequestedStyle;
if (!PreCreateWindow(cs))
return FALSE;
//::CreateDialogIndirect间接被调用来创建一个无模式对话框
if (!CreateDlg(m_lpszTemplateName, pParentWnd))
return FALSE;
//创建对话框时,OnCreate被调用,m_pCreateContext的作用结束了
m_pCreateContext = NULL;
// we use the style from the template - but make sure that
// the WS_BORDER bit is correct
// the WS_BORDER bit will be whatever is in dwRequestedStyle
ModifyStyle(WS_BORDER|WS_CAPTION, cs.style & (WS_BORDER|WS_CAPTION));
ModifyStyleEx(WS_EX_CLIENTEDGE, cs.dwExStyle & WS_EX_CLIENTEDGE);
SetDlgCtrlID(nID);
CRect rectTemplate;
GetWindowRect(rectTemplate);
SetScrollSizes(MM_TEXT, rectTemplate.Size());
// initialize controls etc
if (!ExecuteDlgInit(m_lpszTemplateName))
return FALSE;
// force the size requested
SetWindowPos(NULL, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOZORDER|SWP_NOACTIVATE);
// make visible if requested
if (dwRequestedStyle & WS_VISIBLE)
ShowWindow(SW_NORMAL);
return TRUE;
}
从Create的实现过程可以看出,CreateDialog在创建对话框时使用了Windows预定义的对话框“窗口类”,PreCreateWindow返回的cs在创建对话框窗口时并没有得到体现,所以在CFormView::Create调用PreCreateWindow让程序员修改“窗口类”的风格之后,还要调用ModifyStyle和ModifyStyleEx来按PreCreateWindow返回的cs的值修改窗口风格。
回顾视窗口的创建过程,Create函数被CFrameWnd::CreateView所调用,参数nID取值AFX_IDW_PANE_FIRST。由于CreateDlg设置对话框窗口的ID为对话框模板的ID,所以需要调用函数SetDlgCtrlID(nID)设置视窗口ID为nID(即AFX_IDW_PANE_FIRST)。
由于CFormView是从CScrollView继承,所以调用SetScrollSize设置映射模式,窗口尺寸等。
完成上述动作之后,初始化对话框的控制子窗口。
最后,必要的话,显示视窗口。
这样,一个无模式对话框被创建,它被用作当前MDI窗口或者MDI子窗口的视。如同CDialog的消息处理一样,必要时,消息或者事件将传递给视原来的窗口过程(无模式对话框的原窗口过程)处理,其他的消息处理和通常视一样。
由于是调用对话框创建函数创建视窗口,所以不能向::CreateWindowEX传递创建上下文指针,于是把它保存到成员变量m_pCreateContext中,在OnCreate时使用。OnCreate的实现如下:
int CFormView::OnCreate(LPCREATESTRUCT lpcs)
{
//既然不能通过CreateDialog使用参数传递的方法得到创建上下文
//参数,则使用一个成员变量来传递
return CScrollView::OnCreate(lpcs);
}
CFormView的消息预处理
现在,讨论CFormView 的PreTranslateMessage函数。CDialog覆盖函数PreTranslateMessage的主要目的是处理Tooltip消息、Escape键盘消息和Dialog消息。CFormView覆盖该函数的目的是处理Tooltip消息和Dialog消息。CFormView和CDialog不同之处在于CFormView是一个视,故在把键盘消息当Dialog消息处理之前,必须优先让其父窗口检查按下的键是否是快捷键。PreTranslateMessage函数实现如下:
BOOL CFormView::PreTranslateMessage(MSG* pMsg)
{
ASSERT(pMsg != NULL);
ASSERT_VALID(this);
ASSERT(m_hWnd != NULL);
//过滤Tooltip消息
if (CView::PreTranslateMessage(pMsg))
return TRUE;
//SHIFT+F1上下文帮助模式下,不处理Dialog消息
CFrameWnd* pFrameWnd = GetTopLevelFrame();
if (pFrameWnd != NULL && pFrameWnd->m_bHelpMode)
return FALSE;
//既然IsDialogMessage将把窗口快捷键解释成Dialog消息
//所以在此先调用所有父边框窗口的消息预处理函数
pFrameWnd = GetParentFrame(); // start with first parent frame
while (pFrameWnd != NULL)
{
// allow owner & frames to translate before IsDialogMessage does
if (pFrameWnd->PreTranslateMessage(pMsg))
return TRUE;
// try parent frames until there are no parent frames
pFrameWnd = pFrameWnd->GetParentFrame();
}
// 过滤来自子窗口的消息或者给对话框的消息
return PreTranslateInput(pMsg);
}
由于CFormView是一个视,不是模式对话框,所以它首先要把消息给父窗口(MDI子窗口或者MDI窗口)预处理,如果它们不能处理,则调用PreTranslateInput来过滤Dialog消息。
CFormView的输入焦点
CFormView另一个特性是:在和用户交互中,如果用户离开视窗口,则必须保存CFormView视的哪个控制子窗口拥有输入焦点,以便在重新激活视窗口时,原来的那个窗口重新获得输入焦点。所以,CFormView覆盖了虚拟函数OnActivateView和OnActiveFrame,以便在视窗口失去激活时把它的当前输入焦点保存到成员变量m_hWndFocus中。
为了在适当时候恢复输入焦点,CFormView覆盖了消息处理函数OnSetFocus,以便在视获得输入焦点时把输入焦点传递给m_hWndFocus(如果非空)。
至此,MFC实现对话框的处理分析完毕。