

Программирование на языке MFC
Мой второй блог в серии программирования
Архив раздела «Первая программа на 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…
Я начал реализовывать этот план и какое-то время работал в этом направлении. Однако достаточно быстро я одумался и задал себе один вопрос: а почему, собственно говоря, мне приходится делать всю черновую работу самому? Исходя из того, что я читал про MFC ранее, все должно быть намного проще. Если я иду в правильном направлении, то почему мне приходится делать все самому вручную? Где же хваленые возможности MFC? Все же, наверное, я чего-то не понимаю. И здесь мне пришла в голову мысль, результатом которой и явилось правильное решение. А не попробовать ли мне возложить обработку открытия файла на MFC? Ведь не зря же у класса CWinApp есть метод OnFileOpenQ, не так ли? Карту сообщений своего объекта приложения я немного изменил. Теперь обработчик команды ID__FILE__OPEN стал выглядеть следующим образом:
ON_COMMAND( ID_FILE_OPEN, CWinApp::OnFileOpen )
Тем самым обработку открытия файла я возложил на MFC. Мне осталось только понять, к чему это приведет…
Программа откомпилировалась без ошибок, что меня, честно говоря, несколько удивило. Однако, когда я запустил программу на выполнение в отладочном режиме, то, выбрав элемент «Ореп» меню «File», я получил сообщение, которое приведено на.
Характерной особенностью этой программы является то, что она практически ничего не делает. Я написал этот вариант только для того, чтобы посмотреть, как MFC будет реагировать на открытие файла и т.д. Но, с другой стороны, обратите внимание на то, что обработку открытия файла я, тем не менее, возложил на основное окно приложения. Этот факт привел к тому, что было потеряно достаточно много времени. Тем не менее, это вынудило меня хорошенько пораскинуть мозгами, и в конце концов эти размышления привели меня к правильному решению.
Я старался рассуждать логически. Я хочу отобразить содержимое файла. При этом хочу сделать это в соответствии с требованиями архитектуры «документ/представление». Но у меня нет ясности по многим вопросам. Когда, в какой момент файл на диске должен превратиться в документ? Наверное, в момент открытия файла. Другими словами, при обработке команды на открытие файла, полученной от меню, я должен открыть файл и превратить его в документ. Но какой файл надо открыть? Естественно, тот файл, который я укажу. Где укажу? Конечно же, в стандартном диалоговом окне для выбора открываемого файла! Значит, мне необходимо первым делом подготовить данные для открытия стандартного диалога. После того как я выберу файл, мне необходимо будет его открыть, разобрать по косточкам и каким-то образом содержимое файла превратить в документ… Н-да, работы непочатый край…
А теперь текст непосредственно программы:
#include "stdafx.h"
// Объявляем класс приложения, его поля и методы.
class CDocViewlApp : public CWinApp
{
public:
CDocViewlApp () ; :* protected:
afx_msg void OnFileOpen(); virtual BOOL Initlnstance (); DEC LARE_ME S S AG E_MA P()
};
CDocViewlApp::CDocViewlApp()
{
}
void CDocViewlApp::OnFileOpen()
{
}
class CMainFrame : public CMDIFrameWnd {
DECLARE_DYNAMIC( CMainFrame ); public:
CMainFrame () ; «’
};
IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWnd)
CMainFrame::CMainFrame()
{
}
BOOL CDocViewlApp::Initlnstance() {
#ifdef _AFXDLL
Enable3dControls() ; #else
Enable3dControlsStatic(); #endif
CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();
return TRUE;
}
BEGIN_MESSAGE_MAP( CDocViewlApp, CWinApp )
ON_COMMAND( ID_FILE_OPEN, OnFileOpen ) END_MESSAGE_MAP()
CDocViewlApp theApp;
С чего я начал? Написал небольшую программу, при помощи которой хотел всего-навсего выяснить характер взаимодействия объектов разных классов при работе в соответствии с идеологией архитектурой «документ/представление». Эта программа не должна делать ничего, кроме отображения пустого документа в (естественно!) пустом окне отображения. Впоследствии я планировал использовать текст этой программы как заготовку для других программ.
Ниже приведен текст файла ресурсов моей программы. Комментарии, созданные средой Visual С++, я предварительно удалил.
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "afxres.h"
#undef APSTUDIO_READONLY_SYMBOLS
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS)
#ifdef _WIN32
LANGUAGE LAN G__RU SSI AN, SUBLANG_DE FAULT #pragma code_page(1251) #endif //_WIN32
#ifdef APSTUDIO_INVOKED
1 TEXTINCLUDE DISCARDABLE BEGIN
"resource. h\0"
END
2 TEXTINCLUDE DISCARDABLE . BEGIN
"#include xw‘afxres.h""\r\n" "\0"
END
3 TEXTINCLUDE DISCARDABLE BEGIN
"\r\n" "\0"
END
#endif // APSTUDIO_INVOKED
IDR_RESOURCE ICON DISCARDABLE "ResWCommon.ico"
IDR_RESOURCE MENU DISCARDABLE BEGIN
ID_FILE_OPEN ID APP EXIT
POPUP "&File"
BEGIN
MENUIТЕМ "&Open\tCtrl+0", MENUIТЕМ SEPARATOR MENUIТЕМ "E&xit\tCtrl+x",
END
POPUP "SWindow", GRAYED BEGIN
MENUITEM "Tile &horizontally\tCtrl+H", ID_WINDOW_TILEHORZ, GRAYED
MENUITEM "Tile &vertically\tCtrl+V", ID_WINDOW_TILE_VERT, GRAYED
MENUIТЕМ "&Cascade\tCtrl+C", ID_WINDOW_CASCADE
END
POPUP "Help" BEGIN
MENUIТЕМ "&About", ID_APP_ABOUT
END
END
STRINGTABLE DISCARDABLE BEGIN
ID_FILE_OPEN "Open an existing document"
END
"Display program information, version number and copyright" "Quit the application"
STRINGTABLE DISCARDABLE BEGIN
ID_APP_ABOUT
ID_APP_EXIT
END
STRINGTABLE DISCARDABLE BEGIN
IDR_RESOURCE "PE-file viewer and disassembler"
END
Тем не менее, мы может прекратить работу архива в любое время при помощи метода Abort(), исходный текст которого находится в файле агссоге.срр:
void CArchive::Abort() {
ASSERT(m_bDirectBuffer ||
m_lpBufStart == NULL || AfxIsValidAddress(m_lpBufStart,
m_lpBufMax – m_lpBufStart, IsStoring()));
ASSERT(m_bDirectBuffer ||
m_lpBufCur == NULL || AfxIsValidAddress(m_lpBufCur,
m_lpBufMax – m_lpBufCur, IsStoring()));
// disconnect from the file mjpFile = NULL;
if (!m_bUserBuf) {
ASSERT(!m_bDirectBuffer); delete[] m_lpBufStart; m_lpBufStart = NULL; m_lpBufCur = NULL;
}
delete m_pSchemaMap; m_pSchemaMap = NULL;
// m_pStoreMap and m_pLoadArray are unioned, // so we only need to delete one
ASSERT((CObject*)m_pStoreMap == (CObject*)m_pLoadArray); delete (CObject*)m_pLoadArray; m_pLoadArray = NULL;Посмотрим, что происходит с архивом в случае прекращения работы посредством вызова метода AbortQ. Первым делом метод «отсоединяет» архив от файла, присваивая полю m__pFile значение NULL. Затем в том случае, если у архива есть ассоциированный с ним буфер, производится удаление буфера. Если в буфере остались данные, которые не были записаны в файл, то эти данные будут потеряны. Указатели на начало буфера и на текущую позицию буфера делаются равными NULL. Затем удаляется указатель на хэш-таблицу, содержащую номера схем классов, а за ней и хэш-таблица (или массив) сохраненных объектов.
Для нас главным в этом методе является то, что происходит вызов метода PreCreateWindow() для нашего объекта окна представления:

BOOL CEditView::PreCreateWindow(CREATESTRUCT& cs) {
m_dwDefaultStyle = dwStyleDefault; return CCtrlView::PreCreateWindow(cs);
}
Вызов же метода PreCreateWindow() родительского объекта все ставит на свои места:
BOOL CCtrlView::PreCreateWindow(CREATESTRUCT& cs) {
ASSERT(cs.IpszClass == NULL); cs.IpszClass = m_strClass;
// initialize common controls
VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG)) ; AfxDeferRegisterClass(AFX_WNDCOMMCTLSNEW_REG);
// map default CView style to default style // WS_BORDER is insignificant
if ((cs.style I WS_BORDER) == AFX_WS_DEFAULT_VIEW)
cs.style = m_dwDefaultStyle & (cs.style | ~WS_BORDER);
return CView::PreCreateWindow(cs);
}
Нетрудно заметить, что метод Create(), в свою очередь, вызывает метод CreateEx():
BOOL CWnd::CreateEx(DWORD dwExStyle,
LPCTSTR IpszClassName,
LPCTSTR IpszWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU nIDorHMenu,
LPVOID lpParam)
{
/I allow modification of several common create parameters CREATESTRUCT cs; cs.dwExStyle = dwExStyle; cs.IpszClass = IpszClassName; cs.lpszName = IpszWindowName; cs.style = dwStyle; cs.x = x; cs.y = y; cs.cx = nWidth; cs.cy = nHeight; cs.hwndParent = hWndParent; cs.hMenu = nIDorHMenu;
cs.hlnstance = AfxGetlnstanceHandle(); cs.lpCreateParams = lpParam;
if (!PreCreateWindow(cs)) {
PostNcDestroy(); return FALSE;
}
AfxHookWindowCreate(this);
HWND hWnd = ::CreateWindowEx(cs.dwExStyle,
cs.IpszClass, cs.IpszName, cs.style, cs .x, cs.y, cs.cx, cs.cy,
cs.hwndParent,
cs.hMenu,
‘* cs.hlnstance,
cs.lpCreateParams);
#ifdef _DEBUG
if (hWnd == NULL) {
TRACE1("Warning: Window creation failed:
GetLastError returns 0x%8.8X\n",
GetLastError ());
#endif
if (!AfxUnhookWindowCreate()) PostNcDestroy();
// cleanup if CreateWindowEx fails too soon
if (hWnd == NULL)
return FALSE;
ASSERT (hWnd == m_hWnd) ; fl
// should have been set in send msg hook return TRUE;
}
А теперь давайте займемся подсчетами. Вся моя программа со всеми описаниями классов, комментариями и пропусками состоит из 128 строк. Теперь вспомним, что нам пришлось бы сделать в том случае, если бы мы писали программу без использования архитектуры "документ/представление", используя только функции API. Во-первых, нам понадобилось бы так называемое «стандартное заклинание», в котором необходимо заполнить структуру типа WND-CLASS, зарегистрировать класс окна и запустить цикл обработки сообщений. Во-вторых, понадобилась бы обработка, как минимум, сообщения WM_CREAfE, во время которой необходимо создать окно класса «MDICLIENT». В-третьих, регистрация класса окна представления и написание оконной процедуры окна представления. .. Можно только представить себе, сколько строк содержалось бы в исходном коде такого приложения!
Я достаточно долго программировал с использованием только API, поэтому не буду утверждать, что MFC хуже или лучше «чистого» API. Тем не менее, возможности, которые предоставляет архитектура «документ/представление», не могут не впечатлять.
Какое сообщение получает окно в том случае, когда окно должно перерисовать себя? Естественно, сообщение WM__PAINT. Какой метод должен отвечать за обработку сообщения WM_PAINT у объекта класса CView? Конечно же, CView::OnPaint(). Следовательно, нам неплохо было бы заглянуть в исходный текст этого метода, который, кстати, находится в файле viewcore.cpp:
void CView::OnPaint() {
// standard paint routine
CPaintDC dc(this);
OnPrepareDC(&dc);
OnDraw(&dc);
}_________
[CView::OnPaint ) Видно, что вызов конструктора класса
Ucview.OnPrepareDC) СРа‘ПЮС опРеделяет‘ чт0 «”вод будет
^ – > осуществляться на окно класса CView.
Что ж, логично, иного трудно было ожидать. Теперь заглянем в код
метода CView::OnPrepareDC():
void CView::OnPrepareDC(CDC* pDC, CPrintlnfo* plnfo) {
ASSERT_VALID(pDC);
UNUSED(pDC); // unused in release builds
// Default to one page printing if doc length not known if (plnfo != NULL)
plnfo->m_bContinuePrinting =
(pInfo->GetMaxPage() != Oxffff || (plnfo->m_nCurPage == 1) ) ;
}
void CView::OnDraw(CDC*)
{
}
только подтверждает нашу догадку. Наверное, для того чтобы убедиться в правильности нашей догадки, лучше всего будет привести написанную программу, осуществляющую вывод информации в окно представления. Сделаем так, чтобы программа выводила в окно представления имя файла, на основе которого создан документ. Итак, описание класса CDocView нашей программы принимает следующий вид:

