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

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

Архив за февраля, 2010

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



03.02.2010

На основании сказанного делаем вывод:

Строка ресурсов, идентификатор которой указывается при создании шаблона документа, должна состоять из подстрок, отделенных друг от друга символом-разделителем (по умол­чанию – ‘\п’). При этом пятая и четвертая подстроки соот­ветственно хранят наименование фильтра и непосредствен­но фильтр, используемые при формировании стандартно­го диалогового окна открытия файла.

Значит, вот для чего используются шаблоны документов в про­цессе формирования диалога открытия файла! Только для того, чтобы выбрать ПОДСТРОКИ, описывающие тип файла нашего до­кумента! Кажется, мы нашли еще один ответ на те вопросы, кото­рые сами себе и поставили! Теперь становится ясным смысл пято­го аргумента у DoPromptFileName().

Замечу попутно, что программист может изменить значение сим­вола-разделителя. Если он хочет в качестве разделителя исполь­зовать не символ ‘\п\ а любой другой, то все, что ему нужно, – это перекрыть метод CDocTemplate::GetDocString() и при вызове в нем AfxExtractSubString() в качестве четвертого аргумента добавить значение символа-разделителя. И все!

Ну, и, наверное, последний вопрос о подготовке стандартного окна. Допустим, что мы в шаблоне указали интересующее нас рас­ширение файлов. Сможем ли мы в диалоговом окне открыть файл не только с этим расширением? Ответ на этот вопрос следует из текста метода DoPromptFileName(). Разработчики MFC предвиде­ли это. В ЛЮБОМ случае к списку фильтров добавляется фильтр для открытия любого файла «АН Files ( *.*)».



Итак, функция AfxExtractSubString() разбирает строку (второй аргумент функции, LPCTSTR IpszFullString, – указатель на стро­ку), состоящую из подстрок, разделенных символом-разделите-лем(четвертый аргумент функции, TCHAR chSep, – символ раз­делитель), и возвращает указатель на объект класса CString (первый аргумент функции, CString& rString), в который будет записана подстрока с указанным индексом (третий аргумент функции, int iSubString, и есть номер подстроки). Соответствен­но, метод CDocTemplate::GetDocString() тоже возвращает указа­тель на подстроку с указанным индексом, но при этом индекс строки у него не превышает максимального значения, описан­ного в перечислении DocStringlndex, кроме этого, этот метод вынужден пользоваться значением символа-разделителя, при­нятого по умолчанию.



Мы разобрали назначение всех аргументов, за исключением пятого. Казалось бы, к чему при выдаче диалогового окна нам нуж­но знать шаблон документа? Да, наверное, весь шаблон нам знать ни к чему. Но не зря же мы ранее заметили, что шаблон документа является центром архитектуры «документ/представление» и хра­нилищем всей информации!

Обратите внимание, ува­жаемый читатель, что поэтому наш путь лежит к исходному тексту этой функции, кото­рый находится в файле docmgr.cpp:

AFX_STATIC void AFXAPI _AfxAppendFilterSuffix (

CStringS filter, OPENFILENAME& ofn, CDocTemplate*pTemplate, CString* pstrDefaultExt)

