

Программирование на языке MFC
Мой второй блог в серии программирования
Архив раздела «Архитектура»
Что мы можем здесь увидеть? То, что в описании этого класса присутствует поле rnJTemplateList класса CPtrList, только подтверждает нашу догадку о том, что de facto объекты класса CDocManager являются списками указателей на какие-то шаблоны. Для того чтобы убедиться, что наша догадка верна, достаточно взглянуть на исходный код методов GetFirstDocTemplatePosition() и GetNext-DocTemplate(), который находится в файле docmgr.cpp:
POSITION CDocManager::GetFirstDocTemplatePosition() const {
return m_templateList.GetHeadPosition() ;
}
CDocTemplate* CDocManager::GetNextDocTemplate(
POSITIONS pos) const
{
return (CDocTemplate*)m_templateList.GetNext(pos) ;
Теперь возникает вопрос о том, что за шаблоны включаются в список. Позвольте, уважаемый читатель, высказать предположение о том, что этими шаблонами являются шаблоны ДОКУМЕНТОВ, с которыми работает данное приложение. Я не буду сейчас останавливаться на том, что такое шаблон документа. Это станет ясно из дальнейшего изложения.
Сейчас нам необходимо научиться создавать объект этого класса. Естественно, что для создания объекта мы воспользуемся конструктором. Раз конструктор класса CDocManager параметров не имеет, то создание объекта этого класса затруднений не вызовет. Но каким-то образом нам необходимо передать нашему приложению информацию о том, с документами каких типов (шаблонов) он будет иметь дело! Вспомним, что у класса CWinApp для добавления шаблона в список есть метод AddDocTemplate(), который, фактически является вызовом одноименного метода класса CDocManager. Наверное, именно этим методом и следует воспользоваться при добавлении шаблона документа!
читать отзывы (0)
Наверное, из описания класса CWinApp мы не сумеем извлечь еще какую-то информацию о классе CDocManager. Настало время взглянуть на описание этого класса. Думаю, нет необходимости описывать всю внутреннюю реализацию этого класса. Достаточно будет, если мы рассмотрим назначение наиболее часто используемых полей и методов.
Класс CDocManager в файле afxwin.h описан следующим образом:
class CDocManager : public CObject {
DECLARE_DYNAMIC(CDocManager) public:
// Constructor CDocManager();
//Document functions
virtual void AddDocTemplate(CDocTemplate* pTemplate); virtual POSITION GetFirstDocTemplatePosition() const; virtual CDocTemplate* GetNextDocTemplate(
POSITIONS pos) const; virtual void RegisterShellFileTypes(BOOL bCompat); void UnregisterShellFileTypes();
virtual CDocument* OpenDocumentFile(LPCTSTR IpszFileName);
// open named file
virtual BOOL SaveAllModified(); // save before exit virtual void CloseAllDocuments(BOOL bEndSession);
// close documents before exiting virtual int GetOpenDocumentCount();
// helper for standard commdlg dialogs virtual BOOL DoPromptFileName(CString& fileName
UINT nIDSTitle, DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate);
//Commands
// Advanced: process async DDE request
virtual BOOL OnDDECommand(LPTSTR IpszCommand); virtual void OnFileNew(); virtual void OnFileOpen();
// Implementation protected:
CPtrList m_templateList;
int GetDocumentCount(); // helper to count number
// of total documents
public:
static CPtrList* pStaticList;
// for static CDocTemplate objects static BOOL bStaticInit;
// TRUE during static initialization static CDocManager* pStaticDocManager;
// for static CDocTemplate objects
public:
virtual -CDocManager(); #ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const; #endif };
Значит, либо я сделал что-то не так, либо в программе не определил какие-то данные. Второй вариант, конечно, более вероятен. Для того чтобы убедиться в этом, давайте, уважаемый читатель, посмотрим на то место, в котором была обнаружена ошибка. Как оказалось, на этом месте находится исходный текст метода OnFileOpenQ класса CWinApp. Ниже приведен текст этого метода:
void CWinApp::OnFileOpen() {
ASSERT(m_pDocManager != NULL); m_pDocManager->OnFileOpen();
}
Помните, я говорил, что имеющий уши да услышит? Во-первых, судя по наименованию переменных, используемых в приведенном выше тексте метода, MFC по умолчанию считает, что наше приложение построено в соответствии с идеологией «Документ/представление»! Другими словами, именно эта идеология считается приоритетной при работе с MFC! Значит, именно ЭТОЙ идеологии в MFC отводится очень важная роль. Возможно, подумал я, что понимание одного из ключевых моментов MFC приблизит меня к пониманию идеологии всей библиотеки в целом! Но оставим «лирическое отступление» и продолжим нашу работу.
Во-вторых, здесь MFC сама подсказывает нам причину ошибки в программе! Из приведенного выше текста метода мы можем сделать три важных заключения. Заключение первое состоит в том, что у класса CWinApp есть член m_pDocManager, который, судя по его названию, является указателем на какой-то менеджер документов. Заключение второе – вероятнее всего, именно этот менеджер документов и отвечает за управление документами в приложении. И наконец, заключение третье. Очевидно, что единственной причиной, которая может привести к выдаче приведенного выше сообщения об ошибке, является равенство нулю указателя на менеджер документов. Следовательно, причина этой ошибки состоит в том, что мы просто-напросто не создали менеджер документов!
А что следует из этих заключений? Из этих заключений следует один очень важный вывод.
Тсли наше приложение не обрабатывает команду ID_FILE_OPEN и обработку производит метод CWinApp::OnFileOpen, то перед вызовом этого метода должен быть создан объект класса CDocManager и указатель на этот объект должен быть присвоен полю CWinApp::m_pDocManager.
К сожалению, искать информацию о менеджере документов в MSDN совершенно бесполезно. И в описании членов класса CWinApp мы также не найдем и упоминания об m_pDocManager. Придется опять смотреть исходные коды MFC…
Уважаемый читатель, обратите, пожалуйста, внимание, на тот факт, что сразу после создания структуры cs типа CREATESTRUCT все поля этой структуры обнуляются, в том числе обнуляется и поле IpszClass. В таком случае возникает законный вопрос – какого же класса окно будет создано? Ответ на этот вопрос можно найти в методе CFrameWnd::PreCreateWindow():

BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.IpszClass == NULL) {
VERIFY (Af xDef erRegisterClass (AFX_WNDFRAMEORVIEW_REG) ) ; cs.IpszClass = _afxWndFrameOrView; // COLOR_WINDOW background }
if ((cs.style & FWS_ADDTOTITLE) && afxData.bWin4) cs.style |= FWS_PREFIXTITLE;
if (afxData.bWin4)
cs.dwExStyle |= WS_EX_CLIENTEDGE;
return TRUE;
}
Для только что созданного объекта осуществляется вызов метода CMDIChildWnd::LoadFrame(). При этом в качестве аргументов методу передаются пресловутый идентификатор ресурсов, набор стилей окна (не объекта, а окна!), которое будет создано, указатель на родительское окно и, наконец, указатель на структуру типа Ccreate-Context, которую мы рассмотрели чуть раньше. Здесь стоит заметить, что по умолчанию стилями окна являются WSJDVERLAPPED-WINDOW и FWS_ADDTOTITLE. Исходный текст этого метода находится в файле winmdi.cpp:

