Программирование на языке MFC

Мой второй блог в серии программирования

Архив раздела «Первая программа на MFC»

Что мы можем здесь увидеть? То, что в описании этого класса присутствует поле rnJTemplateList класса CPtrList, только подтвер­ждает нашу догадку о том, что de facto объекты класса CDocMan­ager являются списками указателей на какие-то шаблоны. Для того чтобы убедиться, что наша догадка верна, достаточно взглянуть на исходный код методов GetFirstDocTemplatePosition() и GetNext-DocTemplate(), который находится в файле docmgr.cpp:

POSITION CDocManager::GetFirstDocTemplatePosition() const {

return m_templateList.GetHeadPosition() ;

}

CDocTemplate* CDocManager::GetNextDocTemplate(

POSITIONS pos) const

{

return (CDocTemplate*)m_templateList.GetNext(pos) ;

Теперь возникает вопрос о том, что за шаблоны включаются в список. Позвольте, уважаемый читатель, высказать предположе­ние о том, что этими шаблонами являются шаблоны ДОКУМЕН­ТОВ, с которыми работает данное приложение. Я не буду сейчас останавливаться на том, что такое шаблон документа. Это станет ясно из дальнейшего изложения.

Сейчас нам необходимо научиться создавать объект этого класса. Естественно, что для создания объекта мы восполь­зуемся конструктором. Раз конструктор класса CDocManager параметров не имеет, то создание объекта этого класса за­труднений не вызовет. Но каким-то образом нам необходимо передать нашему приложению информацию о том, с документа­ми каких типов (шаблонов) он будет иметь дело! Вспомним, что у класса CWinApp для добавления шаблона в список есть метод AddDocTemplate(), который, фактически является вызовом одно­именного метода класса CDocManager. Наверное, именно этим методом и следует воспользоваться при добавлении шаблона документа!



Наверное, из описания класса CWinApp мы не сумеем извлечь еще какую-то информацию о классе CDocManager. Настало время взглянуть на описание этого класса. Думаю, нет необходимости описывать всю внутреннюю реализацию этого класса. Достаточно будет, если мы рассмотрим назначение наиболее часто используе­мых полей и методов.

Класс CDocManager в файле afxwin.h описан следующим образом:

class CDocManager : public CObject {

DECLARE_DYNAMIC(CDocManager) public:

// Constructor CDocManager();

//Document functions

virtual void AddDocTemplate(CDocTemplate* pTemplate); virtual POSITION GetFirstDocTemplatePosition() const; virtual CDocTemplate* GetNextDocTemplate(

POSITIONS pos) const; virtual void RegisterShellFileTypes(BOOL bCompat); void UnregisterShellFileTypes();

virtual CDocument* OpenDocumentFile(LPCTSTR IpszFileName);

// open named file

virtual BOOL SaveAllModified(); // save before exit virtual void CloseAllDocuments(BOOL bEndSession);

// close documents before exiting virtual int GetOpenDocumentCount();

// helper for standard commdlg dialogs virtual BOOL DoPromptFileName(CString& fileName

UINT nIDSTitle, DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate);

//Commands

// Advanced: process async DDE request

virtual BOOL OnDDECommand(LPTSTR IpszCommand); virtual void OnFileNew(); virtual void OnFileOpen();

// Implementation protected:

CPtrList m_templateList;

int GetDocumentCount(); // helper to count number

// of total documents

public:

static CPtrList* pStaticList;

// for static CDocTemplate objects static BOOL bStaticInit;

// TRUE during static initialization static CDocManager* pStaticDocManager;

// for static CDocTemplate objects

public:

virtual -CDocManager(); #ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const; #endif };



Значит, либо я сделал что-то не так, либо в программе не опре­делил какие-то данные. Второй вариант, конечно, более вероя­тен. Для того чтобы убедиться в этом, давайте, уважаемый чи­татель, посмотрим на то место, в котором была обнаружена ошибка. Как оказалось, на этом месте находится исходный текст метода OnFileOpenQ класса CWinApp. Ниже приведен текст это­го метода:

void CWinApp::OnFileOpen() {

ASSERT(m_pDocManager != NULL); m_pDocManager->OnFileOpen();

}

Помните, я говорил, что имеющий уши да услышит? Во-первых, судя по наименованию переменных, используемых в приведенном выше тексте метода, MFC по умолчанию считает, что наше прило­жение построено в соответствии с идеологией «Документ/представ­ление»! Другими словами, именно эта идеология считается при­оритетной при работе с MFC! Значит, именно ЭТОЙ идеологии в MFC отводится очень важная роль. Возможно, подумал я, что понимание одного из ключевых моментов MFC приблизит меня к по­ниманию идеологии всей библиотеки в целом! Но оставим «лири­ческое отступление» и продолжим нашу работу.

Во-вторых, здесь MFC сама подсказывает нам причину ошибки в программе! Из приведенного выше текста метода мы можем сделать три важных заключения. Заключение первое состоит в том, что у класса CWinApp есть член m_pDocManager, кото­рый, судя по его названию, является указателем на какой-то ме­неджер документов. Заключение второе – вероятнее всего, имен­но этот менеджер документов и отвечает за управление доку­ментами в приложении. И наконец, заключение третье. Очевид­но, что единственной причиной, которая может привести к выда­че приведенного выше сообщения об ошибке, является равен­ство нулю указателя на менеджер документов. Следовательно, причина этой ошибки состоит в том, что мы просто-напросто не создали менеджер документов!

А что следует из этих заключений? Из этих заключений следует один очень важный вывод.

Тсли наше приложение не обрабатывает команду ID_FILE_OPEN и обработку производит метод CWinApp::OnFileOpen, то перед вызовом этого метода дол­жен быть создан объект класса CDocManager и указатель на этот объект должен быть присвоен полю CWinApp::m_pDocManager.

К сожалению, искать информацию о менеджере документов в MSDN совершенно бесполезно. И в описании членов класса CWi­nApp мы также не найдем и упоминания об m_pDocManager. При­дется опять смотреть исходные коды MFC…



Я начал реализовывать этот план и какое-то время работал в этом направлении. Однако достаточно быстро я одумался и за­дал себе один вопрос: а почему, собственно говоря, мне приходит­ся делать всю черновую работу самому? Исходя из того, что я чи­тал про MFC ранее, все должно быть намного проще. Если я иду в правильном направлении, то почему мне приходится делать все самому вручную? Где же хваленые возможности MFC? Все же, на­верное, я чего-то не понимаю. И здесь мне пришла в голову мысль, результатом которой и явилось правильное решение. А не попро­бовать ли мне возложить обработку открытия файла на MFC? Ведь не зря же у класса CWinApp есть метод OnFileOpenQ, не так ли? Карту сообщений своего объекта приложения я немного изменил. Теперь обработчик команды ID__FILE__OPEN стал выглядеть сле­дующим образом:

ON_COMMAND( ID_FILE_OPEN, CWinApp::OnFileOpen )

Тем самым обработку открытия файла я возложил на MFC. Мне осталось только понять, к чему это приведет…

Программа откомпилировалась без ошибок, что меня, честно говоря, несколько удивило. Однако, когда я запустил программу на выполнение в отладочном режиме, то, выбрав элемент «Ореп» меню «File», я получил сообщение, которое приведено на.



Характерной особенностью этой программы является то, что она практически ничего не делает. Я написал этот вариант только для того, чтобы посмотреть, как MFC будет реагировать на открытие файла и т.д. Но, с другой стороны, обратите внимание на то, что обработку открытия файла я, тем не менее, возложил на основ­ное окно приложения. Этот факт привел к тому, что было потеряно достаточно много времени. Тем не менее, это вынудило меня хо­рошенько пораскинуть мозгами, и в конце концов эти размышле­ния привели меня к правильному решению.

Я старался рассуждать логически. Я хочу отобразить содержи­мое файла. При этом хочу сделать это в соответствии с требова­ниями архитектуры «документ/представление». Но у меня нет яс­ности по многим вопросам. Когда, в какой момент файл на диске должен превратиться в документ? Наверное, в момент открытия файла. Другими словами, при обработке команды на открытие файла, полученной от меню, я должен открыть файл и превратить его в документ. Но какой файл надо открыть? Естественно, тот файл, который я укажу. Где укажу? Конечно же, в стандартном диа­логовом окне для выбора открываемого файла! Значит, мне необ­ходимо первым делом подготовить данные для открытия стандарт­ного диалога. После того как я выберу файл, мне необходимо бу­дет его открыть, разобрать по косточкам и каким-то образом со­держимое файла превратить в документ… Н-да, работы непоча­тый край…



А теперь текст непосредственно программы:

#include "stdafx.h"

// Объявляем класс приложения, его поля и методы.

class CDocViewlApp : public CWinApp

{

public:

CDocViewlApp () ; :* protected:

afx_msg void OnFileOpen(); virtual BOOL Initlnstance (); DEC LARE_ME S S AG E_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

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, OnFileOpen ) END_MESSAGE_MAP()

CDocViewlApp theApp;



С чего я начал? Написал небольшую программу, при помощи которой хотел всего-навсего выяснить характер взаимодействия объектов раз­ных классов при работе в соответствии с идеологией архитектурой «документ/представление». Эта программа не должна делать ниче­го, кроме отображения пустого документа в (естественно!) пустом окне отображения. Впоследствии я планировал использовать текст этой программы как заготовку для других программ.

Ниже приведен текст файла ресурсов моей программы. Ком­ментарии, созданные средой Visual С++, я предварительно удалил.

#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS

#include "afxres.h"

#undef APSTUDIO_READONLY_SYMBOLS

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS)

