I tried to create a View on a Dialog, and thus looked up all the articles on the web. There are several attempts to do this, but more or less all of them produce errors and aren't too useful in my opinion. So I decided to go a different way. I created a new Window type (called CDialogWindow in my demo project) by deriving the class from CFrameWnd. In this CDialogWindow I created a View using Jorge Vigils code from http://www.codeguru.com/doc_view/ReplacingView.shtml (I modified it of course, but am nonetheless grateful to Jorge). This View was a CScrollView which was my main intention, and perhaps this is also yours. Just imagine a task like displaying a bitmap on a dialog in full-size, which doesn't fit in the Dialog form. My own task was to display a video image generated by an ActiveX-Control. Instead of really displaying this image in a dialog, I created the just mentioned view in the CDialogWindow and added a CDialogBar to this window. The result looks nearly like a dialog containing a view.
But unfortunately I have to deal with an unexpected disadvantage using this simple technique. As the CDialogBar isn't connected with an own class but with the CDialogWindow Class, it is not possible to connect variables with the controls. This means of course, that all the advanced controls like spin buttons, scroll bars etc. which need to be represented by a variable of "Control" type, cannot be used. The functionality of the Window is reduced to input and output in text fields using the methods GetDlgItemInt(), GetDlgItemText(), SetDlgItemInt(), SetDlgItemText(). Perhaps someone can improve my project and tell me how to use all sort of controls in such a window.
As you can see in the demo project, I have added my CDialogWindow to an MDI project, not using any of the classes created by the application wizard. So if you take my project as a start point, you still have all the functionality of an MDI-app. Mainly I haven't used the generated view class but a class which I imported from another project, because this class was good for my purpose. Just to show you how the window works, I have replaced my video image by a simple text. What does this mean for you, if you want to copy some of my code:
Copy the CDialogWindow class to your project and create an object with the DoModeless() method as you can see it in the project. If you also want to display anything but not a document, also copy the CExtraView class and modify the OnDraw() method in order to make it display your stuff. If you want to create a view of a document or create a view from your own view class, just replace the class name in the RUNTIME_CLASS Macro in CDialogWindow::OnCreateClient() and don't forget to declare CVideoWindow friend of your own view class. Of course you will have to do some other little modifications like including the header files and so on.Pleas write me an email if you could improve my classes, especially, if you achieve to use all sort of controls in the dialog bar. I'm sorry, but I generated the demo project using the German version of the SDK. Thus the auto-generated comments are in German. This should not prevent you from understanding the code, as all pieces of my own code are commented in English.
Environment developed: VC6 Win 2K]
To create a view you normally follow the Microsoft's Document template model. (i.e) Single document template or multi doc template. You can pass three runtime classes to the constructors:
CSingleDocTemplate( UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass );OR
CMultiDocTemplate( UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass );When the document template is added, frame (child for MDI , main frame for SDI), document, and a view is created dynamically and added to the application's document template list.
But some times you may need to create a view without using the default document template classes. For instance, if you are to create a view in dialog, you can not use the regular way of document template. In those cases you can use the following method:
BOOL CVwindlgDlg::OnInitDialog() { ... CCreateContext pContext; /** * Note:CDialig derived pointer is converted to * CWnd pointer (a common base class for CDialog and CFrameWnd). * Thus casting it back to CFrameWnd is also easy. */ CWnd* pFrameWnd = this; pContext.m_pCurrentDoc = new CMyDocument; pContext.m_pNewViewClass = RUNTIME_CLASS(CMyVw); CMyVw *pView = (CMyVw *) ((CFrameWnd*)pFrameWnd)->CreateView(&pContext); ASSERT(pView); pView->ShowWindow(SW_NORMAL); /** * After a view is created, resize that to * have the same size as the dialog. */ CRect rectWindow; GetWindowRect(rectWindow); /** * Leave a little space for border and title... */ rectWindow.right += 15; rectWindow.top -= 10; pView->MoveWindow(rectWindow); CString str(AfxGetApp()->m_lpCmdLine); /** * Note: "CMyVw" is a CHTMLView derived class to add some * spice to the view, I have provided a HTML page * to which it navigates when the dialog is up. */ char strPath[255]; ::GetCurrentDirectory(255,(LPSTR)(LPCSTR)strPath); strcat(strPath,"//defaultpage.html"); pView->Navigate(strPath); .... return TRUE; // return TRUE unless you set the // focus to a control }通常在程序中,我们需要处理并且显示一些数据,将显示部分如果放到视图中的话,我们将可以忽略大部分的窗口交互的细节,将注意力关注在数据的显示上。
本文是在处理如下的情况中提出的:在数据采集和分析的时候,常常需要观看很多的数据。我们不可能在程序的主界面上显示。我们需要一个弹出窗口来显示,可以随时的关闭之,也就是说这样的窗口应该动态的生成,数量不定(这样限制了我们使用切分视图的选择)。对于这些弹出窗口,我们可以自己写一个CWnd的派生类,自然这是完全可以的,但是这样要求你可能处理滚动等等的一些列消息(我曾经在一个显示图像灰度直方图、以及二值图像投影等中遇到过要求这样显示的情况,当时就是自己从CWnd派生了一个类来显示数据,由于图像比较大,一屏显示不完整,所以要给窗口加滚动条,这样在处理这些消息上花费了我大量的时间,而真正显示数据的代码却很少)。因此我也在一直想使用MFC已经封装的非常好的CView类及其派生类来显示数据。由于我主要用到的就是CScrollView,因此我也以它为列来讲述怎么用视图。
基于对话框和SDI结构的实现上有不同细节, 必须分开了说:
1、如果你的程序是一个基于对话框的程序的话(指的是如利用MFC向导生成的对话框程序之类),那么事情就很简单: 首先,很自然的在Insert/New Class中插入一个类,选择CScrollView做基类假设你自己的视图类命名为CMyScrollView。 在对话框(不论是主界面的对话框还是新弹出的对话框,都一样)的OnInitDialog中加上如下的代码: CRect rectWndClient; GetClientRect(&rectWndClient);
CRuntimeClass *pViewRuntimeClass=RUNTIME_CLASS(CMyScrollView);
CMyScrollView *pView=(CMyScrollView*)pViewRuntimeClass->CreateObject(); pView->Create(NULL,NULL,WS_VISIBLE|WS_CHILD, rectWndClient, this,123,NULL); pView->OnInitialUpdate(); 说明: 1)、由于CView及其派生类的构造函数是保护成员,所以采用RuntimeClass方式来构造 对对象, 2)、在Create函数中第一个参数必须有WS_CHILD属性,表明视图是一个子窗口 3)、第四个参数rectWndClient可以视情况,改变,因为你可能只想在对话框的某个区域 创建视图。 4)、第五个参数表明把对话框作为视图的父窗口。 5)、第六个参数是视图的ID可以任意指定一个整数 在创建之后,如果不掉用OnInitialUpdate的话会有错误,这是因为CScrollView类有个保护成员 m_nMapMode,它在CScrollView的构造函数中被置为 0,如下 m_nMapMode = MM_NONE;//在VIEWSCRL.CPP的115行,MM_NONE是宏, 定义为0 下面是错误的分析原因: 通常窗口是在响应WM_PAINT的时候绘制客户区。所有的视图都派生自CView,他们的的WM_PAINT响应函数如下 void CView::OnPaint() //在VIEWCORE.CPP 176行 { // standard paint routine CPaintDC dc(this); OnPrepareDC(&dc); OnDraw(&dc); } 可见,在绘图之前虚函数OnPrepareDC会被调用,在CScroolView中重写了这个虚函数,这个虚函数的 开头部分如下 ASSERT_VALID(pDC); #ifdef _DEBUG if (m_nMapMode == MM_NONE) { TRACE0("Error: must call SetScrollSizes() or SetScaleToFitSize()"); TRACE0("/tbefore painting scroll view./n"); ASSERT(FALSE); return; } #endif //_DEBUG 可见如果m_nMapMode是0(在构造函数中被默认置为0)的话将在调试程序的时候错误 所以通常应该在调用OnInitialUpdate的时候(注意:OnInitialUpdate被声明为CView的public成员函数,但是用MFC向导产生自己派生类的OnInitialUpdate函数的时候被声明成了protect,你可以将它改成public,然后调用OnInitialUpdate来初始化自己的视图,或者在构造函数中设置m_nMapMode为自己想要的映射模式)初始化一些东西,并且设置m_nMapMode的正确值,m_nMapMode的一个典型值就是 MM_TEXT(关于映射模式可以看有关文章或者书详细介绍)。 上面讲述了如何在对话框中创建视图和相关函数调用的原因。 有一点要讲的是如果你的对话框加上了 最大化按钮的话,你可能想要在最大话对话框的时候让视图也跟着变化,所以你要处理对话框的 WM_SIZE消息。在其中你依据当前对话框的实际大小来调整视图大小。这样你就需要视图的指针 因此你可能的程序应该是这样的 // 在对话框头文件中,声明一个指向你的视图的指针 CMyScrollView * m_pView; // 在构造函数中将指针赋值NULL m_pView = NULL; //在OnInitDialog创建视图 CRect rectWndClient; GetClientRect(&rectWndClient); CRuntimeClass *pViewRuntimeClass=RUNTIME_CLASS(CMyScrollView); m_pView=(CMyScrollView*)pViewRuntimeClass->CreateObject(); m_pView->Create(NULL,NULL,WS_VISIBLE|WS_CHILD, rectWndClient, this,123,NULL); m_pView->OnInitialUpdat(); // 在OnSize中改变视图大小 if (m_pView) //注意在第一次调用OnSize之前视图還沒有创建,这也是要在构造函数中将 { // m_pView)置NULL的原因 m_pView->SetWindowPos(NULL,0,0,cx,cy,SWP_NOZORDER); }
// 注意我们在上面用new的方法【具体是在pViewRuntimeClass->CreateObject()的时候】产生了一个 CMyScrollView对象,我们的程序并没有调用delete的语句,这样会不会产生内存泄漏呢?
答案是否定的:窗口接受最后一个消息是WM_NCDESTROY,在OnNcDestroy()中CView调用了PostNcDestory这个虚函数 我们的类和CScrolView都没有重写PostNcDestory,因此他调用CView的PostNcDestory函数 在这个函数中调用了 delete this; CView自己删除了自己 所以不会有内存泄漏。
2、在SDI(单文档)程序中的对话框中使用CView
在SDI中的弹出对话框中创建视图,如果你用,上面的方法1做的话,只要你的鼠标不点击视图,就不会有问题(这样的要求 恐怕是没有人能够接受的^_^)。 我们先分析这样原因。在视图上点击鼠标会执行如下函数
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(); // 这里 **********(A) if (pParentFrame != NULL) { // eat it if this will cause activation ASSERT( pParentFrame == pDesktopWnd || pDesktopWnd->IsChild(pParentFrame)); //还有这里********(B)
// 省略 …… } return nResult;}
上面的A B 两处是我们找到的问题所在,我们最后出错的地方是在B处,但原因是在A. 当是对话框程序的时候如前面1,返回值pParentFrame是0,不会执行B的断言,也就不会有错。 但是在上面SDI况下,A返回的pParentFrame是应用程序主框架窗口而pDesktopWnd是视图所在的对话框窗口, 我们分析B的代码,发现此时pParentFrame == pDesktopWnd 不成立。而且pParentFrame(此时是主窗口) 也不是pDesktopWnd(此时是对话框)的子窗口,所以ASSERT断言为FALSE ,产生了错误。
单看上面的代码,及其原因我们还是不能决定如何使用CScrollView。
但是观察A处GetParentFrame的实现,却为我们打开了希望之门:
GetParentFrame是从CWnd继承而来
CFrameWnd* CWnd::GetParentFrame() const { if (GetSafeHwnd() == NULL) // no Window attached return NULL;
ASSERT_VALID(this);
CWnd* pParentWnd = GetParent(); // start with one parent up while (pParentWnd != NULL) { if (pParentWnd->IsFrameWnd()) return (CFrameWnd*)pParentWnd; pParentWnd = pParentWnd->GetParent(); } return NULL; }
分析这段代码,发现他的目的就是在他的祖先窗口中上溯,直到找到一个是FrameWnd类型的窗口之后, 返回这个窗口对象的指针(如果没有的话,返回NULL),在SDI中这样的窗口是一定存在的(主框架窗口便是!)。 我们从前面的分析中知道,pDesktopWnd是对话框窗口,他一定不是一个框架窗口,所以断言的前一部分不能满足 只有考虑后一个,即pDesktopWnd->IsChild(pParentFrame),先前已经说了主框架不可能是对话框的子窗口,所以要是 pDesktopWnd->IsChild(pParentFrame)满足,即要存在一个CFrameWnd类型的窗口是对话框的子窗口,也是视图的 父窗口(因为GetParentFrame是从视图开始上溯寻找的),因此我们可以在对话框和视图中间嵌入一个CFrameWnd或者起派生类 的对象。 由于CFrameWnd本身不用什么操作,我们就不用派生一个自己的CMyFrameWnd了,直接用CFrameWnd, 因此 可能的代码是这样的
CRect rectWndClient; GetClientRect(&rectWndClient);
CFrameWnd *pFrame= new CFrameWnd(); pFrame->Create(NULL,NULL,WS_VISIBLE|WS_CHILD,rectWndClient,this);
CRuntimeClass *pViewClass=RUNTIME_CLASS(CMyScrollView);
CMyScrollView *pView=(CMyScrollView*)pViewClass->CreateObject(); pView->Create(NULL,NULL,WS_VISIBLE|WS_CHILD, rectWndClient, pFrame,123); pView->OnInitialUpdate();
为了能够动态的随对话框的大小改变视图的大小,可以把pFrame和pView作为对话框的成员 我自己的某此使用过程中实现如下
// 在对话框头文件中,声明一个指向你的视图的指针 CFrameWnd * m_pFrame ; CMyScrollView * m_pView;
// 在构造函数中将指针赋值NULL m_pFrame = NULL; m_pView = NULL; //在OnInitDialog创建视图 CRect rectWndClient; GetClientRect(&rectWndClient);
m_pFrame= new CFrameWnd(); m_pFrame->Create(NULL,NULL,WS_VISIBLE|WS_CHILD,rectWndClient,this); CRuntimeClass *pViewClass=RUNTIME_CLASS(CMyScrollView);
m_pView=(CMyScrollView*)pViewClass->CreateObject(); m_pView->Create(NULL,NULL,WS_VISIBLE|WS_CHILD, rectWndClient, m_pFrame,123); m_pView->OnInitialUpdate();
// 在OnSize中改变视图大小
//注意在第一次调用OnSize之前视图還沒有创建,这也是要在构造函数中将 // m_pFrame,m_pView 置NULL的原因
if (m_pFrame && m_pView) { m_pFrame-> SetWindowPos(NULL,0,0,cx,cy,SWP_NOZORDER); m_pView-> SetWindowPos(NULL,0,0,cx,cy,SWP_NOZORDER); }