{

ASSERT_VALID(pTemplate);

ASSERT_KINDOF(CDocTemplate, pTemplate);

CString strFilterExt, strFilterName;

if (pTemplate->GetDocString(strFilterExt,

CDocTemplate::filterExt) && !strFilterExt.IsEmpty() && pTemplate->GetDocString(strFilterName, CDocTemplate::filterName)&& !strFilterName.IsEmpty())

{

// a file based document template – add to filter list ASSERT(strFilterExt[0] == Л.’); if (pstrDefaultExt != NULL) {

// set the default extension

*pstrDefaultExt = ((LPCTSTR)strFilterExt) + 1;

// skip the Л.’

ofn.lpstrDefExt = (LPTSTR)(LPCTSTR)(*pstrDefaultExt); ofn.nFilterlndex = ofn.nMaxCustFilter + 1;

// 1 based number

// add to filter

filter += strFilterName; ASSERT (! filter. IsEmpty ());// must have a file type name filter += (TCHAR)’\0′; // next string please filter += (TCHAR)’*'; filter += strFilterExt;

filter += (TCHAR)’\0′; // next string please ofn.nMaxCustFilter++;

(CDocManager::OnFileOpen ) Сердцем этого метода яв-

I _____ ляются, в свою очередь, вы-

|{ CDocManager::DoPromptFileName J зовы методов CDocTem-

I—{^AfxAppendFilterSuffix ) plate::GetDocString():

L-[CDocTemplate::GetDocString]

BOOL CDocTemplate::GetDocString(CStrirvg& rString,

enum DocStringlndex i) const

{

return AfxExtractSubString(rString, m_strDocStrings, (int)i); }

Несмотря на кажущуюся простоту вызова, здесь можно заме­тить кое-что интересное. Во-первых, в классе CDocTemplate есть перечисление DocStringlndex:

enum DocStringlndex {

windowTitle, // default window title

docName, // user visible name for default document

fileNewName, // user visible name for FileNew

// for file based documents:

filterName, // user visible name for FileOpen filterExt, // user visible extension for FileOpen // for file based documents with Shell open support: regFileTypeld, // REGEDIT visible registered

// file type identifier regFileTypeName, // Shell visible registered

// file type name

};

(CDocManager::OnFileOpen ] Об этом перечислении мы

I r —————— v достоточно скоро вспомним.

4CDocManager::DoPromptFileNameJ Во-вторых, наконец-то о се-

L-T_AfxAppendFilterSuffix Л бе напоминает та строка,

[ ( —----------------------- которую мы загрузили из ре-

L{CDocTemplate::GetDocStringj Сурс0в при создании шабло-

L_f AfxExtractSubstring] на Документа! Интересно

также, что в совокупности

именно с этой строкой индекс из перечисления передается функ­ции AfxExtractSubString(). Значит, этот индекс является либо номе­ром символа в строке, либо номером какой-то подстроки? Для того чтобы получить ответ и на этот вопрос, нам нужно лезть еще даль­ше в дебри MFC. Ниже я привожу определение (файл afxwin.h) и тек­ст (файл winstr.cpp) функции AfxExtractSubString():

BOOL AFXAPI AfxExtractSubString(CString& rString, LPCTSTR IpszFullString, int iSubString, TCHAR chSep = Лп‘);

BOOL AFXAPI AfxExtractSubString(CString& rString,

LPCTSTR IpszFullString, int iSubString, TCHAR chSep)

{

if (IpszFullString == NULL) return FALSE;

while (iSubString—) {

IpszFullString = _tcschr(IpszFullString, chSep);

if (IpszFullString == NULL)

{

rString.Empty(); // return empty string as well

return FALSE;

}

lpszFullString++; // point past the separator

}

LPCTSTR lpchEnd = _tcschr(IpszFullString, chSep); int nLen = (lpchEnd == NULL) ?

lstrlen(IpszFullString) : (int)(lpchEnd -

IpszFullString);

ASSERT(nLen >= 0);

memcpy(rString.GetBufferSetLength(nLen),

IpszFullString,

nLen*sizedf" (TCHAR) ) ; return TRUE;

}

Так вот оно что! Оказывается, строка, индекс ресурса которой мы указали при создании шаблона документа, состоит из подстрок! Каждая подстрока отделена от предыдущей символом-разделите­лем! Функции передается указатель на строку, состоящую из под­строк (второй аргумент), индекс подстроки (третий аргумент) и зна­чение символа-разделителя (по умолчанию это VT). Первый аргу­мент – это указатель на подстроку с соответствующим индексом. Если мы посмотрим на тексты DoPromptFileName() и __AfxAppend-FilterSuffix(), то придем к выводу, что пятая и четвертая подстроки хранят соответственно фильтр и название фильтра, используемые при формировании стандартного диалогового окна открытия фай­ла! Выскажем также предположение, что в перечислении DocString­lndex в некотором смысле определены назначения подстрок.



Итак, что мы видим? Во-первых, в самом на­чале текста функции со­здается объект класса CFileDialog, т. е. непосредственно диалоговое окно. Бросается в глаза, что первый аргумент функции, fileName, используется для записи в него име­ни выбранного файла. Также сразу видно, что четвертый аргу­мент метода, bOpenFileDialog, используется как параметр кон­структора диалогового окна. Если bOpenFileDialog равен TRUE, то создается окно для открытия файла. Если же bOpenFileDialog равен FALSE, то создается окно для сохранения файлов. В дан­ном случае bOpenFileDialog равен TRUE, поэтому создается окно для открытия файла. После создания окна из недр MFC загружается строка, содержащая заголовок этого стан­дартного окна. При этом идентификатором строки ресурсов, содержащей заголовок, является второй аргумент метода, nIDSTitle. Возможные значения идентификаторов и, соответ­ственно, заголовка окна я привожу в 15.

Если читатель помнит, то значение второго аргумента при вызо­ве функции было равно AFXJDS_OPENFILE, следовательно, у диа­логового окна будет заголовок «Ореп».

Затем в методе производится установка флагов диалогового окна. Флаги передаются методу как третий аргумент (в данном слу­чае из диалогового окна удаляется окошечко «Read only» и опре­деляется, что в пользователь может выбирать только имена уже существующих файлов).

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



Что нам для этого нужно сделать? Практически ничего. MFC уже подготовила за нас все необходимые данные, нам нужно только пару раз щелкнуть мышкой в диалоговом окне – и файл выбран! Есть, правда, два «но».

«Но» первое. Мы хотим знать «физику» процесса подготовки диалогового окна. «Но» второе – мы хотим изменить список выда­ваемых на отображение файлов, используя для этого другой фильтр. Чтобы ответить на этот вопрос, нам, естественно, придет­ся опять лезть в дебри MFC, и, в частности, начать придется с ме­тода DoPromptFileName():

BOOL CDocManager::QpPromptFileName(CStringS fileName,

UINT nIDSTitle, DWORD lFlags, BOOL bOpenFileDialog,

CDocTemplate* pTemplate)

CFileDialog dlgFile(bOpenFileDialog);

CString title;

VERIFY(title.LoadString(nIDSTitle));

dlgFile,m_ofn.Flags |= lFlags;

CString strFilter; CString strDefault; if (pTemplate != NULL) {

ASSERT_VALID(pTemplate);

_Af xAppendFilterSuf f ix (strFilter, *~

dlgFile,m_ofn,

pTemplate,

&strDefault);

}

else {

// do for all doc template

POSITION pos = m_templateList.GetHeadPosition() ; BOOL bFirst = TRUE;

while (pos != NULL) {

CDocTemplate* pTemplate =

(CDocTemplate*)m_templateList.GetNext(pos) ; _AfxAppendFilterSuffix(strFilter,

dlgFile.m_ofn, pTemplate, bFirst ? SstrDefault : NULL) ;

bFirst = FALSE;

}

}

// append the "*.*" all files filter CString allFilter;

VERIFY(allFilter.LoadString(AFX_IDS_ALLFILTER) ) ; strFilter += allFilter;

strFilter += (TCHAR)’\0′; // next string please strFilter += _T(XX*.*");

strFilter += (TCHAR)’\0′; // last string dlgFile.m_ofn.nMaxCustFilter++;

dlgFile.m_ofn.IpstrFilter = strFilter; dlgFile.m_ofn.IpstrTitle = title;

dlgFile.m_ofп.IpstrFile = fileName.GetBuffer(_МАХ_РАТН);

int nResult = dlgFile.DoModal(); fileName.ReleaseBuffer(); return nResult == IDOK;



Но взглянем на это окно повнимательнее. В основном меня все в нем устраивает, за исключением того, что мне предлагается осу­ществить выбор из всех имеющихся файлов. А если я хочу, чтобы мне был показаны только файлы с расширением «ехе» или «txt»? Могу ли я каким-нибудь образом изменить фильтр? А на этот во­прос ответить не так просто, как на первый. Здесь ответ не лежит на поверхности. Что ж, делать нечего, придется опять разбираться с исходным текстом MFC.

Поставим точку прерывания на методе CWinApp::OnFileOpen() в файле appdlg.cpp и запустим программу на выполнение. Стоп! Выполнение программы приостановилось. Исходный текст мето­да, на котором мы остановились, приведен ниже:

docmgr.cpp берем исходный код

метода CDocManager::OnFileOpen():

void CDocManager::OnFileOpen() {

// prompt the user (with all document templates) CString newName;

if (!DoPromptFileName(newName,

AFX_IDS_OPENFILE, OFN_HIDEREADONLY | FN_FILEMUS TEX1ST,

TRUE, NULL))

return; // open cancelled

AfxGetApp()->OpenDocumentFile(newName)/

// if returns NULL, the userhas already been alerted

CDocManager::OnFileOpen ]

Здесь же явно видно, что процесс открытия файла J разделен на два этапа. Этап

первый – вызов метода DoPromptFileNameO (забегая вперед, скажу, что именно эта функция обеспечивает подготов­ку и выдачу диалогового окна для выбора файла). Этап второй -непосредственно открытие файла и преобразование его в доку­мент. Наверное, логично будет, если мы изложение материала так же разделим на две части. В первой части мы должны рассмот­реть, каким образом формируется стандартное диалоговое окно для выбора открываемого файла. Кроме этого, мы должны опре­делить, каким образом мы можем повлиять на процесс формиро­вания этого окна. Во второй части нам необходимо понять, что про­исходит после того, как имя открываемого файла определено. Дру­гими словами, нам нужно понять, как из файла рождается доку­мент.



Итак, мы создали заготовку шаблона документа. Но до сих пор нам не ясно, что происходит непосредственно после того, как мы выбе­рем элемент «Ореп» меню «File». Другими словами, мы не знаем, как происходит открытие документа.

Естественно, до написания этих заметок я уже трассировал метод CWinApp::OnFileOpen(). Поэтому мне бы хотелось, чтобы чи­татель набрался терпения. Работа предстоит не очень сложная, однако достаточно кропотливая.

(cWinApp::OnFileOpen J Итак>после создания шаблона документа нам необходимо создать документ. О том, что такое документ и что нам необходимо сделать для того, чтобы создать его, мы пока не имеем ни малейшего понятия. Именно поэто­му мы не сделали НИКАКИХ изменений при вызове конструктора шаб­лона. Указатель на информацию времени выполнения нашего доку­мента при инициализации шаблона до сих пор равен NULL.

Что ж, откомпилируем нашу программу и постараемся выпол­нить ее. Так, окно отобразилось. Выбираем элемент «Ореп» меню «File»… Вот это да! В этот момент произошло то, чего я, честно говоря, никак не ожидал. На экране появилось стандартное диало­говое окно (см. 12), предлагающее мне осуществить выбор того файла, который будет открыт.

Откуда оно взялось? Я же не писал ни строчки кода для вызова этого окна! Мне так и захотелось воскликнуть: «Ай да MFC!» © Биб­лиотека оказала мне такую услугу! Как вспомню о том множестве полей, которые необходимо было заполнить соответствующими зна­чениями при подготовке стандартного диалогового окна… Ответ на вопрос о «происхождении» окна очевиден. Код, обеспечиваю­щий выдачу диалогового окна находится где-то в недрах MFC. Иначе

) tookpif^MyProjects

· sti

· the first

Fitebarne:: r[" .Files of typer

Cancel

просто и быть не может. По мановению волшебной палочки стан­дартные диалоговые окна не создаются.



03.02.2010

Здесь мы увидим кое-что интересное. Так… MFC пытается за­грузить меню и таблицу акселераторов… Но их идентификатор совпадает с идентификатором строки, которую пытается загрузить конструктор базового класса! Отсюда следующие выводы.

Вывод первый. В состав ресурсов, идентификатор которых используется при создании шаблона документа, могут входить строка, меню и таблица акселераторов. Вывод второй (обратный первому). Идентификаторы строки, меню и таблицы акселераторов, которые будут использоваться при работе программы, написанной в соответствии с архитектурой «документ/представление»,должны быть ОДИНАКОВЫМИ!

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

Метод CWinApp::AddDocTemplate(CDocTemplate* pTemplate) ни­чего интересного для нас в себе не содержит. В нем происходит инициализация списка шаблонов и добавление шаблона в этот список. Мне кажется, что более подробное рассмотрение этого вопроса ничего существенного к пониманию нами процесса созда­ния шаблона не добавит.

Пример

Ну, кажется, «охота на ресурсы» завершена успешно. Мы выяс­нили, какие ресурсы могут входить в состав приложения. Обрати­те, пожалуйста, внимание на то, что мы НИ РАЗУ не заглянули в документацию, предлагаемую MSDN, или в какие-нибудь другие пособия по MFC. Исходный код MFC позволил нам самостоятель­но сделать выводы, приведенные выше.

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



Теперь можно вернуться к CMultiDocTemplate::CMultiDocTemplate(). Обратите, пожалуйста, внимание, уважаемый читатель на второй вызов LoadTemplate():

void CMultiDocTemplate::LoadTemplate() {

CDocTemplate::LoadTemplate();

if (m_nIDResource != 0 && m_hMenuShared == NULL) {

HINSTANCE hlnst = AfxFindResourceHandle(

MAKEINTRESOURCE(m_nIDResource), RT_MENU); m_hMenuShared = ::LoadMenu(hlnst,

MAKEINTRESOURCE(m_nIDResource)); m_hAccelTable = ::LoadAccelerators(hlnst,

MAKEINTRESOURCE(m_nIDResource));

}



03.02.2010

Что здесь может быть важным для дальнейшего понимания предупреждает нас о том, что указатели на информацию времени выполнения не должны быть равными NULL. Во-вторых, класс документа должен быть унаследован от CDocument, класс фрейма – от CFrameWnd и, наконец, класс представления – от CView. В-третьих, поля m__nlDResource, m_pDocClass, m_pFrameClass и mjDViewClass инициализируются теми значениями, которые мы указали при вызове конструктора. Здесь нет ничего странного, и это мы могли предполагать. Больший интерес вызывает обращение к функции LoadTemplate():

void CDocTemplate::LoadTemplate() {

if (m_strDocStrings.IsEmpty() &&

!m_strDocStrings.LoadString(m_nIDResource) )

{

TRACE1 ("Warning: no document names irv. string for

template #%d.\n", m_nIDResource) ;

}

if (m_nIDEmbeddingResource != 0 && m_hMenuEmbedding == NULL)

{

// load menu to be used while editing an embedding // (as a server)

HINSTANCE hlnst = AfxFindResourceHandle(

MAKEINTRESOURCE(m_nIDEmbeddingResource) ,

RT_MENU);

m_hMenuEmbedding = ::LoadMenu(hlnst,

MAKEINTRESOURCE(m_nIDEmbeddingResource) ) ; m_hAccelEmbedding = ::LoadAccelerators(hlnst,

MAKEINTRESOURCE(m_nIDEmbeddingResource) ) ;

}

if (m_nIDServerResource != 0 && m_hMenuInPlaceServer == NULL)

{

// load menu to be used while editing in-place // (as a server)

HINSTANCE hlnst = AfxFindResourceHandle(

MAKEINTRESOURCE(m_nIDServerResource) , RT_MENU);

m_hMenuInPlaceServer = ::LoadMenu(hlnst,

MAKEINTRESOURCE(m_nIDServerResource) ) ;

m_hAccelInPlaceServer = ::LoadAccelerators(hlnst,

MAKEINTRESOURCE(m_nIDServerResource) ) ;

}

if (m_nIDContainerResource != 0 && m_hMenuInPlace == NULL) {

// load menu to be used while in-place editing // session (as a container)

HINSTANCE hlnst = AfxFindResourceHandle(

MAKEINTRESOURCE(m_nIDContainerResource), RT_MENU);

m_hMenuInPlace = ::LoadMenu(hlnst,

MAKEINTRESOURCE(m_nIDContainerResource)); m_hAccelInPlace = ::LoadAccelerators(hlnst,

MAKEINTRESOURCE(m nIDContainerResource));

(CMultiDocTemplate::CMultiDocTemplate)

4CMultiDocTemplate::LoadTemplate ]

Взглянем повнимательнее на текст этого метода. Во-перВых, мы заметим, что в поле m_strDocStrings загружается содержимое СТРОКИ, зафужаемой из ресурсов. И, что очень важно, идентификатор этой строки равен тому, который мы указали при вызове конструктора. Если строка по каким-то причинам в ресурсах не найдена, то нам будет выдано отладочное сообщение «Warning: no document names in string for template…» Сле­довательно, MFC подсказывает нам, что в строковом ресурсе долж­ны быть какие-то имена документов. Запомним это. Забегая вперед, скажу, что нам этот факт еще пригодится.



fcMultiDocTemplate::CMultiDocTemplatej Первым, естественно, был вызван сам конструктор. Его исходный текст находится в файле docmulti.cpp:

CMultiDocTemplate::CMultiDocTemplate(UINT nIDResource,

CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass) : CDocTemplate(nIDResource, pDocClass, pFrameClass, pViewClags)

{

ASSERT(m_docList.IsEmpty()); m_hMenuShared = NULL; m_hAccelTable = NULL;

m_nUntitledCount = 0; // start at 1

// load resources in constructor if not // statically allocated if (ICDocManager::bStaticInit) LoadTemplate();

(CMultiDocTemplate-CMultiDocTemplateJ Очевидно, что аргументы,
I г~ ~ ^ с которыми производится

L-{CDocTemplate::CDocTemplate ] вызов конструктора CMultl.

DocTemplate(), передаются конструктору родительского класса CDocTemplate, чей исходный код находится в файле doctempl.cpp:

CDocTemplate::CDocTemplate(UINT nIDResource,

CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)

{

ASSERT_VALID_IDR(nIDResource); ASSERT(pDocClass == NULL ||

pDocClass-IsDerivedFrom(RUNTIME_CLASS(CDocument) ) ) ; ASSERT(pFrameClass == NULL ||

pFrameClass->IsDerivedFrom(RUNTIME_CLASS(CFrameWnd) ) ) ASSERT(pViewClass == NULL ||

pViewClass->IsDerivedFrom(RUNTIME_CLASS(CView)) ) ;

m_nIDResource = nIDResource; m_nIDServerResource = NULL; m_nIDEmbeddingResource = NULL; m nIDContainerResource = NULL;

m_pDocClass = pDocClass; m_pFrameClass = pFrameClass; m_pViewClass = pViewClass; m_p01eFrameClass = NULL; m_p01eViewClass = NULL;

m_pAttachedFactory = NULL; m_hMenuInPlace = NULL;

m_hAccelInPlace = NULL; m_hMenuEmbedding = NULL; m_hAccelEmbedding = NULL; m_hMenuInPlaceServer = NULL; m_hAccelInPlaceServer = NULL;

// add to pStaticList if constructed as static

// instead of on heap

if (CDocManager:rbStaticInit)

{

m_bAutoDelete = FALSE;

if (CDocManager::pStaticList == NULL)

CDocManager:rpStaticList = new CPtrList; if (CDocManager::pStaticDocManager == NULL)

CDocManager::pStaticDocManager = new CDocManager; CDocManager::pStaticList->AddTail(this);

else

{

m_bAutoDelete = TRUE; // usually allocated on the heap LoadTemplate();



Я постарался откомпилировать эту программу, но получил массу диагностических сообщений, из которых следовало, что класс CDocTemplate является абстрактным и для того, чтобы на­следовать от него другие классы, мне надо переопределить массу методов. Меня это не устраивало. Во-первых, я только изучаю возможности MFC, следовательно, у меня нет желания пере­писывать половину методов. Во-вторых, a priori я уверен, что разработчики MFC довели работу до логического завершения и предоставили программисту классы, унаследованные от CDocTemplate, готовые к немедленному использованию. По­копавшись в исходных текстах MFC, я обнаружил, что от CDocTemplate наследуются два класса – CSingleDocTemplate и CMultiDocTemplate. Судя по привеленным комментариям, пер­вый из этих классов предназначен для работы с однодокументным интерфейсом, а второй – для работы с MDI. Я подумал и ре­шил, что моя программа должна поддерживать многодокумент­ный интерфейс, поэтому в своей программе вместо абстрактного класса CDocTemplate я использовал производный от него класс CMultiDocTemplate. Мой расчет на то, что это не должно оказать особого влияния на понимание архитектуры «документ/пред­ставление», оказался верен. Еще одной особенностью этой программы было то, что я понятия не имел о том, идентификатор каких ресурсов я должен указывать в качестве первого аргумента конструктора и надеялся выяснить это при помощи MFC. Поэтому идентификатор ресурсов я сделал равным нулю.

После внесенных изменений я получил следующий код:

BOOL CDocViewlApp::Initlnstance() {

#ifdef _AFXDLL

Enable3dControls() ; #else

Enable3dControlsStatic(); #endif

LoadStdProfileSettings();

CMultiDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate( 0,

NULL, NULL, NULL );

AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();

return TRUE;

Программа откомпилировалась нормально, но, запустив про­грамму на выполнение, я немедленно получил сообщение об ошиб­ке, представленное на 11.

Заглянув в место возникновения ошибки, я узнал, что сообще­ние выдано макросом ASSERT_VALID_IDR, который находится в файле afxpriv.h:

#define ASSERT_VALID_IDR(nIDR) ASSERT((nIDR) != 0 &&

(nIDR) < 0×8000)

Значит, идентификатор ресурсов не может быть нулем или более 0×7fff. Попутно замечу, что в том же файле указано, что номера ресурсов от 0 до 0×6fff могут использоваться програм­мистами, а номера от 0×7000 до 0×7fff зарезервированы для ре­сурсов MFC и стандартных ресурсов Windows. Так… Это не бог весть что, но уже какая-то отправная информация для нас есть. Изменим значение нашего идентификатора ресурсов, скажем, на 0×6fff.. Замечательно! Программа откомпилировалась без ошибок. Я поставил точку прерывания на конструктор шаблона документа и стал смотреть, что происходит во время создания шаблона.



После приведенных изменений метод CDocView1::lnitlnstance() имел следующее содержание:

BOOL CDocViewlApp::Initlnstance() {

#ifdef _AFXDLL

Enable3dControls (); #else

Enable3dControlsStatic(); #endif

LoadStdProfileSettings(); CDocTemplate* pDocTemplate; pDocTemplate = new CDocTemplate( 0,

NULL ),

NULL,

NULL );

AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();

return TRUE;

}



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



Вспомним, что класс CDocManager является хранилищем указа­телей на шаблоны документов. С другой стороны, метод AddDocTem-plate() в качестве аргумента использует указатель на объект класса CDocTemplate. Следовательно, в этот момент в игру вступает объект класса CDocTemplate. Как следует из названия, он представляет со­бой именно шаблон документа. Подробное описание этого класса мы приведем позже, а сейчас заметим только, что конструктор этого класса имеет следующий вид:

protected:

CDocTemplate(UINT nIDResource,

CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass);

Так… Для инициализации шаблона документа нам нужна ин­формация времени исполнения о каких-то трех составляющих. Судя по названиям этих составляющих, речь в данном случае идет о до­кументе, окне фрейма и окне представления. Значит, этот так на­зываемый шаблон документа – это не просто шаблон, а это не­кий контейнер, который соединяет в себе воедино информацию о трех важнейших составляющих архитектуры «документ/представ­ление». Тем более важно понять, как он создается, какие данные он использует, и как эти данные взаимодействуют друг с другом.



03.02.2010

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

В приложении 1У|ожно не создавать явным образом объект класса CDocManager. Если объект класса CDocManager не был создан вручную до момента добавления первого шаб­лона, то в момент добавления первого шаблона документа этот объект будет создан автоматически.

Давайте вспомним также, что при вызове метода CWinApp::Add-DocTemplate(CDocTemplate*pTemplate) фактически вызывается метод объекта класса CDocManager:: AddDocTemplate( CDocTemplate* pTem­plate). Из этого факта можно сделать еще один вывод.

Для того, чтобы передать нашему приложению информа­цию о том, с какими типами документов ему придется рабо­тать, нам необходимо до создания (или открытия) документа добавить в список шаблонов шаблон нашего документа. Всю остальную работу MFC, вероятно, сделает самостоя­тельно!

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



Здесь мне представляется необходимым еще раз привести ис­ходный код метода CWinApp::AddDocTemplate():

void CWinApp::AddDocTemplate(CDocTemplate* pTemplate) {

if (m_pDocManager == NULL)

m_pDocManager = new CDocManager; m_pDocManager->AddDocTemplate(pTemplate);

}



03.02.2010

Строчку, которая нас интересует, мы найдем в файле afxwin.h. В описании класса CWinApp среди множества полей мы увидим:

CDocManager* m_pDocManager;

Значит, мы на правильном пути! Более того, это поле описа­но как общедоступное (public)! Но почему-то в MSDN я не нашел ни слова о классе CDocManager, за исключением нескольких упо­минаний в разделах периодики и Knowledge Base. Честно гово­ря, меня это несколько удивило. С одной стороны, член класса описан как доступный (public). А с другой, чтобы использовать его сначала нужно пролезть по исходникам MFC. Тем самым тех­нические писатели, готовившие описание MFC, разорвали связь между приложением и СИСТЕМОЙ используемых при работе архитектуры «документ/представление» классов. Но ничего не поделаешь. Из-за того, что в MSDN нет ни слова об объекте это­го класса, нам придется немного задержаться и взглянуть на описание этого объекта.

Сначала нам неплохо было бы получить хотя бы общее представ­ление о том, что представляет собой класс CDocManager. В описании класса CWinApp можно найти всего три метода, которые работают с этим полем. Этими методами являются AddDocTemplate(), Get-FirstDocTemplatePosition() и GetNextDocTemplate(). Их исходный код, который находится в файле appui2.cpp, приведен ниже:

void CWinApp::AddDocTemplate(CDocTemplate* pTemplate) {

if (m_pDocManager == NULL)

m_pDocManager = new CDocManager; m_pDocManager->AddDocTemplate(pTemplate) ;

}

POSITION CWinApp::GetFirstDocTemplatePosition() const {

if (m_pDocManager == NULL) return NULL;

return m_pDocManager->GetFirstDocTemplatePosition();

}

CDocTemplate* CWinApp::GetNextDocTemplate(POSITIONS

rPosition) const

{

ASSERT(m_pDocManager != NULL);

return m_pDocManager->GetNextDocTemplate(rPosition) ;

}

Судя по именам и исходному коду, эти методы предназначены для работы со списком каких-то шаблонов, не так ли? В таком слу­чае возможно, что в основе своей класс CDocManager является списком, не так ли?