#ifdef _WIN32

LANGUAGE LAN G__RU SSI AN, SUBLANG_DE FAULT #pragma code_page(1251) #endif //_WIN32

#ifdef APSTUDIO_INVOKED

1 TEXTINCLUDE DISCARDABLE BEGIN

"resource. h\0"

END

2 TEXTINCLUDE DISCARDABLE . BEGIN

"#include xw‘afxres.h""\r\n" "\0"

END

3 TEXTINCLUDE DISCARDABLE BEGIN

"\r\n" "\0"

END

#endif // APSTUDIO_INVOKED

IDR_RESOURCE ICON DISCARDABLE "ResWCommon.ico"

IDR_RESOURCE MENU DISCARDABLE BEGIN

ID_FILE_OPEN ID APP EXIT

POPUP "&File"

BEGIN

MENUIТЕМ "&Open\tCtrl+0", MENUIТЕМ SEPARATOR MENUIТЕМ "E&xit\tCtrl+x",

END

POPUP "SWindow", GRAYED BEGIN

MENUITEM "Tile &horizontally\tCtrl+H", ID_WINDOW_TILEHORZ, GRAYED

MENUITEM "Tile &vertically\tCtrl+V", ID_WINDOW_TILE_VERT, GRAYED

MENUIТЕМ "&Cascade\tCtrl+C", ID_WINDOW_CASCADE

