作者:朱金灿
来源:http://blog.csdn.net/clever101
本文拙文《在对话框上创建视图的方法总结》,有网友朋友来邮件反映:在对话框上可以成功创建视图,但是用鼠标单击视图的时候,就会出现Assert错误,说ViewCore.cpp 252行中有错。具体是:CView类的onmouseActive函数中ASSERT(pParentFrame == pDesktopWnd || pDesktopWnd->IsChild(pParentFrame)),出错!
下班回家我决心亲自试验一番,我在我的对话框程序上添加了各种鼠标消息,包括WM_LBUTTONDOWN、WM_MOUSEMOVE和WM_LBUTTONUP,但是都没有出错。我一下抓瞎了,只好上论坛发贴求助,但是一时之间也没多少人回应。我只好直接在google上搜索ASSERT(pParentFrame == pDesktopWnd || pDesktopWnd->IsChild(pParentFrame))这个断言错误。终于搜到了一篇相关文章《如何在没有文档的情况下使用CView及其派生类》。阅读之下我算是明白了为什么会出错。首先我们明确断言错误是在CView::OnMouseActivate函数上发生的,那么让我们看看MSDN对OnMouseActivate函数的解释:
The framework calls this member function when the cursor is in an inactive window and the user presses a mouse button.
大意是当光标在一个非活动窗口和用户按下鼠标时框架就会调用这个消息。那我们再看看CView::OnMouseActivate函数的源码:
int CView::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message) { int nResult = CWnd::OnMouseActivate(pDesktopWnd, nHitTest, message); if (nResult == MA_NOACTIVATE || nResult == MA_NOACTIVATEANDEAT) return nResult; // frame does not want to activate CFrameWnd* pParentFrame = GetParentFrame(); if (pParentFrame != NULL) { // eat it if this will cause activation ASSERT(pParentFrame == pDesktopWnd || pDesktopWnd->IsChild(pParentFrame)); // either re-activate the current view, or set this view to be active CView* pView = pParentFrame->GetActiveView(); HWND hWndFocus = ::GetFocus(); if (pView == this && m_hWnd != hWndFocus && !::IsChild(m_hWnd, hWndFocus)) { // re-activate this view OnActivateView(TRUE, this, this); } else { // activate this view pParentFrame->SetActiveView(this); } } return nResult; }
我们着重分析一下代码:
CFrameWnd* pParentFrame = GetParentFrame(); if (pParentFrame != NULL) { // eat it if this will cause activation ASSERT(pParentFrame == pDesktopWnd || pDesktopWnd->IsChild(pParentFrame));
在对话框程序上pDesktopWnd是对话框指针,视图类的父窗口也是对话框指针,那么ASSERT(pParentFrame == pDesktopWnd || pDesktopWnd->IsChild(pParentFrame))这句就不会出错,因为CView::GetParentFrame()的往他的祖先窗口中上溯,直到找到一个是FrameWnd类型的窗口之后,返回这个窗口对象的指针(如果没有的话,返回NULL),在对话框程序中这么要么为NULL,要么为对话框指针;但是假如是单文档程序呢,情况就不一样了,CView::GetParentFrame()必定是返回单文档程序的框架指针,而pDesktopWnd指针则是对话框指针,二者不相等,那么我们再看看pDesktopWnd->IsChild(pParentFrame)是否为TRUE,很显然框架窗口不可能是对话框窗口的父窗口。
那么怎么解决这个问题呢?网上的一种办法是:在对话框的视图类上嵌入一个框架窗口,使得pDesktopWnd->IsChild(pParentFrame)这句断言为TRUE。具体是这样做的:
在对话框类上定义一个框架类指针:
CFrameWnd * m_pFrame ;
然后在对话框类的构造函数上将框架指针初始化为NULL:
m_pFrame = NULL;
然后在OnInitDialog函数创建视图,具体代码如下:
UINT TargetCtrlID = IDC_STATIC_VIEW; CWnd *pWnd = this->GetDlgItem(TargetCtrlID); CRect RectTargetCtrl; pWnd->GetWindowRect(RectTargetCtrl); this->ScreenToClient(RectTargetCtrl); m_pFrame= new CFrameWnd(); m_pFrame->Create(NULL,NULL,WS_VISIBLE|WS_CHILD,RectTargetCtrl,this); m_pView = (CMyView*)RUNTIME_CLASS(CMyView)->CreateObject(); //在目标位置动态创建视图 if (NULL==m_pView) { return FALSE; } m_pView->Create(NULL, NULL,WS_VISIBLE|WS_CHILD, RectTargetCtrl,m_pFrame,TargetCtrlID);
如果你用我的第二种方法创建视图的话,那么具体代码如下:
UINT TargetCtrlID = IDC_STATIC_VIEW; CWnd *pWnd = this->GetDlgItem(TargetCtrlID); CRect RectTargetCtrl; pWnd->GetWindowRect(RectTargetCtrl); this->ScreenToClient(RectTargetCtrl); m_pFrame= new CFrameWnd(); m_pFrame->Create(NULL,NULL,WS_VISIBLE|WS_CHILD,RectTargetCtrl,this); CCreateContext context; context.m_pCurrentDoc = NULL;//不要文档为空 context.m_pCurrentFrame = m_pFrame; context.m_pLastView = NULL;//前一个视图为空 context.m_pNewDocTemplate = NULL;//文档模板为空 context.m_pNewViewClass = RUNTIME_CLASS(CMyView); //1.动态调用CreateObject创建一个对象并获得指针 m_pView = (CMyView*)context.m_pNewViewClass->CreateObject();//通过指针创建视图对象 //以下代码参考CFrameWnd类中的CreateView函数 if (m_pView==NULL) { TRACE1("Warning: Dynamic create of view type %hs failed.\n", context.m_pNewViewClass->m_lpszClassName); } ASSERT_KINDOF(CWnd,m_pView); //2.真正创建视图窗口 if (!m_pView->Create(NULL,NULL,WS_VISIBLE|WS_CHILD,RectTargetCtrl,m_pFrame, AFX_IDW_PANE_FIRST,&context)) { TRACE0("Warning: Couldn't create view for frame.\n"); return FALSE; }
这样你怎么在视图上单击都不会出错了,可能你会怀疑m_pFrame= new CFrameWnd(),而没有相应的delete语句,这会不会导致内存泄漏呢?不过经过我测试,其中并没有内存泄露,具体原因,一时我也没有找到,可能DestroyWindow函数把自己的内存delete掉了,MFC的内部机制有时实在诡异。
说实话,我觉得这种做法稍显繁琐。我们其实可以换一种思路,既然我们是在CView::OnMouseActivate函数的断言出错,那么我们重写WM_MOUSEACTIVE消息的响应函数不就可以吗?我们就这样试一试。
依然采用我先前两种创建视图的方法,不用什么框架类。然后在你自定义的视图类上重写WM_MOUSEACTIVATE的响应函数,具体代码如下:
int CMyView::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message) { // TODO: 在此添加消息处理程序代码和/或调用默认值 // return CView::OnMouseActivate(pDesktopWnd, nHitTest, message); return CWnd::OnMouseActivate(pDesktopWnd, nHitTest, message); }结果测试,这种办法也完全可行。
后来我检讨了自己思路,我发现了自己开始的思路是完全错误的,我觉得既然是ASSERT(pParentFrame ==pDesktopWnd||pDesktopWnd->IsChild(pParentFrame))出错了,我就应该先分析这句断言为什么出错,而不是忙着去搜索资料。这个道理有点像前天在CCTV 6看到的那部《危机时刻》的一个情节:一车翻车了,排爆大队的副大队长忙着怎么去封锁铁路和疏散村民,而水平更高的大队长却先分析出翻车的是火,然后对症下药地调来一辆消防车把淋湿,一举化除危机。
参考文献:
如何在没有文档的情况下使用CView及其派生类