BOOL CMDIChildWnd::LoadFrame(UINT nIDResource,
DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext)
{
// only do this once
ASSERT_VALID_IDR(nIDResource) ;
ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);
ASSERT(m_hMenuShared == NULL); // only do once
m_nIDHelp = nIDResource;
// ID for help context (+HID_BASE_RESOURCE)
// parent must be MDI Frame (or NULL for default)
ASSERT(pParentWnd == NULL ||
pParentWnd->IsKindOf(RUNTIME_CLASS(CMDIFrameWnd))); // will be a child of MDIClient
ASSERT(!(dwDefaultStyle & WS_POPUP));
dwDefaultStyle |= WS_CHILD;
// if available – get MDI child menus from doc template
ASSERT(m_hMenuShared == NULL); // only do once
CMultiDocTemplate* pTemplate; if (pContext != NULL &&
(pTemplate = (CMultiDocTemplate*)pContext->
m_pNewDocTemplate) != NULL)
{
ASSERT_KINDOF(CMultiDocTemplate, pTemplate); // get shared menu from doc template
m_hMenuShared = pTemplate->m_hMenuShared; m_hAccelTable = pTemplate->m_hAccelTable;
}
else {
TRACEO(“Warning: no shared menu/acceltable for MDI Child
window.\n”);
// if this happens, programmer must load these manually }
CString strFullString, strTitle;
if (strFullString.LoadString(nIDResource))
AfxExtractSubString(strTitle, strFullString, 0); // first sub-string
ASSERT(m_hWnd == NULL);
if (!Create(GetlconWndClass(dwDefaultStyle, nIDResource), strTitle, dwDefaultStyle, rectDefault,
(CMDIFrameWnd*)pParentWnd, pContext))
{
return FALSE; // will self destruct on failure normally
// it worked ! return TRUE;
}
Что мы можем увидеть в тексте этого модуля? Многое. Здесь даны ответы на еще несколько вопросов, которые мы напрямую не задавали, но которые, как говорится, висели в воздухе. Например, что еще, кроме меню, акселераторов и строки, может входить в состав передаваемых шаблону документа ресурсов? Каково назначение оставшихся подстрок строкового ресурса? Из текста модуля очевидно, что, во-первых, то, что в состав ресурсов, переданных шаблону документа, могут входить не только меню, акселераторы и строка, но и ресурсы помощи. Во-вторых, меню, которое мы создали, является меню окна фрейма! Для нас это очень важно, ибо, наконец, мы поняли, что за меню мы должны создавать при создании шаблона документа. Именно к окну фрейма и относятся акселераторы, которые мы можем создать одновременно с меню.
Этот метод непосредственно создает окно фрейма. Но до того момента, пока окно фрейма будет создано, происходит еще несколько интересных вещей. Например, перед вызовом метода Create() вызывается метод CFrameWnd::GetlconWnd-Class(). В качестве аргументов этому методу передаются стиль окна и опять-таки идентификатор ресурсов. Текст этой функции находится в файле winfrm.cpp:
LPCTSTR CFrameWnd::GetlconWndClass(DWORD dwDefaultStyle,
UINT nIDResource)
{
ASSERT_VALID_IDR(nIDResource); HINSTANCE hlnst =
AfxFindResourceHandle(MAKEINTRESOURCE(nIDResource),
RT_GROUP_ICON); HICON hlcon = ::LoadIcon(hlnst,
MAKEINTRESOURCE(nIDResource));
if (hlcon != NULL) {
CREATESTRUCT cs;
memset(&cs, 0, sizeof(CREATESTRUCT));
cs.style = dwDefaultStyle;
PreCreateWindow(cs); // will fill IpszClassName with default WNDCLASS name // ignore instance handle from PreCreateWindow.
WNDCLASS wndcls;
if (cs.IpszClass != NULL &&
GetClassInfo(AfxGetlnstanceHandle(), cs.IpszClass,
&wndcls) && wndcls.hlcon != hlcon)
{
// register a very similar WNDCLASS
return AfxRegisterWndClass(wndcls.style,
wndcls.hCursor, wndcls.hbrBackground, hlcon);
}
}
return NULL; // just use the default
}
Так… Оказывается, в состав ресурсов, передаваемых шаблону документа, может входить и иконка, которая будет использоваться при отображении окна фрейма! Функция пытается загрузить иконку из ресурсов, и, если иконка не загрузилась, то ничего не происходит и метод Create() использует данные, принятые по умолчанию, загрузилась, Если же иконка загрузилась нормально, то MFC, естественно, регистрирует класс окна, которому принадлежит иконка. После этого вызывается метод PreCreateWindow(), который мы можем использовать для того, чтобы произвести какие-то действия ДО создания окна.
Пока мы видим, что сразу после проверки того, что указатель на документ и идентификатор ресурсов ненулевые, создается структура типа CCreateContext. Описание этой структуры находится в файле afxext.h:
struct CCreateContext // Creation information structure // All fields are optional and may be NULL
{
// for creating.,new views CRuntimeClass* m_pNewViewClass;
// runtime class of view to create or NULL CDocument* m_pCurrentDoc;
// for creating MDI children (CMDIChildWnd::LoadFrame) CDocTemplate* m_pNewDocTemplate;
// for sharing view/frame state from the // original view/frame
CView* m_pLastView; CFrameWnd* m_pCurrentFrame;
// Implementation CCreateContext();
};
В поля этой структуры записываются указатели на документ, шаблон документа, а также указатели на фрейм, который будет создан, и на окно представления документа. Обратим внимание на этот факт, дело в том, что эти данные будут использованы в дальнейшем.
На что необходимо обратить внимание? К этому моменту мы еще не представляем характера взаимодействия между документом и фреймом. Поэтому просто предположим, что каким-то образом наш документ будет отображаться в рамках фрейма. Давайте поразмыслим, уважаемый читатель. Мы пишем программу, которая будет работать с многодокументным интерфейсом. Наша программа должна отображать данные в одном из дочерних окон многодокументного интерфейса. Следовательно, логично будет в качестве фрейма использовать окно класса CMDIChildWnd. Давайте так и поступим, уважаемый читатель. Итак, ниже я привожу текст метода CDocView1App::lnitlnstance() нашей программы:
BOOL CDocViewlApp::Initlnstance () {
#ifdef _AFXDLL
Enable3dControls(); #else
Enable3dControlsStatic(); #endif
LoadStdProfileSettings(); CDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate( IDR_DOCUMENT,
RUNTIME_CLASS( CDoc ),% RUNTIME_CLASS( CMDIChildWnd ),
NULL );
AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();
return TRUE;
}
создается фрейм этого документа. Нетрудно догадаться, что создание фрейма происходит при вызове метода CDocTemplate:: CreateNewFrame():
CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc,
CFrameWnd* pother)
{
if (pDoc != NULL)
ASSERT_VALID(pDoc); // create a frame wired to the specified document
ASSERT(m_nIDResource != 0); // must have a resource. ID
// to load from
CCreateContext context;
context,m_pCurrentFrame = pother;
context.m_pCurrentDoc = pDoc;
context,m_pNewViewClass = m_pViewClass;
context.m_pNewDocTemplate = this;
if (m_pFrameClass == NULL) ‘ {
TRACEO("Error: you must override
CDocTemplate::CreateNewFrame.\n") ;
ASSERT(FALSE); return NULL;
}
CFrameWnd* pFrame =
(CFrameWnd*)m_pFrameClass->CreateObject() ; if (pFrame == NULL) {
TRACE1("Warning: Dynamic create of frame %hs failed.\n",
m_pFrameClass->m_lpszClassName);
return NULL;
}
ASSERT_KINDOF(CFrameWnd, pFrame);
if (context.m_pNewViewClass == NULL)
TRACEO("Warning: creating frame with no default
view.\n");
// create new from resource
if (!pFrame->LoadFrame(m_nIDResource,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles
NULL, &context))
{
TRACEO("Warning: CDocTemplate couldn’t create
a frame.\n"); // frame will be deleted in PostNcDestroy cleanup return NULL;
}
// it worked ! return pFrame;
}
Мне бы хотелось обратить внимание читателя на то, что в качестве аргументов методу передаются указатель на документ и указатель (пока нулевой), в который будет записан указатель на созданный фрейм. Мы уже однажды (при рассмотрении метода DoPromptFileName()) замечали, что методу передаются указатели, которые с первого взгляда совершенно не нужны для работы метода. Кажется, здесь тот же случай – ну зачем, скажите, пожалуйста, при создании фрейма знать указатель на документ? То, что буквально в первых строках метода осуществляется проверка того, не равен ли идентификатор ресурсов нулю, говорит о том, что, вероятнее всего, при создании фрейма опять будут использоваться ресурсы. Но, как говорится, поживем – увидим.
Итак, подведем промежуточные итоги. К настоящему времени мы выяснили, что необходимо сделать для того, чтобы MFC начала работать с приложением в соответствии с требованиями архитектуры «документ/представление», выяснили, как определить, какой файл мы будем открывать, создали пустой документ. Однако обратите внимание, что до настоящего момента мы не можем никоим образом увидеть тот документ, который мы только что создали. Нам просто негде визуализировать наш документ! Для того чтобы увидеть результаты нашей работы, нам необходимо срочно создать окно представления. Следовательно, нам необходимо вернуться к коду метода CMultiDocTemplate::OpenDocumentFile() и посмотреть, что же происходит после того, как новый документ был создан, каким образом создается окно, в котором будет отображаться содержимое нашего документа.
Вернувшись, мы видим, что после создания нового документа программа пытается создать фрейм для нашего документа. Кажется, нас сейчас ждет очередная неприятность… Так и есть. Мы получили очередное сообщение об ошибке.
Посмотрев внимательнее, мы заметим, что попутно нам было выдано и отладочное сообщение о необходимости переопределить метод CDocTemplate::CreateNewFrame «Еггог: you must override CDocTemplate::CreateNewFrame.». Сейчас, наверное, уже нет необходимости разбираться, что произошло. Наверное, читатель уже догадался. Да, дело именно в том, что при определении шаблона документа мы указатель на информацию времени выполнения фрейма нашего документа оставили равным NULL! Что ж, придется внести еще небольшие изменения в нашу программу.
Вносим необходимые изменения в метод InitlnstanceQ нашего приложения:
BOOL CDocViewlApp::Initlnstance() {
fifdef _AFXDLL
Enable3dControls(); #else
Enable3dControlsStatic(); #endif
.»
LoadStdProfileSettings(); CDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate( IDR_DOCUMENT,
RUNTIME_CLASS(CDoc), NULL, NULL ); AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow );
pMainFrame->UpdateWindow(); return TRUE;
}
Снова компилируем программу, запускаем на выполнение. ..И б-лагополучно минуем то место, на котором в прошлый раз у нас было выдано сообщение об ошибке. Однако нам необходимо понять, что происходит при создании документа, т. е. что происходит при работе метода CDocTemplate::CreateNewDocument().
CObject* CRuntimeClass::CreateObject() {
if (m_pfnCreateObject == NULL) {
TRACE(_T("Error: Trying to create object which is not ") _T("DECLARE_DYNCREATE \nor DECLARE_SERIAL: %hs.\n"), m_lpszClassName); return NULL;
}
CObject* pObject = NULL;
TRY
{
pObject = (*m_pfnCreateObject) () ;
}
END_TRY
return pObject;(CWJnApp::OnFJIeOpen ) Во-вторых, при помощи метода S . CMultiDocTemplate::AddDocu-
ЦCDocManager::OnFileOpen J ment() (файл docmulti.cpp)
UcWinAppriOpenDocumentFile) только что созданный
N J документ добавля-
L-[ CDocManager::OpenDocumentFile ] ется в список
CMultiDocTemplate::OpenDocumentFjle] Документов
— —’ шаблона:
CDocTemplate::CreateNewDocum^n?)
^CMultiDocTemplate::AddDocument]
void CMultiDocTemplate::AddDocument(CDocument* pDoc) {
ASSERT_VALID(pDoc);
CDocTemplate::AddDocument(pDoc);
ASSERT(m_docList.Find(pDoc, NULL) == NULL);
// must not be in list
m_docList.AddTail(pDoc);
}
CWinAPP::OnFileOpen ) A этот мет0* Фактически представ-
————————– J ляет собой вызов одноименного
ч
CDocManager::OnFileOpen ] метода базового класса
CWinApp::OpenPocumentFilQ (фаЙЛ doctemPlcPP):
^-{CDocManager^OpenDocumentFile")
L^CMultiDocTemplate::OpenDocumentFile] CDocTemplate::CreateNewDocument] CMultiDocTemplate::AddDocument] L^CDocTemplate::AddDocument ]
void CDocTemplate::AddDocument(CDocument* pDoc) {
ASSERT_VALID(pDoc);
ASSERT(pDoc->m_pDocTemplate == NULL); // no template attached yet pDoc->m_pDocTemplate = this;
Исходя из текстов этих методов, можно заметить, что не только у шаблона документа есть список документов, открытых на основе данного шаблона, но и у каждого документа есть указатель на тот шаблон, при помощи которого он был открыт. Этакая своеобразная обратная связь!
Итак, что происходит при создании нового документа, мы выяснили. Фактически создается пустой документ, на него делается ссылка в шаблоне, а документ, в свою очередь, запоминает указатель на шаблон.
Итак, метод CDocTemplate::MatchDocType() используется для того, чтобы проверить, совпадает ли расширение открываемого файла с указанным в шаблоне документа. Зафиксировав этот факт, мы можем вернуться к методу CDocManager::OpenDocumentFile().
CDocument* CMultiDocTemplate::OpenDocumentFile(
LPCTSTR IpszPathName, BOOL bMakeVisible)
{
CDocument* pDocument = CreateNewDocument();
if (pDocument == NULL)
{
TRACEO("CDocTemplate::CreateNewDocument
returned NULL.\n"); AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC); return NULL;
}
ASSERT_VALID(pDocument);
BOOL bAutoDelete = pDocument->m_bAutoDelete; pDocument->m_bAutoDelete = FALSE;
// don’t destroy if something goes wrong CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL); pDocument->m_bAutoDelete = bAutoDelete; if (pFrame == NULL)
{
Af xMessageBox (AFX_IDP_FAILED_TO_CREATE_DOC) ;
delete pDocument; // explicit delete on error
return NULL;
}
ASSERT_VALID(pFrame);
if (IpszPathName == NULL) {
// create a new document – with default document name SetDefaultTitle(pDocument);
// avoid creating temporary compound file when // starting up invisible
if (IbMakeVisible)
pDocument->m_bEmbedded = TRUE;
if (!pDocument->OnNewDocument ())
{
// user has be alerted to what failed in OnNewDocument
TRACEO("CDocument::OnNewDocument returned FALSE An"); pFrame->DestroyWindow(); return NULL;
}
// it worked, now bump untitled count m_nUntitledCount++;
}
else {
// open an existing document CWaitCursor wait;
if (!pDocument->OnOpenDocument(IpszPathName)) {
// user has be alerted to what failed in OnOpenDocument TRACEO("CDocument::OnOpenDocument returned FALSE An"); pFrame->DestroyWindow (); return NULL;
}
pDocument->SetPathName(IpszPathName);
}
InitialUpdateFrame(pFrame, pDocument, bMakeVisible); return pDocument;
В качестве единственного аргумента используется имя открываемого файла.
Во-первых, мы получили отладочное сообщение о -том, что нам необходимо переопределить метод CreateNewDocu-ment(): «Еггог: you must override CDocTemplate::CreateNew-Document.» Во-вторых, мы получаем сообщение об ошибке.
Что же могло случиться? Заглянем в исходный код метода CDocTemplate::CreateNewDocument():
CDocument* CDocTemplate::CreateNewDocument() {
// default implementation constructs one from CRuntimeClass if (m_pDocClass == NULL) {
TRACEO("Error: you must override
CDocTemplate::CreateNewDocument.\n") ;
ASSERT(FALSE); return NULL;
}
CDocument* pDocument =
(CDocument*)m_pDocClass->CreateObject() ; if (pDocument == NULL) {
TRACE1("Warning: Dynamic create of document type
%hs failed.\n", m_pDocClass->m_lpszClassName) ; return NULL;
}
ASSERT_KINDOF(CDocument, pDocument); AddDocument(pDocument); return pDocument;
Конечно! Мы же в нашей программе оставили указатель на информацию времени исполнения объекта класса CDocument, равную NULL! А здесь первым делом осуществляется проверка, не равен ли нулю этот указатель! Что ж, делать нечего. Для того чтобы мы могли хоть немного продвинуться вперед, изменим нашу программу, добавив в начало ее описание класса нашего документа:
class CDoc : public CDocument {
DECLARE_DYNCREATE( CDoc ) public:
CDoc () ; virtual -CDoc(); >;
IMPLEMENT_DYNCREATE( CDoc, CDocument )
CDoc::CDoc()
{
}
CDoc::~CDoc()
{
}
Невооруженным глазом видно, что в начале функции ‘CDocTemplate::MatchDocType] производятся всевоз можные подготовительные действия, а также проверяется, не является ли выбранный нами файл ссылкой на другой файл. С точки зрения архитектуры «документ/представление» все это не очень интересно. Интересное начинается с момента начала перебора списка шаблонов. Обратите внимание, уважаемый читатель, на то, что для каждого шаблона в списке шаблонов вызывается метод CDocTemplate::MatchDocType(). Этот метод определяет, насколько расширение открываемого файла соответствует указанному в шаблонах. В качестве аргументов этому методу передаются имя файла, который мы хотим открыть, и переменная типа "указатель на документ", которая равна NULL. Ниже я привожу исходный текст этого метода. Его можно найти в файле docmgr.cpp:
CDocTemplate:Confidence
CDocTemplate::MatchDocType(LPCTSTR IpszPathName,
CDocument*& rpDocMatch)
{
ASSERT(IpszPathName != NULL); rpDocMatch = NULL;
// go through all documents
POSITION pos = GetFirstDocPosition();
while (pos != NULL)
{
CDocument* pDoc = GetNextDoc(pos);
if (AfxComparePath(pDoc->GetPathName(), IpszPathName)) {
// already open
rpDocMatch = pDoc; return yesAlreadyOpen;
}
}
// see if it matches our default suffix CString strFilterExt; if (GetDocString(strFilterExt,
CDocTemplate::filterExt) && !strFilterExt.IsEmpty())
{
// see if extension matches
ASSERT(strFilterExt[0] == 4.’);
LPCTSTR IpszDot = _tcsrchr(IpszPathName, ‘.’);
if (IpszDot != NULL &&
lstrcmpi(IpszDot, strFilterExt) == 0) return yesAttemptNative; // extension matches,
// looks like ours
}
// otherwise we will guess it may work return yesAttemptForeign;
}
Обратите внимание на следующие обстоятельства. Если файл с именем, совпадающим с первым аргументом метода, уже открыт в соответствии с текущим шаблоном, то указатель на открытый документ сохраняется в указателе, переданном в качестве второго документа. Другими словами, создается новая ССЫЛКА на открытый ранее документ, при этом вторая копия документа не создается. В этом случае метод возвращает значение yesAlreadyOpen.
Если файл ранее не был открыт в соответствии с текущим шаблоном, то выбирается подстрока, содержащая расширение файла (помните, читатель, описание метода CDocTemplate::GetDocString()?), из переданной шаблону строки ресурсов. Если эта подстрока совпадает с расширением открываемого файла, то метод возвращает значение yesAttemptNative. Указатель на документ, переданный в качестве второго аргумента, при этом не изменяется и остается равным NULL.
И наконец, если расширение открываемого файла не соответствует расширению, записанному в шаблоне, то метод возвращает значение yesAttemptForeign. При этом указатель, переданный в качестве второго аргумента, также остается равным NULL.
Любознательный читатель, вероятно, тут же задаст мне вопрос. Как же так, ведь в перечислении CDocTemplate: Confidence используются еще три значения, noAttempt, maybeAttemptForeign и may-beAttemptNative? А в каких случаях возвращаются эти значения? Здесь я могу только догадываться. Возможно, программисту потребуется иногда делать предположение о том, что открываемый файл соответствует текущему шаблону вне зависимости от расширения. Например, если шаблон файла предусматривает расширение «arj», а пользователь выбрал файл с расширением «aDD» (где «DD» означает две любые цифры), то, наверное, программист сможет, переопределив метод CDocTemplate::MatchDocType(), вернуть значение maybeAttemptNative. С другой стороны, если пользователь в этом случае выбрал файл с расширением, скажем, «txt», то программист может сделать предположение о том, что открываемый файл не соответствует шаблону, и вернуть значение тау-beAttemptForeign. Наверное, возможны еще какие-то случае, когда программисту потребуется использовать значение noAttempt.
Итак, мы прошли уже достаточно большой путь. Самое главное, поняли, что необходимо делать в начале работы в рамках архитектуры «документ/представление». Мы разобрались, какие ресурсы нам необходимо подготовить, создали шаблон документа, подготовили к отображению окно выбора файла. Теперь нам необходимо выяснить, что происходит в ходе открытия файла.
CDocument* CDocManager::OpenDocumentFile(LPCTSTR IpszFileName) {
// find the highest confidence
POSITION pos = m_templateList.GetHeadPosition (); CDocTemplate:Confidence bestMatch =
CDocTemplate::noAttempt;
CDocTemplate* pBestTemplate = NULL; CDocument* pOpenDocument = NULL;
TCHAR szPath[_MAX_PATH];
ASSERT(lstrlen(IpszFileName) < _countof(szPath)) ;
TCHAR szTemp[_MAX_PATH];
if (IpszFileName[0] == yV" )
++lpszFileName;
lstrcpyn(szTemp, IpszFileName, _MAX_^ATH);
LPTSTR IpszLast = _tcsrchr(szTemp, ) ;
if (IpszLast != NULL)
*lpszLast = 0; AfxFullPath(szPath, szTemp); TCHAR szLinkName[_MAX_PATH]; if (AfxResolveShortcut(AfxGetMainWnd(),
szPath,
szLinkName,
_MAX_PATH)) lstrcpy(szPath, szLinkName);
while (pos != NULL) {
CDocTemplate* pTemplate =
(CDocTemplate*)m_templateList.GetNext(pos) ; ASSERT_KINDOF(CDocTemplate, pTemplate);
CDocTemplate::Confidence match;
ASSERT(pOpenDocument == NULL);
match = pTemplate->MatchDocType(szPath,
pOpenDocument);
if (match > bestMatch) {
bestMatch = match; pBestTemplate = pTemplate;
}
if (match == CDocTemplate::yesAlreadyOpen)
break; // stop here
}
if (pOpenDocument != NULL) {
POSITION pos = pOpenDocument->GetFirstViewPosition() ; if (pos != NULL)
{
CView* pView = pOpenDocument->GetNextView(pos);
// get first one
ASSERT_VALID(pView);
CFrameWnd* pFrame = pView->GetParentFrame(); if (pFrame != NULL)
pFrame->ActivateFrame(); else
TRACEO("Error: Can not find a frame for document
to activate.\n");
CFrameWnd* pAppFrame;
if (pFrame != (pAppFrame =
(CFrameWnd*)AfxGetApp()->m_pMainWnd))
{
ASSERT_KINDOF(CFrameWnd, pAppFrame); pAppFrame->ActivateFrame();
}
}
else {
TRACEO("Error: Can not find a view for document
to activate An") ;
}
return pOpenDocument;
if (pBestTemplate == NULL) {
AfxMessageBox(AFX_IDP_FAILED_TO_OPEN_DOC) . return NULL;
return pBestTemplate->OpenDocumentFile(szPath);
В заключение необходимо привести пример использования строки ресурсов для формирования диалога открытия файла. В файл ресурсов я добавил строку
IDRJDOCUMENT "\nExe-file\nExe-file\n
Executable files (*.exe)\n.exe\n\n"
Мне бы хотелось, чтобы в диалоговом окне мне предлагалось осуществить выбор из ехе-файлов. Естественно, мне необходимо указать идентификатор ресурса в методе lnitlnstance() моего приложения. После внесенных изменений он стал выглядеть следующим образом:
BOOL CDocViewlApp::Initlnstance () {
#ifdef _AFXDLL
Enable3dControls(); #else
Enable3dControlsStatic (); #endif
LoadStdProfileSettings(); CDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate( IDRJDOCUMENT,
NULL, NULL, NULL );
AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE );
m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ) ; pMainFrame->UpdateWindow() ;
return TRUE;
}
Посмотрим, правильны ли мои рассуждения и сработает ли в данном случае моя программа. Итак, компилируем… Запускаем… Выбираем «File», «Ореп» и…
Окно, появившееся на отображении, вы видите на 13. © Очень надеюсь, что того материала, который мы только что изучили, достаточно для понимания того, что происходит при подготовке стандартного диалога. Надеюсь, что и в этом случае мы успешно решили все поставленные задачи – мы поняли «физику» процесса и осознали возможную степень влияния программиста на процесс создания диалогового окна. Конечно, я мог бы объяснять все мелочи, встречающиеся в исходном коде, например, отображение диалогового окна при помощи метода DoModal(), но имеет ли это смысл?
На основании сказанного делаем вывод:
Строка ресурсов, идентификатор которой указывается при создании шаблона документа, должна состоять из подстрок, отделенных друг от друга символом-разделителем (по умолчанию – ‘\п’). При этом пятая и четвертая подстроки соответственно хранят наименование фильтра и непосредственно фильтр, используемые при формировании стандартного диалогового окна открытия файла.
Значит, вот для чего используются шаблоны документов в процессе формирования диалога открытия файла! Только для того, чтобы выбрать ПОДСТРОКИ, описывающие тип файла нашего документа! Кажется, мы нашли еще один ответ на те вопросы, которые сами себе и поставили! Теперь становится ясным смысл пятого аргумента у 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.. Замечательно! Программа откомпилировалась без ошибок. Я поставил точку прерывания на конструктор шаблона документа и стал смотреть, что происходит во время создания шаблона.