END

POPUP "Help" BEGIN

MENUIТЕМ "&About", ID_APP_ABOUT

END

END

STRINGTABLE DISCARDABLE BEGIN

ID_FILE_OPEN "Open an existing document"

END

"Display program information, version number and copyright" "Quit the application"

STRINGTABLE DISCARDABLE BEGIN

ID_APP_ABOUT

ID_APP_EXIT

END

STRINGTABLE DISCARDABLE BEGIN

IDR_RESOURCE "PE-file viewer and disassembler"

END



08.02.2010

Тем не менее, мы может прекратить работу архива в любое время при помощи метода Abort(), исходный текст которого находится в файле агссоге.срр:

void CArchive::Abort() {

ASSERT(m_bDirectBuffer ||

m_lpBufStart == NULL || AfxIsValidAddress(m_lpBufStart,

m_lpBufMax – m_lpBufStart, IsStoring()));

ASSERT(m_bDirectBuffer ||

m_lpBufCur == NULL || AfxIsValidAddress(m_lpBufCur,

m_lpBufMax – m_lpBufCur, IsStoring()));

// disconnect from the file mjpFile = NULL;

if (!m_bUserBuf) {

ASSERT(!m_bDirectBuffer); delete[] m_lpBufStart; m_lpBufStart = NULL; m_lpBufCur = NULL;

}

delete m_pSchemaMap; m_pSchemaMap = NULL;

// m_pStoreMap and m_pLoadArray are unioned, // so we only need to delete one

ASSERT((CObject*)m_pStoreMap == (CObject*)m_pLoadArray); delete (CObject*)m_pLoadArray; m_pLoadArray = NULL;Посмотрим, что происходит с архивом в случае прекращения работы посредством вызова метода AbortQ. Первым делом метод «отсоединяет» архив от файла, присваивая полю m__pFile значе­ние NULL. Затем в том случае, если у архива есть ассоциированный с ним буфер, производится удаление буфера. Если в буфере остались данные, которые не были записаны в файл, то эти данные будут потеряны. Указатели на начало буфера и на текущую позицию буфера делаются равными NULL. Затем удаляется указатель на хэш-таблицу, содержащую номера схем классов, а за ней и хэш-таблица (или массив) сохраненных объектов.



Для нас главным в этом методе является то, что происходит вызов метода PreCreateWindow() для нашего объекта окна пред­ставления:

Пример


BOOL CEditView::PreCreateWindow(CREATESTRUCT& cs) {

m_dwDefaultStyle = dwStyleDefault; return CCtrlView::PreCreateWindow(cs);

}

Вызов же метода PreCreateWindow() родительского объекта все ставит на свои места:

BOOL CCtrlView::PreCreateWindow(CREATESTRUCT& cs) {

ASSERT(cs.IpszClass == NULL); cs.IpszClass = m_strClass;

// initialize common controls

VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG)) ; AfxDeferRegisterClass(AFX_WNDCOMMCTLSNEW_REG);

// map default CView style to default style // WS_BORDER is insignificant

if ((cs.style I WS_BORDER) == AFX_WS_DEFAULT_VIEW)

cs.style = m_dwDefaultStyle & (cs.style | ~WS_BORDER);

return CView::PreCreateWindow(cs);

}



03.02.2010

Нетрудно заметить, что метод Create(), в свою очередь, вызы­вает метод CreateEx():

BOOL CWnd::CreateEx(DWORD dwExStyle,

LPCTSTR IpszClassName,

LPCTSTR IpszWindowName,

DWORD dwStyle,

int x,

int y,

int nWidth,

int nHeight,

HWND hWndParent,

HMENU nIDorHMenu,

LPVOID lpParam)

