Программирование на языке 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;

}



Обратите внимание, читатель, что указатель на структуру типа 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()) замечали, что методу передаются указате­ли, которые с первого взгляда совершенно не нужны для работы метода. Кажется, здесь тот же случай – ну зачем, скажите, пожа­луйста, при создании фрейма знать указатель на документ? То, что буквально в первых строках метода осуществляется проверка того, не равен ли идентификатор ресурсов нулю, говорит о том, что, вероятнее всего, при создании фрейма опять будут использо­ваться ресурсы. Но, как говорится, поживем – увидим.