

Программирование на языке 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()) замечали, что методу передаются указатели, которые с первого взгляда совершенно не нужны для работы метода. Кажется, здесь тот же случай – ну зачем, скажите, пожалуйста, при создании фрейма знать указатель на документ? То, что буквально в первых строках метода осуществляется проверка того, не равен ли идентификатор ресурсов нулю, говорит о том, что, вероятнее всего, при создании фрейма опять будут использоваться ресурсы. Но, как говорится, поживем – увидим.
читать отзывы (0)
Итак, подведем промежуточные итоги. К настоящему времени мы выяснили, что необходимо сделать для того, чтобы 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(), но имеет ли это смысл?
На основании сказанного делаем вывод:
Строка ресурсов, идентификатор которой указывается при создании шаблона документа, должна состоять из подстрок, отделенных друг от друга символом-разделителем (по умолчанию – ‘\п’). При этом пятая и четвертая подстроки соответственно хранят наименование фильтра и непосредственно фильтр, используемые при формировании стандартного диалогового окна открытия файла.
Значит, вот для чего используются шаблоны документов в процессе формирования диалога открытия файла! Только для того, чтобы выбрать ПОДСТРОКИ, описывающие тип файла нашего документа! Кажется, мы нашли еще один ответ на те вопросы, которые сами себе и поставили! Теперь становится ясным смысл пятого аргумента у 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(), то придем к выводу, что пятая и четвертая подстроки хранят соответственно фильтр и название фильтра, используемые при формировании стандартного диалогового окна открытия файла! Выскажем также предположение, что в перечислении DocStringlndex в некотором смысле определены назначения подстрок.
Итак, что мы видим? Во-первых, в самом начале текста функции создается объект класса 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
просто и быть не может. По мановению волшебной палочки стандартные диалоговые окна не создаются.
Здесь мы увидим кое-что интересное. Так… 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));
}
Что здесь может быть важным для дальнейшего понимания предупреждает нас о том, что указатели на информацию времени выполнения не должны быть равными 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);
Так… Для инициализации шаблона документа нам нужна информация времени исполнения о каких-то трех составляющих. Судя по названиям этих составляющих, речь в данном случае идет о документе, окне фрейма и окне представления. Значит, этот так называемый шаблон документа – это не просто шаблон, а это некий контейнер, который соединяет в себе воедино информацию о трех важнейших составляющих архитектуры «документ/представление». Тем более важно понять, как он создается, какие данные он использует, и как эти данные взаимодействуют друг с другом.
Взглянув на этот текст, мы можем сделать вывод, который в значительной степени облегчит нам жизнь.
В приложении 1У|ожно не создавать явным образом объект класса CDocManager. Если объект класса CDocManager не был создан вручную до момента добавления первого шаблона, то в момент добавления первого шаблона документа этот объект будет создан автоматически.
Давайте вспомним также, что при вызове метода CWinApp::Add-DocTemplate(CDocTemplate*pTemplate) фактически вызывается метод объекта класса CDocManager:: AddDocTemplate( CDocTemplate* pTemplate). Из этого факта можно сделать еще один вывод.
Для того, чтобы передать нашему приложению информацию о том, с какими типами документов ему придется работать, нам необходимо до создания (или открытия) документа добавить в список шаблонов шаблон нашего документа. Всю остальную работу MFC, вероятно, сделает самостоятельно!
Зная все это, можно более подробно описдть причину, которая вызвала появление сообщения об ошибке. Ошибка, из-за которой мы полезли в глубины MFC, заключается в том, что мы не добавили в приложение ни одного шаблона документа, из-за чего не был проинициализирован m_pDocManager и мы получили соответствующее сообщение. Ура! Причину ошибки мы определили! Но ведь нам нужно не только определить причину ошибки, но и устранить ее! Другими словами, нам необходимо создать шаблон документа, а потом добавить его в список шаблонов.
Здесь мне представляется необходимым еще раз привести исходный код метода CWinApp::AddDocTemplate():
void CWinApp::AddDocTemplate(CDocTemplate* pTemplate) {
if (m_pDocManager == NULL)
m_pDocManager = new CDocManager; m_pDocManager->AddDocTemplate(pTemplate);
}
Строчку, которая нас интересует, мы найдем в файле 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 является списком, не так ли?