{

/I allow modification of several common create parameters CREATESTRUCT cs; cs.dwExStyle = dwExStyle; cs.IpszClass = IpszClassName; cs.lpszName = IpszWindowName; cs.style = dwStyle; cs.x = x; cs.y = y; cs.cx = nWidth; cs.cy = nHeight; cs.hwndParent = hWndParent; cs.hMenu = nIDorHMenu;

cs.hlnstance = AfxGetlnstanceHandle(); cs.lpCreateParams = lpParam;

if (!PreCreateWindow(cs)) {

PostNcDestroy(); return FALSE;

}

AfxHookWindowCreate(this);

HWND hWnd = ::CreateWindowEx(cs.dwExStyle,

cs.IpszClass, cs.IpszName, cs.style, cs .x, cs.y, cs.cx, cs.cy,

cs.hwndParent,
cs.hMenu,
‘*
cs.hlnstance,

cs.lpCreateParams);

#ifdef _DEBUG

if (hWnd == NULL) {

TRACE1("Warning: Window creation failed:

GetLastError returns 0x%8.8X\n",

GetLastError ());

#endif

if (!AfxUnhookWindowCreate()) PostNcDestroy();

// cleanup if CreateWindowEx fails too soon

if (hWnd == NULL)

return FALSE;
ASSERT (hWnd == m_hWnd) ;
fl

// should have been set in send msg hook return TRUE;

}



А теперь давайте займемся подсчетами. Вся моя программа со всеми описаниями классов, комментариями и пропусками состоит из 128 строк. Теперь вспомним, что нам пришлось бы сделать в том случае, если бы мы писали программу без использования архитек­туры "документ/представление", используя только функции API. Во-первых, нам понадобилось бы так называемое «стандартное за­клинание», в котором необходимо заполнить структуру типа WND-CLASS, зарегистрировать класс окна и запустить цикл обработки сообщений. Во-вторых, понадобилась бы обработка, как минимум, сообщения WM_CREAfE, во время которой необходимо создать окно класса «MDICLIENT». В-третьих, регистрация класса окна представления и написание оконной процедуры окна представле­ния. .. Можно только представить себе, сколько строк содержалось бы в исходном коде такого приложения!

Я достаточно долго программировал с использованием только API, поэтому не буду утверждать, что MFC хуже или лучше «чисто­го» API. Тем не менее, возможности, которые предоставляет архи­тектура «документ/представление», не могут не впечатлять.



Какое сообщение получает окно в том случае, когда окно должно перерисовать себя? Естественно, сообщение WM__PAINT. Какой метод должен отвечать за обработку сообщения WM_PAINT у объекта класса CView? Конечно же, CView::OnPaint(). Следовательно, нам неплохо было бы заглянуть в исходный текст этого метода, который, кстати, находится в файле viewcore.cpp:

void CView::OnPaint() {

// standard paint routine
CPaintDC dc(this);
OnPrepareDC(&dc);
OnDraw(&dc);
}_________

[CView::OnPaint ) Видно, что вызов конструктора класса

Ucview.OnPrepareDC) СРаПЮС опРеделяетчт0 «”вод будет
^ – > осуществляться на окно класса CView.

Что ж, логично, иного трудно было ожидать. Теперь заглянем в код

метода CView::OnPrepareDC():

void CView::OnPrepareDC(CDC* pDC, CPrintlnfo* plnfo) {

ASSERT_VALID(pDC);

UNUSED(pDC); // unused in release builds

// Default to one page printing if doc length not known if (plnfo != NULL)

plnfo->m_bContinuePrinting =

(pInfo->GetMaxPage() != Oxffff || (plnfo->m_nCurPage == 1) ) ;

}

void CView::OnDraw(CDC*)

{

}

только подтверждает нашу догадку. Наверное, для того чтобы убе­диться в правильности нашей догадки, лучше всего будет привес­ти написанную программу, осуществляющую вывод информации в окно представления. Сделаем так, чтобы программа выводила в окно представления имя файла, на основе которого создан до­кумент. Итак, описание класса CDocView нашей программы прини­мает следующий вид:

Пример

class CDocView : public CView {

DECLARE_DYNCREATE ( CDocView ) public:

CDocView();

virtual ~CDocView(); void OnDraw( CDC* pDC );

};

Ниже я привожу текст метода OnDraw():

void CDocView::OnDraw( CDC* pDC ) {

pDC->TextOut( 10, 10, GetDocument()->GetPathName () );

}

Программа откомпилировалась без ошибок!



Обратите внимание, читатель, что указатель на структуру типа 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. Единст­венное, что в этом случае требуется от программиста, так это написать приложение в соответствии с требованиями архитек­туры «документ/представление»!

Осталось понять одну «мелочь» – как осуществляется отобра­жение данных документа в окне представления.

Я опять старался рассуждать логически.



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

Любознательный читатель уже, наверное, приготовил мне во­прос: да, конечно, все что рассказывается, это верно, но каким же образом создается окно представления и как оно относится к окну фрейма и документу?



