
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:
CDialogWindow class to your project and create an object with the DoModeless() method as you can see it in the project. CExtraView class and modify the OnDraw() method in order to make it display your stuff. 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} 通常在程序中,我們需要處理并且顯示一些數據,將顯示部分如果放到視圖中
的話(huà),我們將可以忽略大部分的窗口交互的細節,將注意力關(guān)注在數據的顯示
上。
本文是在處理如下的情況中提出的:在數據采集和分析的時(shí)候,常常需要觀(guān)看很
多的數據。我們不可能在程序的主界面上顯示。我們需要一個(gè)彈出窗口來(lái)顯示,
可以隨時(shí)的關(guān)閉之,也就是說(shuō)這樣的窗口應該動(dòng)態(tài)的生成,數量不定(這樣限制
了我們使用切分視圖的選擇)。對于這些彈出窗口,我們可以自己寫(xiě)一個(gè)CWnd的
派生類(lèi),自然這是完全可以的,但是這樣要求你可能處理滾動(dòng)等等的一些列消息(我
曾經(jīng)在一個(gè)顯示圖像灰度直方圖、以及二值圖像投影等中遇到過(guò)要求這樣顯示的
情況,當時(shí)就是自己從CWnd派生了一個(gè)類(lèi)來(lái)顯示數據,由于圖像比較大,一屏顯
示不完整,所以要給窗口加滾動(dòng)條,這樣在處理這些消息上花費了我大量的時(shí)間,
而真正顯示數據的代碼卻很少)。因此我也在一直想使用MFC已經(jīng)封裝的非常好的
CView類(lèi)及其派生類(lèi)來(lái)顯示數據。由于我主要用到的就是CScrollView,因此我也
以它為列來(lái)講述怎么用視圖。
基于對話(huà)框和SDI結構的實(shí)現上有不同細節, 必須分開(kāi)了說(shuō):
1、如果你的程序是一個(gè)基于對話(huà)框的程序的話(huà)(指的是如利用MFC向導生成的對話(huà)
框程序之類(lèi)),那么事情就很簡(jiǎn)單:
首先,很自然的在Insert/New Class中插入一個(gè)類(lèi),選擇CScrollView做基類(lèi)
假設你自己的視圖類(lèi)命名為CMyScrollView。
在對話(huà)框(不論是主界面的對話(huà)框還是新彈出的對話(huà)框,都一樣)的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();
說(shuō)明:
1)、由于CView及其派生類(lèi)的構造函數是保護成員,所以采用RuntimeClass方式來(lái)構造
對對象,
2)、在Create函數中第一個(gè)參數必須有WS_CHILD屬性,表明視圖是一個(gè)子窗口
3)、第四個(gè)參數rectWndClient可以視情況,改變,因為你可能只想在對話(huà)框的某個(gè)區域
創(chuàng )建視圖。
4)、第五個(gè)參數表明把對話(huà)框作為視圖的父窗口。
5)、第六個(gè)參數是視圖的ID可以任意指定一個(gè)整數
在創(chuàng )建之后,如果不掉用OnInitialUpdate的話(huà)會(huì )有錯誤,這是因為CScrollView類(lèi)有個(gè)保護成員
m_nMapMode,它在CScrollView的構造函數中被置為 0,如下
m_nMapMode = MM_NONE;//在VIEWSCRL.CPP的115行,MM_NONE是宏, 定義為0
下面是錯誤的分析原因:
通常窗口是在響應WM_PAINT的時(shí)候繪制客戶(hù)區。所有的視圖都派生自CView,他們的的WM_PAINT響應函數如下
void CView::OnPaint() //在VIEWCORE.CPP 176行
{
// standard paint routine
CPaintDC dc(this);
OnPrepareDC(&dc);
OnDraw(&dc);
}
可見(jiàn),在繪圖之前虛函數OnPrepareDC會(huì )被調用,在CScroolView中重寫(xiě)了這個(gè)虛函數,這個(gè)虛函數的
開(kāi)頭部分如下
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
可見(jiàn)如果m_nMapMode是0(在構造函數中被默認置為0)的話(huà)將在調試程序的時(shí)候錯誤
所以通常應該在調用OnInitialUpdate的時(shí)候(注意:OnInitialUpdate被聲明為CView的public成員函數,但是用MFC向導產(chǎn)生自己派生類(lèi)的OnInitialUpdate函數的時(shí)候被聲明成了protect,你可以將它改成public,然后調用OnInitialUpdate來(lái)初始化自己的視圖,或者在構造函數中設置m_nMapMode為自己想要的映射模式)初始化一些東西,并且設置m_nMapMode的正確值,m_nMapMode的一個(gè)典型值就是 MM_TEXT(關(guān)于映射模式可以看有關(guān)文章或者書(shū)詳細介紹)。
上面講述了如何在對話(huà)框中創(chuàng )建視圖和相關(guān)函數調用的原因。 有一點(diǎn)要講的是如果你的對話(huà)框加上了
最大化按鈕的話(huà),你可能想要在最大話(huà)對話(huà)框的時(shí)候讓視圖也跟著(zhù)變化,所以你要處理對話(huà)框的
WM_SIZE消息。在其中你依據當前對話(huà)框的實(shí)際大小來(lái)調整視圖大小。這樣你就需要視圖的指針
因此你可能的程序應該是這樣的
// 在對話(huà)框頭文件中,聲明一個(gè)指向你的視圖的指針
CMyScrollView * m_pView;
// 在構造函數中將指針賦值NULL
m_pView = NULL;
//在OnInitDialog創(chuàng )建視圖
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éi)有創(chuàng )建,這也是要在構造函數中將
{ // m_pView)置NULL的原因
m_pView->SetWindowPos(NULL,0,0,cx,cy,SWP_NOZORDER);
}
// 注意我們在上面用new的方法【具體是在pViewRuntimeClass->CreateObject()的時(shí)候】產(chǎn)生了一個(gè)
CMyScrollView對象,我們的程序并沒(méi)有調用delete的語(yǔ)句,這樣會(huì )不會(huì )產(chǎn)生內存泄漏呢?
答案是否定的:窗口接受最后一個(gè)消息是WM_NCDESTROY,在OnNcDestroy()中CView調用了PostNcDestory這個(gè)虛函數
我們的類(lèi)和CScrolView都沒(méi)有重寫(xiě)PostNcDestory,因此他調用CView的PostNcDestory函數 在這個(gè)函數中調用了
delete this; CView自己刪除了自己 所以不會(huì )有內存泄漏。
2、在SDI(單文檔)程序中的對話(huà)框中使用CView
在SDI中的彈出對話(huà)框中創(chuàng )建視圖,如果你用,上面的方法1做的話(huà),只要你的鼠標不點(diǎn)擊視圖,就不會(huì )有問(wèn)題(這樣的要求
恐怕是沒(méi)有人能夠接受的^_^)。
我們先分析這樣原因。在視圖上點(diǎn)擊鼠標會(huì )執行如下函數
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 兩處是我們找到的問(wèn)題所在,我們最后出錯的地方是在B處,但原因是在A(yíng).
當是對話(huà)框程序的時(shí)候如前面1,返回值pParentFrame是0,不會(huì )執行B的斷言,也就不會(huì )有錯。
但是在上面SDI況下,A返回的pParentFrame是應用程序主框架窗口而pDesktopWnd是視圖所在的對話(huà)框窗口,
我們分析B的代碼,發(fā)現此時(shí)pParentFrame == pDesktopWnd 不成立。而且pParentFrame(此時(shí)是主窗口)
也不是pDesktopWnd(此時(shí)是對話(huà)框)的子窗口,所以ASSERT斷言為FALSE ,產(chǎn)生了錯誤。
單看上面的代碼,及其原因我們還是不能決定如何使用CScrollView。
但是觀(guān)察A處GetParentFrame的實(shí)現,卻為我們打開(kāi)了希望之門(mén):
GetParentFrame是從CWnd繼承而來(lái)
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;
}
分析這段代碼,發(fā)現他的目的就是在他的祖先窗口中上溯,直到找到一個(gè)是FrameWnd類(lèi)型的窗口之后, 返回這個(gè)窗口對象的指針(如果沒(méi)有的話(huà),返回NULL),在SDI中這樣的窗口是一定存在的(主框架窗口便是!)。
我們從前面的分析中知道,pDesktopWnd是對話(huà)框窗口,他一定不是一個(gè)框架窗口,所以斷言的前一部分不能滿(mǎn)足 只有考慮后一個(gè),即pDesktopWnd->IsChild(pParentFrame),先前已經(jīng)說(shuō)了主框架不可能是對話(huà)框的子窗口,所以要是 pDesktopWnd->IsChild(pParentFrame)滿(mǎn)足,即要存在一個(gè)CFrameWnd類(lèi)型的窗口是對話(huà)框的子窗口,也是視圖的 父窗口(因為GetParentFrame是從視圖開(kāi)始上溯尋找的),因此我們可以在對話(huà)框和視圖中間嵌入一個(gè)CFrameWnd或者起派生類(lèi) 的對象。 由于CFrameWnd本身不用什么操作,我們就不用派生一個(gè)自己的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();
為了能夠動(dòng)態(tài)的隨對話(huà)框的大小改變視圖的大小,可以把pFrame和pView作為對話(huà)框的成員
我自己的某此使用過(guò)程中實(shí)現如下
// 在對話(huà)框頭文件中,聲明一個(gè)指向你的視圖的指針
CFrameWnd * m_pFrame ;
CMyScrollView * m_pView;
// 在構造函數中將指針賦值NULL
m_pFrame = NULL;
m_pView = NULL;
//在OnInitDialog創(chuàng )建視圖
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éi)有創(chuàng )建,這也是要在構造函數中將
// m_pFrame,m_pView 置NULL的原因
聯(lián)系客服