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

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

Наверное, из описания класса 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 };



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



03.02.2010

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

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

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

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

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



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

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

AFX_STATIC void AFXAPI _AfxAppendFilterSuffix (

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

{

ASSERT_VALID(pTemplate);

ASSERT_KINDOF(CDocTemplate, pTemplate);

CString strFilterExt, strFilterName;

if (pTemplate->GetDocString(strFilterExt,

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

{

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

// set the default extension

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

// skip the Л.’

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

// 1 based number

// add to filter

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

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

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

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

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

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

L-[CDocTemplate::GetDocString]

BOOL CDocTemplate::GetDocString(CStrirvg& rString,

enum DocStringlndex i) const

{

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

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

enum DocStringlndex {

windowTitle, // default window title

docName, // user visible name for default document

fileNewName, // user visible name for FileNew

// for file based documents:

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

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

// file type name

};

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

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

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

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

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

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

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

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

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

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

BOOL AFXAPI AfxExtractSubString(CString& rString,

LPCTSTR IpszFullString, int iSubString, TCHAR chSep)

{

if (IpszFullString == NULL) return FALSE;

while (iSubString—) {

IpszFullString = _tcschr(IpszFullString, chSep);

if (IpszFullString == NULL)

{

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

return FALSE;

}

lpszFullString++; // point past the separator

}

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

lstrlen(IpszFullString) : (int)(lpchEnd -

IpszFullString);

ASSERT(nLen >= 0);

memcpy(rString.GetBufferSetLength(nLen),

IpszFullString,

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

}

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



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