Я надеюсь, что читатель помнит о том, что пока при создании шаблона нашего документа указатель на информацию времени исполнения класса окна представления равен нулю. Следова­тельно, пока мы можем на некоторое время оставить вопрос о создании окна представления и вернуться в метод 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.



Вполне очевидно, что этот метод используется для того, чтобы определить класс и стили окна, которое будет создаваться.

Сначала определяется класс окна. В том случае, если класс окна до сих пор не определен (и ТОЛЬКО в этом случае), то полю Ipsz­Class присваивается значение _afxWndFrameOrView. Что находит­ся в этой переменной, станет ясным после того, как мы взглянем на участок кода, находящийся в файле afximpl.h:

// special AFX window class name mangling

#ifndef _UNICODE #define _UNICODE_SUFFIX #else

#define _UNICODE_SUFFIX _TPu") #endif

#ifndef _DEBUG #define DEBUG SUFFIX

#else

#define _DEBUG_SUFFIX _T(M") #endif

#ifdef _AFXDLL #define _STATIC_SUFFIX #else

#define _STATIC_SUFFIX _T(^s") #endif

#define AFX_WNDCLASS(s) \ _TPAfx") _T(s) _T("42") _DEBUG_SUFFIX

#define AFX_WND #define AFX_WNDCONTROLBAR #define AFX_WNDMDIFRAME #define AFX_WNDFRAMEORVIEW #define AFX WNDOLECONTROL

STATIC SUFFIX UNICODE SUFFIX

AFX_WNDCLASS("Wnd") AFX_WNDCLASSrControlBar") AFX_WNDCLASS("MDIFrame") AFX_WNDCLASS("FrameOrView") AFX_WNDCLASSrOleControl")

С другой стороны, в файле wincore.cpp мы увидим:

const TCHAR _afxWnd[] = AFX_WND;

const TCHAR _afxWndControlBar[] = AFX_WNDCONTROLBAR; const TCHAR _afxWndMDIFrame[] = AFX_WNDMDIFRAME; const TCHAR _afxWndFrameOrView[] = AFX_WN D FRAME ORVIEW; const TCHAR _afxWnd01eControl[] = AFX_WNDOLECONTROL;

Исходя из этих фактов, а также зная, что в настоящий момент мы генерируем отладочную версию нашей программы, сделаем вывод о том, что классом создаваемого окна фрейма будет являться «AfxFrameOrView42d». В качестве упражнения я предлагаю чита­телю проверить правильность этого утверждения.

Определив класс и стили создаваемого окна фрейма, мы полу­чили все необходимое для создания окна. Следовательно, ника­ких проблем создание окна фрейма вызвать не должно.



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



Итак, подведем промежуточные итоги. К настоящему времени мы выяснили, что необходимо сделать для того, чтобы MFC нача­ла работать с приложением в соответствии с требованиями архи­тектуры «документ/представление», выяснили, как определить, какой файл мы будем открывать, создали пустой документ. Однако обратите внимание, что до настоящего момента мы не можем ни­коим образом увидеть тот документ, который мы только что созда­ли. Нам просто негде визуализировать наш документ! Для того что­бы увидеть результаты нашей работы, нам необходимо срочно соз­дать окно представления. Следовательно, нам необходимо вернуть­ся к коду метода CMultiDocTemplate::OpenDocumentFile() и посмот­реть, что же происходит после того, как новый документ был соз­дан, каким образом создается окно, в котором будет отображаться содержимое нашего документа.

Вернувшись, мы видим, что после создания нового документа программа пытается создать фрейм для нашего документа. Кажет­ся, нас сейчас ждет очередная неприятность… Так и есть. Мы полу­чили очередное сообщение об ошибке.

Посмотрев внимательнее, мы заметим, что попутно нам было выдано и отладочное сообщение о необходимости переопределить метод CDocTemplate::CreateNewFrame «Еггог: you must override CDocTemplate::CreateNewFrame.». Сейчас, наверное, уже нет не­обходимости разбираться, что произошло. Наверное, читатель уже догадался. Да, дело именно в том, что при определении шаблона документа мы указатель на информацию времени выполнения фрейма нашего документа оставили равным NULL! Что ж, придет­ся внести еще небольшие изменения в нашу программу.



Вносим необходимые изменения в метод InitlnstanceQ нашего приложения:

BOOL CDocViewlApp::Initlnstance() {

fifdef _AFXDLL

Enable3dControls(); #else

Enable3dControlsStatic(); #endif

LoadStdProfileSettings(); CDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate( IDR_DOCUMENT,

RUNTIME_CLASS(CDoc), NULL, NULL ); AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow );

pMainFrame->UpdateWindow(); return TRUE;

}

Снова компилируем программу, запускаем на выполнение. ..И б-лагополучно минуем то место, на котором в прошлый раз у нас было выдано сообщение об ошибке. Однако нам необходимо по­нять, что происходит при создании документа, т. е. что происходит при работе метода CDocTemplate::CreateNewDocument().