class CDocView : public CView {
DECLARE_DYNCREATE ( CDocView ) public:
CDocView();
virtual ~CDocView(); void OnDraw( CDC* pDC );
};
Ниже я привожу текст метода OnDraw():
void CDocView::OnDraw( CDC* pDC ) {
pDC->TextOut( 10, 10, GetDocument()->GetPathName () );
}
Программа откомпилировалась без ошибок!
Обратите внимание, читатель, что указатель на структуру типа CCreateContext (в нее чуть раньше мы записали наши указатели на документ,шаблон документа, а также указатели на фрейм и на окно представления документа) записывается сначала в поле lpCreateParams структуры cs типа CREATESTRUCT, а потом в поле IParam структуры mcs типа MDICREATESTRUCT Указатель на структуру mcs посылается окну типа MDICLIENT посредством сообщения WM_MDICREATE. Что же происходит дальше? После получения этого сообщения создается дочернее окно MDI, которое будет играть роль окна фрейма. Естественно, при своем создании оно получает сообщение WM_CREATE, на что реагирует вызовом метода CMDIChildWnd::OnCreate():
int CMDIChildWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) {
// call base class with IParam context (not MDI one) MDICREATESTRUCT* lpmcs;
lpmcs = (MDICREATESTRUCT*)lpCreateStruct->lpCreateParams; CCreateContext* pContext = (CCreateContext*)lpmcs->lParam;
return OnCreateHelper(lpCreateStruct, pContext);
Из переданной окну структуры типа CREATESTRUCT выбирается указатель на CCreateContext, после чего указатели на структуры типов CREATESTRUCT и CCreateStruct передаются методу CFrameWnd::OnCreateHelper:
int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs.
CCreateContext* pContext)
if (CWnd::OnCreate(lpcs) == -1) return -1;
// create special children first
if (!OnCreateClie*nt (lpcs, pContext) ) {
TRACEO("Failed to create client pane/view for frame.\n") ; return -1;
}
// post message for initial message string
PostMessage(WM_SETMESSAGESTRING, AFX__IDS_IDLEMESSAGE);
// make sure the child windows have been properly sized RecalcLayout();
return 0; // create ok
}
BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT,
CCreateContext* pContext)
{
// default create client will create a view if asked for it if (pContext != NULL && pContext->m_pNewViewClass != NULL) {
if (CreateView (pContext, AFX_IDW_PANE_FIRST) == NULL) return FALSE;
}
return TRUE;
}
CWnd* CFrameWnd::CreateView(CCreateContext* pContext,
UINT nID)
{
ASSERT(m_hWnd != NULL); ASSERT(::IsWindow(m_hWnd)); ASSERT(pContext != NULL);
ASSERT(pContext->m_pNewViewClass != NULL);
// Note: can be a CWnd with PostNcDestroy self cleanup CWnd* pView =
(CWnd*)pContext->m_pNewViewClass->CreateObject() ; if (pView == NULL) {
TRACE1("Warning: Dynamic create of view type
%hs failed.\n",
pContext->m_pNewViewClass->m_lpszClassName) ; return NULL;
ASSERT_KINDOF(CWnd, pView);
// views are always created with a border! if (!pView->Create(NULL, NULL,
AFX_WS_DEFAULT_VIEW, CRect(0,0,0, 0), this, nID,
pContext))
{
TRACEO("Warning: could not create view for frame.\n");
return NULL; // can’t continue without a view
}
if (afxData.bWin4 && (pView->GetExStyle () &
WS_EX_CLIENTEDGE))
{
// remove the 3d style from the frame, since the view is // providing it.
// make sure to recalc the non-client area
ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED);
}
return pView;
}
Смотрите внимательно, читатель! Сначала создается объект (представление) того класса, который мы указали при создании шаблона документа. После этого создается непосредственно окно представления. При этом видно, родительским окном объявляется окно фрейма! Так что же получается? MFC решило за программиста одну из достаточно часто встречавшихся задач -расположение дочернего окна поверх клиентской области родительского окна. Теперь программисту нет необходимости самому создавать дочернее окно и отслеживать изменения размеров родительского окна – эти задачи решает за него MFC. Единственное, что в этом случае требуется от программиста, так это написать приложение в соответствии с требованиями архитектуры «документ/представление»!
Осталось понять одну «мелочь» – как осуществляется отображение данных документа в окне представления.
Я опять старался рассуждать логически.
Во-первых, для того чтобы рассмотреть вопрос отображения документа, нам необходимо вспомнить, что у нас до сих пор указатель на информацию времени исполнения окна представления нашей программы равен NULL. В связи с этим необходимо описать класс окна представления..Следрвательно, наша программа немного изменилась. Сразу после описания класса документа я добавил описание класса окна представления:
class CDocView : public CView {
DECLARE_DYNCREATE ( CDocView ) public:
CDocView();
virtual -CDocView();
>;
IMPLEMENT_DYNCREATE( CDocView, CView )
CDocView::CDocView()
{
}
CDocView::-CDocView()
{
}
Кроме этого, я изменил метод lnitlnstance() класса CDocViewl:
BOOL CDocViewlApp::Initlnstance() {
#ifdef _AFXDLL
Enable3dControls(); #else
Enable3dControlsStatic(); #endif
LoadStdProfileSettings(); CDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate( IDR_DOCUMENT,
RUNTIME__CLASS ( CDoc ) , RUNTIME_CLASS( CMDIChildWnd ), RUNTIME_CLASS( CDocView ) ) ; AddDocTemplate( pDocTemplate ); CMainFrame* pMainFrame = new CMainFrame; pMainFrame->LoadFrame( IDR_RESOURCE ); m_pMainWnd = pMainFrame; pMainFrame->ShowWindow(m_nCmdShow ); pMainFrame->UpdateWindow();
return TRUE;
}
Когда я постарался откомпилировать эту программу, я получил сообщение о том, что у класса, наследуемого от CView, не определен метод OnDraw(). Ну, конечно же! Откуда же наш произвольный класс окна представления будет знать, каким образом ему необходимо перерисовываться? Давайте опять постараемся рассуждать логически. Какое сообщение получает окно, когда ему нужно перерисовать себя? Правильно, WM_PAINT. Но для обработки сообщения WM_PAINT объекты MFC используют метод OnPaintQ.
Не является исключением и объекты класса CView и унаследованных от него. Исходный код этого метода находится в файле viewcore.cpp:
void CView::OnPaint() {
// standard paint routine CPaintDC dc(this); OnPrepareDC(&dc); OnDraw(&dc);
}
Очевидно, что перерисовка осуществляется методом OnDraw(). Взглянем на исходный код этого метода, который также находится в файле viewcore.cpp:
void CView::OnDraw(CDC*)
{
}
To, что этот метод не делает ничего, подтверждает нашу догадку о том, что нам нужно переписать именно этот метод. Что ж описание нашего класса CDocView придется немного изменить. Теперь оно будет выглядеть так:
class CDocView : public CView {
DEСLARE_DYNCREATE ( CDocView ) public:
CDocView();
virtual ~CDocView(); void OnDraw( CDC* pDC );
};
Естественно, нам придется переопределить и добавить в программу метод OnDravtr(). Пока этот метод нас не очень интересует, поэтому оставим его пустым:
void CDocView::OnDraw( CDC* pDC )
{
}
Наверное, отсюда можно сделать вывод, что класс CView предоставляет только базовые возможности для отображения данных. Надеюсь, у нас еще будет шанс разобраться с этим.
Любознательный читатель уже, наверное, приготовил мне вопрос: да, конечно, все что рассказывается, это верно, но каким же образом создается окно представления и как оно относится к окну фрейма и документу?
Я надеюсь, что читатель помнит о том, что пока при создании шаблона нашего документа указатель на информацию времени исполнения класса окна представления равен нулю. Следовательно, пока мы можем на некоторое время оставить вопрос о создании окна представления и вернуться в метод CMDIChildWnd::LoadFrame().
Из текста метода CMDIChildWnd::LoadFrame() кроме сказанного выше становится понятным и назначение начальной подстроки строкового ресурса – эта подстрока определяет заголовок создаваемого окна фрейма.
Теперь, когда мы знаем все о том, как происходит создание нового документа и создание нового фрейма, нам придется вернуться к методу ь CMultiDocTemplate::OpenDocumentFile() и продолжить его рассмотрение на имя открываемого файла. Текст этого метода, приведенный ниже, можно найти в файле doccore.cpp:
BOOL CDocument::OnOpenDocument(LPCTSTR IpszPathName) {
if (IsModifiedO )
TRACEO("Warning: OnOpenDocument replaces an unsaved
document.\n") ;
CFileException fe;
CFile* pFile = GetFile(IpszPathName,
CFile::modeRead|CFile::shareDenyWrite,
&fe) ; if (pFile == NULL) {
ReportSaveLoadException(IpszPathName,
return FALSE;
DeleteContents ();
&fe, FALSE,
AFX_IDP FAILED TO_OPEN_DOC);
SetModifiedFlag(); // dirty during de-serialize
CArchive loadArchive(pFile,
CArchive::load | CArchive:rbNoFlushOnDelete); loadArchive.m_pDocument = this; loadArchive.m_bForceFlat = FALSE; TRY {
CWaitCursor wait;
if (pFile->GetLength() != 0)
Serialize(loadArchive); // load me
loadArchive.Close(); ReleaseFile(pFile, FALSE);
}
CATCH_ALL(e) {
ReleaseFile(pFile, TRUE);
DeleteContents(); // remove failed contents
TRY {
ReportSaveLoadException(IpszPathName, e, FALSE,
AFX_IDP_FAILED_TO_OPEN_DOC);
}
END_TRY
DELETE_EXCEPTION(e); return FALSE;
}
END_CATCH_ALL
SetModifiedFlag(FALSE); // start off with unmodified
return TRUE;
}
Я прошу читателя уделить этому методу ОСОБОЕ внимание. Немного опережая события, скажу, что именно здесь мы увидим, в какой момент из файла получается документ, какой метод необходимо переопределить, чтобы всю рутинную работу возложить на MFC.
Вполне очевидно, что этот метод используется для того, чтобы определить класс и стили окна, которое будет создаваться.
Сначала определяется класс окна. В том случае, если класс окна до сих пор не определен (и ТОЛЬКО в этом случае), то полю IpszClass присваивается значение _afxWndFrameOrView. Что находится в этой переменной, станет ясным после того, как мы взглянем на участок кода, находящийся в файле afximpl.h:
// special AFX window class name mangling
#ifndef _UNICODE #define _UNICODE_SUFFIX #else
#define _UNICODE_SUFFIX _TPu") #endif
#ifndef _DEBUG #define DEBUG SUFFIX
#else
#define _DEBUG_SUFFIX _T(M") #endif
#ifdef _AFXDLL #define _STATIC_SUFFIX #else
#define _STATIC_SUFFIX _T(^s") #endif
#define AFX_WNDCLASS(s) \ _TPAfx") _T(s) _T("42") _DEBUG_SUFFIX
#define AFX_WND #define AFX_WNDCONTROLBAR #define AFX_WNDMDIFRAME #define AFX_WNDFRAMEORVIEW #define AFX WNDOLECONTROL
STATIC SUFFIX UNICODE SUFFIX
AFX_WNDCLASS("Wnd") AFX_WNDCLASSrControlBar") AFX_WNDCLASS("MDIFrame") AFX_WNDCLASS("FrameOrView") AFX_WNDCLASSrOleControl")
С другой стороны, в файле wincore.cpp мы увидим:
const TCHAR _afxWnd[] = AFX_WND;
const TCHAR _afxWndControlBar[] = AFX_WNDCONTROLBAR; const TCHAR _afxWndMDIFrame[] = AFX_WNDMDIFRAME; const TCHAR _afxWndFrameOrView[] = AFX_WN D FRAME ORVIEW; const TCHAR _afxWnd01eControl[] = AFX_WNDOLECONTROL;
Исходя из этих фактов, а также зная, что в настоящий момент мы генерируем отладочную версию нашей программы, сделаем вывод о том, что классом создаваемого окна фрейма будет являться «AfxFrameOrView42d». В качестве упражнения я предлагаю читателю проверить правильность этого утверждения.
Определив класс и стили создаваемого окна фрейма, мы получили все необходимое для создания окна. Следовательно, никаких проблем создание окна фрейма вызвать не должно.
Для только что созданного объекта осуществляется вызов метода CMDIChildWnd::LoadFrame(). При этом в качестве аргументов методу передаются пресловутый идентификатор ресурсов, набор стилей окна (не объекта, а окна!), которое будет создано, указатель на родительское окно и, наконец, указатель на структуру типа Ccreate-Context, которую мы рассмотрели чуть раньше. Здесь стоит заметить, что по умолчанию стилями окна являются WSJDVERLAPPED-WINDOW и FWS_ADDTOTITLE. Исходный текст этого метода находится в файле winmdi.cpp:

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