

Программирование на языке MFC
Мой второй блог в серии программирования
Мне бы хотелось, чтобы вы вспомнили, что происходит при получении окном класса CMDIChildWnd сообщения WM_CREATE. Во время работы этого метода вызываются еще много других методов, в том числе CFrameWnd::CreateView(). Именно при работе этого метода и происходит вызов метода CreateObject() для объекта класса окна представления. Естественно, метод CreateObject() вызывает конструктор объекта окна представления. И, разумеется, вызываются строго по порядку конструкторы классов, от которых унаследован наш объект. Если мы в качестве окна приложения используем объект класса CEditView, то первым вызывается конструктор CEditView::CEditView(). Исходный код этого конструктора находится в файде viewedit.cpp:
// pass a NULL style because dwStyleDefault stays for // backward compatibility
CEditView::CEditView() : CCtrlView(_T("EDIT"), NULL)
m_nTabStops = 8*4; // default 8 character positions m_hPrinterFont = NULL; m_hMirrorFont = NULL; m_pShadowBuffer = NULL; m_nShadowSize = 4);
}
Перед тем, как отработает этот конструктор, управление будет передано конструктору класса CCtrlView.
Исходный текст конструктора класса CCtrlView можно найти в файле viewcore.cpp:
CCtrlView::CCtrlView(LPCTSTR IpszClass, DWORD dwStyle) {
m_strClass = IpszClass;
m_dwDefaultStyle = dwStyle;
}
читать отзывы (0)
Обратите внимание, читатель, что указатель на структуру типа CCreateContext (в нее чуть раньше мы записали наши указатели на документ,шаблон документа, а также указатели на фрейм и на окно представления документа) записывается сначала в поле lpCreateParams структуры cs типа CREATESTRUCT, а потом в поле IParam структуры mcs типа MDICREATESTRUCT Указатель на структуру mcs посылается окну типа MDICLIENT посредством сообщения WM_MDICREATE. Что же происходит дальше? После получения этого сообщения создается дочернее окно MDI, которое будет играть роль окна фрейма. Естественно, при своем создании оно получает сообщение WM_CREATE, на что реагирует вызовом метода CMDIChildWnd::OnCreate():
int CMDIChildWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) {
// call base class with IParam context (not MDI one) MDICREATESTRUCT* lpmcs;
lpmcs = (MDICREATESTRUCT*)lpCreateStruct->lpCreateParams; CCreateContext* pContext = (CCreateContext*)lpmcs->lParam;
return OnCreateHelper(lpCreateStruct, pContext);
Из переданной окну структуры типа CREATESTRUCT выбирается указатель на CCreateContext, после чего указатели на структуры типов CREATESTRUCT и CCreateStruct передаются методу CFrameWnd::OnCreateHelper:
int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs.
CCreateContext* pContext)
if (CWnd::OnCreate(lpcs) == -1) return -1;
// create special children first
if (!OnCreateClie*nt (lpcs, pContext) ) {
TRACEO("Failed to create client pane/view for frame.\n") ; return -1;
}
// post message for initial message string
PostMessage(WM_SETMESSAGESTRING, AFX__IDS_IDLEMESSAGE);
// make sure the child windows have been properly sized RecalcLayout();
return 0; // create ok
}
BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT,
CCreateContext* pContext)
{
// default create client will create a view if asked for it if (pContext != NULL && pContext->m_pNewViewClass != NULL) {
if (CreateView (pContext, AFX_IDW_PANE_FIRST) == NULL) return FALSE;
}
return TRUE;
}
CWnd* CFrameWnd::CreateView(CCreateContext* pContext,
UINT nID)
{
ASSERT(m_hWnd != NULL); ASSERT(::IsWindow(m_hWnd)); ASSERT(pContext != NULL);
ASSERT(pContext->m_pNewViewClass != NULL);
// Note: can be a CWnd with PostNcDestroy self cleanup CWnd* pView =
(CWnd*)pContext->m_pNewViewClass->CreateObject() ; if (pView == NULL) {
TRACE1("Warning: Dynamic create of view type
%hs failed.\n",
pContext->m_pNewViewClass->m_lpszClassName) ; return NULL;
ASSERT_KINDOF(CWnd, pView);
// views are always created with a border! if (!pView->Create(NULL, NULL,
AFX_WS_DEFAULT_VIEW, CRect(0,0,0, 0), this, nID,
pContext))
{
TRACEO("Warning: could not create view for frame.\n");
return NULL; // can’t continue without a view
}
if (afxData.bWin4 && (pView->GetExStyle () &
WS_EX_CLIENTEDGE))
{
// remove the 3d style from the frame, since the view is // providing it.
// make sure to recalc the non-client area
ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED);
}
return pView;
}
Смотрите внимательно, читатель! Сначала создается объект (представление) того класса, который мы указали при создании шаблона документа. После этого создается непосредственно окно представления. При этом видно, родительским окном объявляется окно фрейма! Так что же получается? MFC решило за программиста одну из достаточно часто встречавшихся задач -расположение дочернего окна поверх клиентской области родительского окна. Теперь программисту нет необходимости самому создавать дочернее окно и отслеживать изменения размеров родительского окна – эти задачи решает за него MFC. Единственное, что в этом случае требуется от программиста, так это написать приложение в соответствии с требованиями архитектуры «документ/представление»!
Осталось понять одну «мелочь» – как осуществляется отображение данных документа в окне представления.
Я опять старался рассуждать логически.
Читатель, взгляните, пожалуйста, на исходный код метода CMDIChildWnd::LoadFrame(). В нем вы увидите следующий оператор:
if (!Create(GetlconWndClass(dwDefaultStyle, nIDResource), strTitle, dwDefaultStyle, rectDefault,
(CMDIFrameWnd*) pParentWnd, pContext)) …
BOOL CMDIChildWnd::Create(LPCTSTR IpszClassName,
LPCTSTR IpszWindowName, DWORD dwStyle, const RECT& rect, CMDIFrameWnd* pParentWnd, CCreateContext* pContext)
{
if (pParentWnd == NULL) {
CWnd* pMainWnd = AfxGetThread()->m_pMainWnd; ASSERT(pMainWnd != NULL); ASSERT_KINDOF(CMDIFrameWnd, pMainWnd); pParentWnd = (CMDIFrameWnd*)pMainWnd;
ASSERT(::IsWindow(pParentWnd->m_hWndMDIClient));
// insure correct window positioning pParentWnd->RecalcLayout();
// first copy into a CREATESTRUCT for PreCreate CREATESTRUCT cs; cs.dwExStyle = OL; cs.IpszClass = IpszClassName; cs.lpszName = IpszWindowName; cs.style = dwStyle; cs.x = rect.left; cs.y = rect.top; cs.cx = rect.right – rect.left; cs.cy = rect.bottom – rect.top; cs.hwndParent = pParentWnd->m_hWnd; cs.hMenu = NULL;
cs.hlnstance = AfxGetlnstanceHandle(); cs.lpCreateParams = (LPVOID)pContext;
if (!PreCreateWindow(cs)) {
PostNcDestroy(); return FALSE;
}
// extended style must be zero for MDI Children // (except under Win4)
ASSERT(afxData.bWin4 || cs.dwExStyle == 0);
ASSERT(cs.hwndParent == pParentWnd->m_hWnd); // must not change
// now copy into a MDICREATESTRUCT for real create MDICREATESTRUCT mcs; mcs.szClass = cs :*lpszClass; mcs.szTitle = cs.lpszName; mcs.hOwner = cs.hlnstance;
mcs.x = cs.x; mcs.у = cs.y; mcs.cx = cs.cx; mcs.cy = cs.cy;
mcs.style = cs.style & -(WS_MAXIMIZE | WS_VISIBLE); mcs.lParam = (LONG)cs.lpCreateParams;
// create the window through the MDICLIENT window AfxHookWindowCreate(this); HWND hWnd =
(HWND)::SendMessage(pParentWnd->m_hWndMDIClient, WM_MDICREATE, 0,
(LPARAM)&mcs); if (!AfxUnhookWindowCreate()) PostNcDestroy(); // cleanup if MDICREATE fails too soon
if (hWnd ~ NULL) return FALSE;
// special handling of visibility (always created invisible) if (cs.style & WS_VISIBLE) {
// place the window on top in z-order before showing it ::BringWindowToTop(hWnd);
// show it as specified
if (cs.style & WS_MINIMIZE)
ShowWindow(SW_SHOWMINIMIZED); else if (cs.style & WS_MAXIMIZE)
ShowWindow(SW_SHOWMAXIMIZED) ; else
ShowWindow(SW_SHOWNORMAL);
// make sure it is active (visibility == activation) pParentWnd->MDIActivate(this);
// refresh MDI Window menu
::SendMessage(pParentWnd->m_hWndMDIClient, WM_MDIREFRESHMENU, 0, 0) ; }
ASSERT(hWnd == m_hWnd); return TRUE;
}
fCMDIChJldWnd::OnCreate)
Во-первых, для того чтобы рассмотреть вопрос отображения документа, нам необходимо вспомнить, что у нас до сих пор указатель на информацию времени исполнения окна представления нашей программы равен NULL. В связи с этим необходимо описать класс окна представления..Следрвательно, наша программа немного изменилась. Сразу после описания класса документа я добавил описание класса окна представления:
class CDocView : public CView {
DECLARE_DYNCREATE ( CDocView ) public:
CDocView();
virtual -CDocView();
>;
IMPLEMENT_DYNCREATE( CDocView, CView )
CDocView::CDocView()
{
}
CDocView::-CDocView()
{
}
Кроме этого, я изменил метод lnitlnstance() класса CDocViewl:
BOOL CDocViewlApp::Initlnstance() {
#ifdef _AFXDLL
Enable3dControls(); #else
Enable3dControlsStatic(); #endif
LoadStdProfileSettings(); CDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate( IDR_DOCUMENT,
RUNTIME__CLASS ( CDoc ) , RUNTIME_CLASS( CMDIChildWnd ), RUNTIME_CLASS( CDocView ) ) ; AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();
return TRUE;
}
Когда я постарался откомпилировать эту программу, я получил сообщение о том, что у класса, наследуемого от CView, не определен метод OnDraw(). Ну, конечно же! Откуда же наш произвольный класс окна представления будет знать, каким образом ему необходимо перерисовываться? Давайте опять постараемся рассуждать логически. Какое сообщение получает окно, когда ему нужно перерисовать себя? Правильно, WM_PAINT. Но для обработки сообщения WM_PAINT объекты MFC используют метод OnPaintQ.
Не является исключением и объекты класса CView и унаследованных от него. Исходный код этого метода находится в файле viewcore.cpp:
void CView::OnPaint() {
// standard paint routine CPaintDC dc(this); OnPrepareDC(&dc); OnDraw(&dc);
}
Очевидно, что перерисовка осуществляется методом OnDraw(). Взглянем на исходный код этого метода, который также находится в файле viewcore.cpp:
void CView::OnDraw(CDC*)
{
}
To, что этот метод не делает ничего, подтверждает нашу догадку о том, что нам нужно переписать именно этот метод. Что ж описание нашего класса CDocView придется немного изменить. Теперь оно будет выглядеть так:
class CDocView : public CView {
DEСLARE_DYNCREATE ( CDocView ) public:
CDocView();
virtual ~CDocView(); void OnDraw( CDC* pDC );
};
Естественно, нам придется переопределить и добавить в программу метод OnDravtr(). Пока этот метод нас не очень интересует, поэтому оставим его пустым:
void CDocView::OnDraw( CDC* pDC )
{
}
Наверное, отсюда можно сделать вывод, что класс CView предоставляет только базовые возможности для отображения данных. Надеюсь, у нас еще будет шанс разобраться с этим.
Любознательный читатель уже, наверное, приготовил мне вопрос: да, конечно, все что рассказывается, это верно, но каким же образом создается окно представления и как оно относится к окну фрейма и документу?
Документ, естественно, рождается из файла, поэтому первым делом метод, вызывая GetFile(), открывает файл, имя которого, как я уже говорил, он получил в качестве аргумента. Затем начинается самое интересное. На основе файла создается объект класса САг-chive, т. е. АРХИВ. В архитектуре «документ/представление» считается, что программист должен иметь возможность легко сохранять созданные в памяти структуры данных в дисковом файле, после чего он должен иметь возможность вновь считать их из файла. Архив позволяет программисту читать и записывать в файл не просто некоторые объемы информации, а ОБЪЕКТЫ всевозможных типов! Именно поэтому его использование в большинстве случаев представляется оправданным. И дальше вызывается метод Serialize(), который работает уже не с файлом, а с архивом! По умолчанию этот метод не делает ничего. Естественно, откуда программе знать, что и как программисту захотелось сохранить в архиве? А для программиста здесь раздолье! Можно проверить формат открытого файла, определить, при необходимости, его структуру, выполнить все мыслимые и немыслимые действия. Для того чтобы продемонстрировать работу метода Serialize(), давайте попробуем открыть файл в нашей программе и считать его в буфер. Внесем небольшие изменения в наш класс документа – добавим два поля, в которые запишем, во-первых, размер файла, а второй будет являться указателем на буфер, в который мы будем читать файл. После внесенных изменений наша программа выглядит следующим образом:
#include "stdafx.h" #include <afxcview.h> #include "resource.h"
// Объявляем класс приложения, его поля и методы.
class CDoc : public CDocument {
DECLARE_DYNCREATE( CDoc ) int nFileLength; void* pMyFile; public: CDocO ;
void Serialize ( CArchive &ar ); virtual -CDoc();
}/
• IMPLEMENT_DYNCREATE( CDoc, CDocument )
CDoc::CDoc()
{
}
CDoc::-CDoc()
{
}
void CDoc::Serialize( CArchive &ar ) {
if( ar.IsStoring() )
{
}
else {
// Определяем длину файла.
nFileLength = ar.GetFile()->GetLength();’
TRACE( _T( "File length = %d.\n"), nFileLength’ );
// Выделяем буфер в памяти, в который будем считывать файл. pMyFile = new chart nFileLength ];
// Считываем файл в буфер.
ar.Read( pMyFile, nFileLength ); }
}
class CDocViewlApp : public CWinApp {
public:
CDocViewlApp(); protected:
afx_msg void OnFileOpen(); virtual BOOL Inifrlnstance(); DEC LARE_ME S S AGE_MA P()
};
CDocViewlApp::CDocViewlApp()
{
}
void CDocViewlApp::OnFileOpen()
{
}
class CMainFrame : public CMDIFrameWnd {
DECLARE_DYNAMIC( CMainFrame ); public:
CMainFrame();
>;
IMPLEMENT_DYNAMIC (CMainFrame, CMDIFrameWnd)
CMainFrame::CMainFrame()
{
}
BOOL CDocViewlApp::Initlnstance() {
#ifdef _AFXDLL
Enable3dControls(); #else
Enable3dControlsStatic(); #endif
LoadStdProfileSettings(); CDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate( IDR_DOCUMENT,
RUNTIME_CLASS( CDoc ) , RUNTIME_CLASS( CMDIChildWnd ), NULL );
AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame ( IDR__RESOURCE ) ; m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();
return TRUE;
}
BEGIN_MESSAGE_MAP( CDocViewlApp, CWinApp )
ON_COMMAND( ID_FILE_OPEN, CWinApp::OnFileOpen ) END_MESSAGE_MAP()
CDocViewlApp theApp;
Если мы запустим нашу программу на выполнение (внутри среды разработки) и откроем какой-нибудь файл, то увидим, что на отладочном мониторе появилось сообщение о размере открытого файла. Любознательный читатель может проверить, произойдет ли считывание содержимого файла в буфер. Я абсолютно уверен, что в обычных условиях файл будет считан в буфер без каких-либо проблем.
Я надеюсь, что читатель помнит о том, что пока при создании шаблона нашего документа указатель на информацию времени исполнения класса окна представления равен нулю. Следовательно, пока мы можем на некоторое время оставить вопрос о создании окна представления и вернуться в метод CMDIChildWnd::LoadFrame().
Из текста метода CMDIChildWnd::LoadFrame() кроме сказанного выше становится понятным и назначение начальной подстроки строкового ресурса – эта подстрока определяет заголовок создаваемого окна фрейма.
Теперь, когда мы знаем все о том, как происходит создание нового документа и создание нового фрейма, нам придется вернуться к методу ь CMultiDocTemplate::OpenDocumentFile() и продолжить его рассмотрение на имя открываемого файла. Текст этого метода, приведенный ниже, можно найти в файле doccore.cpp:
BOOL CDocument::OnOpenDocument(LPCTSTR IpszPathName) {
if (IsModifiedO )
TRACEO("Warning: OnOpenDocument replaces an unsaved
document.\n") ;
CFileException fe;
CFile* pFile = GetFile(IpszPathName,
CFile::modeRead|CFile::shareDenyWrite,
&fe) ; if (pFile == NULL) {
ReportSaveLoadException(IpszPathName,
return FALSE;
DeleteContents ();
&fe, FALSE,
AFX_IDP FAILED TO_OPEN_DOC);
SetModifiedFlag(); // dirty during de-serialize
CArchive loadArchive(pFile,
CArchive::load | CArchive:rbNoFlushOnDelete); loadArchive.m_pDocument = this; loadArchive.m_bForceFlat = FALSE; TRY {
CWaitCursor wait;
if (pFile->GetLength() != 0)
Serialize(loadArchive); // load me
loadArchive.Close(); ReleaseFile(pFile, FALSE);
}
CATCH_ALL(e) {
ReleaseFile(pFile, TRUE);
DeleteContents(); // remove failed contents
TRY {
ReportSaveLoadException(IpszPathName, e, FALSE,
AFX_IDP_FAILED_TO_OPEN_DOC);
}
END_TRY
DELETE_EXCEPTION(e); return FALSE;
}
END_CATCH_ALL
SetModifiedFlag(FALSE); // start off with unmodified
return TRUE;
}
Я прошу читателя уделить этому методу ОСОБОЕ внимание. Немного опережая события, скажу, что именно здесь мы увидим, в какой момент из файла получается документ, какой метод необходимо переопределить, чтобы всю рутинную работу возложить на MFC.
Для только что созданного объекта осуществляется вызов метода CMDIChildWnd::LoadFrame(). При этом в качестве аргументов методу передаются пресловутый идентификатор ресурсов, набор стилей окна (не объекта, а окна!), которое будет создано, указатель на родительское окно и, наконец, указатель на структуру типа Ccreate-Context, которую мы рассмотрели чуть раньше. Здесь стоит заметить, что по умолчанию стилями окна являются WSJDVERLAPPED-WINDOW и FWS_ADDTOTITLE. Исходный текст этого метода находится в файле winmdi.cpp:

BOOL CMDIChildWnd::LoadFrame(UINT nIDResource,
DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext)
{
// only do this once
ASSERT_VALID_IDR(nIDResource) ;
ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);
ASSERT(m_hMenuShared == NULL); // only do once
m_nIDHelp = nIDResource;
// ID for help context (+HID_BASE_RESOURCE)
// parent must be MDI Frame (or NULL for default)
ASSERT(pParentWnd == NULL ||
pParentWnd->IsKindOf(RUNTIME_CLASS(CMDIFrameWnd))); // will be a child of MDIClient
ASSERT(!(dwDefaultStyle & WS_POPUP));
dwDefaultStyle |= WS_CHILD;
// if available – get MDI child menus from doc template
ASSERT(m_hMenuShared == NULL); // only do once
CMultiDocTemplate* pTemplate; if (pContext != NULL &&
(pTemplate = (CMultiDocTemplate*)pContext->
m_pNewDocTemplate) != NULL)
{
ASSERT_KINDOF(CMultiDocTemplate, pTemplate); // get shared menu from doc template
m_hMenuShared = pTemplate->m_hMenuShared; m_hAccelTable = pTemplate->m_hAccelTable;
}
else {
TRACEO(“Warning: no shared menu/acceltable for MDI Child
window.\n”);
// if this happens, programmer must load these manually }
CString strFullString, strTitle;
if (strFullString.LoadString(nIDResource))
AfxExtractSubString(strTitle, strFullString, 0); // first sub-string
ASSERT(m_hWnd == NULL);
if (!Create(GetlconWndClass(dwDefaultStyle, nIDResource), strTitle, dwDefaultStyle, rectDefault,
(CMDIFrameWnd*)pParentWnd, pContext))
{
return FALSE; // will self destruct on failure normally
// it worked ! return TRUE;
}
Что мы можем увидеть в тексте этого модуля? Многое. Здесь даны ответы на еще несколько вопросов, которые мы напрямую не задавали, но которые, как говорится, висели в воздухе. Например, что еще, кроме меню, акселераторов и строки, может входить в состав передаваемых шаблону документа ресурсов? Каково назначение оставшихся подстрок строкового ресурса? Из текста модуля очевидно, что, во-первых, то, что в состав ресурсов, переданных шаблону документа, могут входить не только меню, акселераторы и строка, но и ресурсы помощи. Во-вторых, меню, которое мы создали, является меню окна фрейма! Для нас это очень важно, ибо, наконец, мы поняли, что за меню мы должны создавать при создании шаблона документа. Именно к окну фрейма и относятся акселераторы, которые мы можем создать одновременно с меню.
Этот метод непосредственно создает окно фрейма. Но до того момента, пока окно фрейма будет создано, происходит еще несколько интересных вещей. Например, перед вызовом метода Create() вызывается метод CFrameWnd::GetlconWnd-Class(). В качестве аргументов этому методу передаются стиль окна и опять-таки идентификатор ресурсов. Текст этой функции находится в файле winfrm.cpp:
LPCTSTR CFrameWnd::GetlconWndClass(DWORD dwDefaultStyle,
UINT nIDResource)
{
ASSERT_VALID_IDR(nIDResource); HINSTANCE hlnst =
AfxFindResourceHandle(MAKEINTRESOURCE(nIDResource),
RT_GROUP_ICON); HICON hlcon = ::LoadIcon(hlnst,
MAKEINTRESOURCE(nIDResource));
if (hlcon != NULL) {
CREATESTRUCT cs;
memset(&cs, 0, sizeof(CREATESTRUCT));
cs.style = dwDefaultStyle;
PreCreateWindow(cs); // will fill IpszClassName with default WNDCLASS name // ignore instance handle from PreCreateWindow.
WNDCLASS wndcls;
if (cs.IpszClass != NULL &&
GetClassInfo(AfxGetlnstanceHandle(), cs.IpszClass,
&wndcls) && wndcls.hlcon != hlcon)
{
// register a very similar WNDCLASS
return AfxRegisterWndClass(wndcls.style,
wndcls.hCursor, wndcls.hbrBackground, hlcon);
}
}
return NULL; // just use the default
}
Так… Оказывается, в состав ресурсов, передаваемых шаблону документа, может входить и иконка, которая будет использоваться при отображении окна фрейма! Функция пытается загрузить иконку из ресурсов, и, если иконка не загрузилась, то ничего не происходит и метод Create() использует данные, принятые по умолчанию, загрузилась, Если же иконка загрузилась нормально, то MFC, естественно, регистрирует класс окна, которому принадлежит иконка. После этого вызывается метод PreCreateWindow(), который мы можем использовать для того, чтобы произвести какие-то действия ДО создания окна.
Пока мы видим, что сразу после проверки того, что указатель на документ и идентификатор ресурсов ненулевые, создается структура типа CCreateContext. Описание этой структуры находится в файле afxext.h:
struct CCreateContext // Creation information structure // All fields are optional and may be NULL
{
// for creating.,new views CRuntimeClass* m_pNewViewClass;
// runtime class of view to create or NULL CDocument* m_pCurrentDoc;
// for creating MDI children (CMDIChildWnd::LoadFrame) CDocTemplate* m_pNewDocTemplate;
// for sharing view/frame state from the // original view/frame
CView* m_pLastView; CFrameWnd* m_pCurrentFrame;
// Implementation CCreateContext();
};
В поля этой структуры записываются указатели на документ, шаблон документа, а также указатели на фрейм, который будет создан, и на окно представления документа. Обратим внимание на этот факт, дело в том, что эти данные будут использованы в дальнейшем.
На что необходимо обратить внимание? К этому моменту мы еще не представляем характера взаимодействия между документом и фреймом. Поэтому просто предположим, что каким-то образом наш документ будет отображаться в рамках фрейма. Давайте поразмыслим, уважаемый читатель. Мы пишем программу, которая будет работать с многодокументным интерфейсом. Наша программа должна отображать данные в одном из дочерних окон многодокументного интерфейса. Следовательно, логично будет в качестве фрейма использовать окно класса CMDIChildWnd. Давайте так и поступим, уважаемый читатель. Итак, ниже я привожу текст метода CDocView1App::lnitlnstance() нашей программы:
BOOL CDocViewlApp::Initlnstance () {
#ifdef _AFXDLL
Enable3dControls(); #else
Enable3dControlsStatic(); #endif
LoadStdProfileSettings(); CDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate( IDR_DOCUMENT,
RUNTIME_CLASS( CDoc ),% RUNTIME_CLASS( CMDIChildWnd ),
NULL );
AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();
return TRUE;
}
создается фрейм этого документа. Нетрудно догадаться, что создание фрейма происходит при вызове метода CDocTemplate:: CreateNewFrame():
CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc,
CFrameWnd* pother)
{
if (pDoc != NULL)
ASSERT_VALID(pDoc); // create a frame wired to the specified document
ASSERT(m_nIDResource != 0); // must have a resource. ID
// to load from
CCreateContext context;
context,m_pCurrentFrame = pother;
context.m_pCurrentDoc = pDoc;
context,m_pNewViewClass = m_pViewClass;
context.m_pNewDocTemplate = this;
if (m_pFrameClass == NULL) ‘ {
TRACEO("Error: you must override
CDocTemplate::CreateNewFrame.\n") ;
ASSERT(FALSE); return NULL;
}
CFrameWnd* pFrame =
(CFrameWnd*)m_pFrameClass->CreateObject() ; if (pFrame == NULL) {
TRACE1("Warning: Dynamic create of frame %hs failed.\n",
m_pFrameClass->m_lpszClassName);
return NULL;
}
ASSERT_KINDOF(CFrameWnd, pFrame);
if (context.m_pNewViewClass == NULL)
TRACEO("Warning: creating frame with no default
view.\n");
// create new from resource
if (!pFrame->LoadFrame(m_nIDResource,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles
NULL, &context))
{
TRACEO("Warning: CDocTemplate couldn’t create
a frame.\n"); // frame will be deleted in PostNcDestroy cleanup return NULL;
}
// it worked ! return pFrame;
}
Мне бы хотелось обратить внимание читателя на то, что в качестве аргументов методу передаются указатель на документ и указатель (пока нулевой), в который будет записан указатель на созданный фрейм. Мы уже однажды (при рассмотрении метода DoPromptFileName()) замечали, что методу передаются указатели, которые с первого взгляда совершенно не нужны для работы метода. Кажется, здесь тот же случай – ну зачем, скажите, пожалуйста, при создании фрейма знать указатель на документ? То, что буквально в первых строках метода осуществляется проверка того, не равен ли идентификатор ресурсов нулю, говорит о том, что, вероятнее всего, при создании фрейма опять будут использоваться ресурсы. Но, как говорится, поживем – увидим.