CObject* CRuntimeClass::CreateObject() {

if (m_pfnCreateObject == NULL) {

TRACE(_T("Error: Trying to create object which is not ") _T("DECLARE_DYNCREATE \nor DECLARE_SERIAL: %hs.\n"), m_lpszClassName); return NULL;

}

CObject* pObject = NULL;

TRY

{

pObject = (*m_pfnCreateObject) () ;

}

END_TRY

return pObject;(CWJnApp::OnFJIeOpen ) Во-вторых, при помощи метода
S . CMultiDocTemplate::AddDocu-

ЦCDocManager::OnFileOpen J ment() (файл docmulti.cpp)

UcWinAppriOpenDocumentFile) только что созданный

N J документ добавля-

L-[ CDocManager::OpenDocumentFile ] ется в список

CMultiDocTemplate::OpenDocumentFjle] Документов
—’ шаблона:

CDocTemplate::CreateNewDocum^n?)

^CMultiDocTemplate::AddDocument]

void CMultiDocTemplate::AddDocument(CDocument* pDoc) {

ASSERT_VALID(pDoc);

CDocTemplate::AddDocument(pDoc);

ASSERT(m_docList.Find(pDoc, NULL) == NULL);

// must not be in list

m_docList.AddTail(pDoc);

}

CWinAPP::OnFileOpen ) A этот мет0* Фактически представ-
————————– J ляет собой вызов одноименного

ч

CDocManager::OnFileOpen ] метода базового класса

CWinApp::OpenPocumentFilQ (фаЙЛ doctemPlcPP):

^-{CDocManager^OpenDocumentFile")

L^CMultiDocTemplate::OpenDocumentFile] CDocTemplate::CreateNewDocument] CMultiDocTemplate::AddDocument] L^CDocTemplate::AddDocument ]

void CDocTemplate::AddDocument(CDocument* pDoc) {

ASSERT_VALID(pDoc);

ASSERT(pDoc->m_pDocTemplate == NULL); // no template attached yet pDoc->m_pDocTemplate = this;

Исходя из текстов этих методов, можно заметить, что не только у шаблона документа есть список документов, открытых на основе данного шаблона, но и у каждого документа есть указатель на тот шаблон, при помощи которого он был открыт. Этакая своеобраз­ная обратная связь!

Итак, что происходит при создании нового документа, мы выяс­нили. Фактически создается пустой документ, на него делается ссылка в шаблоне, а документ, в свою очередь, запоминает указа­тель на шаблон.



Итак, метод CDocTemplate::MatchDocType() используется для того, чтобы проверить, совпадает ли расширение открываемого файла с указанным в шаблоне документа. Зафиксировав этот факт, мы можем вернуться к методу CDocManager::OpenDocumentFile().

CDocument* CMultiDocTemplate::OpenDocumentFile(

LPCTSTR IpszPathName, BOOL bMakeVisible)

{

CDocument* pDocument = CreateNewDocument();

if (pDocument == NULL)

{

TRACEO("CDocTemplate::CreateNewDocument

returned NULL.\n"); AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC); return NULL;

}

ASSERT_VALID(pDocument);

BOOL bAutoDelete = pDocument->m_bAutoDelete; pDocument->m_bAutoDelete = FALSE;

// don’t destroy if something goes wrong CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL); pDocument->m_bAutoDelete = bAutoDelete; if (pFrame == NULL)

{

Af xMessageBox (AFX_IDP_FAILED_TO_CREATE_DOC) ;

delete pDocument; // explicit delete on error

return NULL;

}

ASSERT_VALID(pFrame);

if (IpszPathName == NULL) {

// create a new document – with default document name SetDefaultTitle(pDocument);

// avoid creating temporary compound file when // starting up invisible

if (IbMakeVisible)

pDocument->m_bEmbedded = TRUE;

if (!pDocument->OnNewDocument ())

{

// user has be alerted to what failed in OnNewDocument

TRACEO("CDocument::OnNewDocument returned FALSE An"); pFrame->DestroyWindow(); return NULL;

}

// it worked, now bump untitled count m_nUntitledCount++;

}

else {

// open an existing document CWaitCursor wait;

if (!pDocument->OnOpenDocument(IpszPathName)) {

// user has be alerted to what failed in OnOpenDocument TRACEO("CDocument::OnOpenDocument returned FALSE An"); pFrame->DestroyWindow (); return NULL;

}

pDocument->SetPathName(IpszPathName);

}

InitialUpdateFrame(pFrame, pDocument, bMakeVisible); return pDocument;

В качестве единственного аргумента используется имя откры­ваемого файла.

Во-первых, мы получили отладочное сообщение о -том, что нам необходимо переопределить метод CreateNewDocu-ment(): «Еггог: you must override CDocTemplate::CreateNew-Document.» Во-вторых, мы получаем сообщение об ошибке.

Что же могло случиться? Заглянем в исходный код метода CDocTemplate::CreateNewDocument():

CDocument* CDocTemplate::CreateNewDocument() {

// default implementation constructs one from CRuntimeClass if (m_pDocClass == NULL) {

TRACEO("Error: you must override

CDocTemplate::CreateNewDocument.\n") ;

ASSERT(FALSE); return NULL;

}

CDocument* pDocument =

(CDocument*)m_pDocClass->CreateObject() ; if (pDocument == NULL) {

TRACE1("Warning: Dynamic create of document type

%hs failed.\n", m_pDocClass->m_lpszClassName) ; return NULL;

}

ASSERT_KINDOF(CDocument, pDocument); AddDocument(pDocument); return pDocument;

Конечно! Мы же в нашей программе оставили указатель на ин­формацию времени исполнения объекта класса CDocument, равную NULL! А здесь первым делом осуществляется проверка, не равен ли нулю этот указатель! Что ж, делать нечего. Для того чтобы мы могли хоть немного продвинуться вперед, изменим нашу программу, доба­вив в начало ее описание класса нашего документа:

class CDoc : public CDocument {

DECLARE_DYNCREATE( CDoc ) public:

CDoc () ; virtual -CDoc(); >;

IMPLEMENT_DYNCREATE( CDoc, CDocument )

CDoc::CDoc()

{

}

CDoc::~CDoc()

{

}



Невооруженным глазом видно, что в начале функции ‘CDocTemplate::MatchDocType] производятся всевоз можные подготовительные действия, а также проверяется, не является ли выбран­ный нами файл ссылкой на другой файл. С точки зрения архитек­туры «документ/представление» все это не очень интересно. Ин­тересное начинается с момента начала перебора списка шабло­нов. Обратите внимание, уважаемый читатель, на то, что для каж­дого шаблона в списке шаблонов вызывается метод CDocTemplate::MatchDocType(). Этот метод определяет, насколько расширение открываемого файла соответствует указанному в шаб­лонах. В качестве аргументов этому методу передаются имя фай­ла, который мы хотим открыть, и переменная типа "указатель на документ", которая равна NULL. Ниже я привожу исходный текст этого метода. Его можно найти в файле docmgr.cpp:

CDocTemplate:Confidence

CDocTemplate::MatchDocType(LPCTSTR IpszPathName,

CDocument*& rpDocMatch)

{

ASSERT(IpszPathName != NULL); rpDocMatch = NULL;

// go through all documents

POSITION pos = GetFirstDocPosition();

while (pos != NULL)

{

CDocument* pDoc = GetNextDoc(pos);

if (AfxComparePath(pDoc->GetPathName(), IpszPathName)) {

// already open

rpDocMatch = pDoc; return yesAlreadyOpen;

}

}

// see if it matches our default suffix CString strFilterExt; if (GetDocString(strFilterExt,

CDocTemplate::filterExt) && !strFilterExt.IsEmpty())

{

// see if extension matches

ASSERT(strFilterExt[0] == 4.’);

LPCTSTR IpszDot = _tcsrchr(IpszPathName, ‘.’);

if (IpszDot != NULL &&

lstrcmpi(IpszDot, strFilterExt) == 0) return yesAttemptNative; // extension matches,

// looks like ours

}

// otherwise we will guess it may work return yesAttemptForeign;

}

Обратите внимание на следующие обстоятельства. Если файл с именем, совпадающим с первым аргументом метода, уже открыт в соответствии с текущим шаблоном, то указатель на открытый документ сохраняется в указателе, переданном в качестве второго документа. Другими словами, создается новая ССЫЛКА на откры­тый ранее документ, при этом вторая копия документа не создает­ся. В этом случае метод возвращает значение yesAlreadyOpen.

Если файл ранее не был открыт в соответствии с текущим шабло­ном, то выбирается подстрока, содержащая расширение файла (помните, читатель, описание метода CDocTemplate::GetDocString()?), из переданной шаблону строки ресурсов. Если эта подстрока сов­падает с расширением открываемого файла, то метод возвращает значение yesAttemptNative. Указатель на документ, переданный в качестве второго аргумента, при этом не изменяется и остается равным NULL.

И наконец, если расширение открываемого файла не соответ­ствует расширению, записанному в шаблоне, то метод возвраща­ет значение yesAttemptForeign. При этом указатель, переданный в качестве второго аргумента, также остается равным NULL.

Любознательный читатель, вероятно, тут же задаст мне вопрос. Как же так, ведь в перечислении CDocTemplate: Confidence исполь­зуются еще три значения, noAttempt, maybeAttemptForeign и may-beAttemptNative? А в каких случаях возвращаются эти значения? Здесь я могу только догадываться. Возможно, программисту по­требуется иногда делать предположение о том, что открываемый файл соответствует текущему шаблону вне зависимости от рас­ширения. Например, если шаблон файла предусматривает расши­рение «arj», а пользователь выбрал файл с расширением «aDD» (где «DD» означает две любые цифры), то, наверное, программист сможет, переопределив метод CDocTemplate::MatchDocType(), вер­нуть значение maybeAttemptNative. С другой стороны, если поль­зователь в этом случае выбрал файл с расширением, скажем, «txt», то программист может сделать предположение о том, что откры­ваемый файл не соответствует шаблону, и вернуть значение тау-beAttemptForeign. Наверное, возможны еще какие-то случае, когда программисту потребуется использовать значение noAttempt.



Итак, мы прошли уже достаточно большой путь. Самое главное, поняли, что необходимо делать в начале работы в рамках архитек­туры «документ/представление». Мы разобрались, какие ресурсы нам необходимо подготовить, создали шаблон документа, подго­товили к отображению окно выбора файла. Теперь нам необходи­мо выяснить, что происходит в ходе открытия файла.

CDocument* CDocManager::OpenDocumentFile(LPCTSTR IpszFileName) {

// find the highest confidence

POSITION pos = m_templateList.GetHeadPosition (); CDocTemplate:Confidence bestMatch =

CDocTemplate::noAttempt;

CDocTemplate* pBestTemplate = NULL; CDocument* pOpenDocument = NULL;

TCHAR szPath[_MAX_PATH];

ASSERT(lstrlen(IpszFileName) < _countof(szPath)) ;

TCHAR szTemp[_MAX_PATH];

if (IpszFileName[0] == yV" )

++lpszFileName;
lstrcpyn(szTemp, IpszFileName, _MAX_^ATH);
LPTSTR IpszLast = _tcsrchr(szTemp,
) ;

if (IpszLast != NULL)

*lpszLast = 0; AfxFullPath(szPath, szTemp); TCHAR szLinkName[_MAX_PATH]; if (AfxResolveShortcut(AfxGetMainWnd(),

szPath,

szLinkName,

_MAX_PATH)) lstrcpy(szPath, szLinkName);

while (pos != NULL) {

CDocTemplate* pTemplate =

(CDocTemplate*)m_templateList.GetNext(pos) ; ASSERT_KINDOF(CDocTemplate, pTemplate);

CDocTemplate::Confidence match;

ASSERT(pOpenDocument == NULL);

match = pTemplate->MatchDocType(szPath,

pOpenDocument);

if (match > bestMatch) {

bestMatch = match; pBestTemplate = pTemplate;

}

if (match == CDocTemplate::yesAlreadyOpen)
break;
// stop here

}

if (pOpenDocument != NULL) {

POSITION pos = pOpenDocument->GetFirstViewPosition() ; if (pos != NULL)

{

CView* pView = pOpenDocument->GetNextView(pos);

// get first one

ASSERT_VALID(pView);

CFrameWnd* pFrame = pView->GetParentFrame(); if (pFrame != NULL)

pFrame->ActivateFrame(); else

TRACEO("Error: Can not find a frame for document

to activate.\n");

CFrameWnd* pAppFrame;

if (pFrame != (pAppFrame =

(CFrameWnd*)AfxGetApp()->m_pMainWnd))

{

ASSERT_KINDOF(CFrameWnd, pAppFrame); pAppFrame->ActivateFrame();

}

}

else {

TRACEO("Error: Can not find a view for document

to activate An") ;

}

return pOpenDocument;

if (pBestTemplate == NULL) {

AfxMessageBox(AFX_IDP_FAILED_TO_OPEN_DOC) . return NULL;

return pBestTemplate->OpenDocumentFile(szPath);



В заключение необходимо привести пример использования стро­ки ресурсов для формирования диалога открытия файла. В файл ресурсов я добавил строку

IDRJDOCUMENT "\nExe-file\nExe-file\n

Executable files (*.exe)\n.exe\n\n"

Мне бы хотелось, чтобы в диалоговом окне мне предлагалось осуществить выбор из ехе-файлов. Естественно, мне необходимо указать идентификатор ресурса в методе lnitlnstance() моего при­ложения. После внесенных изменений он стал выглядеть следую­щим образом:

BOOL CDocViewlApp::Initlnstance () {

#ifdef _AFXDLL

Enable3dControls(); #else

Enable3dControlsStatic (); #endif

LoadStdProfileSettings(); CDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate( IDRJDOCUMENT,

NULL, NULL, NULL );

AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE );

m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ) ; pMainFrame->UpdateWindow() ;

return TRUE;

}

Посмотрим, правильны ли мои рассуждения и сработает ли в данном случае моя программа. Итак, компилируем… Запуска­ем… Выбираем «File», «Ореп» и…

Окно, появившееся на отображении, вы видите на 13. © Очень надеюсь, что того материала, который мы только что изу­чили, достаточно для понимания того, что происходит при подго­товке стандартного диалога. Надеюсь, что и в этом случае мы ус­пешно решили все поставленные задачи – мы поняли «физику» процесса и осознали возможную степень влияния программиста на процесс создания диалогового окна. Конечно, я мог бы объяс­нять все мелочи, встречающиеся в исходном коде, например, ото­бражение диалогового окна при помощи метода DoModal(), но имеет ли это смысл?