

Программирование на языке 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 };
читать отзывы (1)
На что необходимо обратить внимание? К этому моменту мы еще не представляем характера взаимодействия между документом и фреймом. Поэтому просто предположим, что каким-то образом наш документ будет отображаться в рамках фрейма. Давайте поразмыслим, уважаемый читатель. Мы пишем программу, которая будет работать с многодокументным интерфейсом. Наша программа должна отображать данные в одном из дочерних окон многодокументного интерфейса. Следовательно, логично будет в качестве фрейма использовать окно класса 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()) замечали, что методу передаются указатели, которые с первого взгляда совершенно не нужны для работы метода. Кажется, здесь тот же случай – ну зачем, скажите, пожалуйста, при создании фрейма знать указатель на документ? То, что буквально в первых строках метода осуществляется проверка того, не равен ли идентификатор ресурсов нулю, говорит о том, что, вероятнее всего, при создании фрейма опять будут использоваться ресурсы. Но, как говорится, поживем – увидим.
На основании сказанного делаем вывод:
Строка ресурсов, идентификатор которой указывается при создании шаблона документа, должна состоять из подстрок, отделенных друг от друга символом-разделителем (по умолчанию – ‘\п’). При этом пятая и четвертая подстроки соответственно хранят наименование фильтра и непосредственно фильтр, используемые при формировании стандартного диалогового окна открытия файла.
Значит, вот для чего используются шаблоны документов в процессе формирования диалога открытия файла! Только для того, чтобы выбрать ПОДСТРОКИ, описывающие тип файла нашего документа! Кажется, мы нашли еще один ответ на те вопросы, которые сами себе и поставили! Теперь становится ясным смысл пятого аргумента у 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(), то придем к выводу, что пятая и четвертая подстроки хранят соответственно фильтр и название фильтра, используемые при формировании стандартного диалогового окна открытия файла! Выскажем также предположение, что в перечислении DocStringlndex в некотором смысле определены назначения подстрок.
Что нам для этого нужно сделать? Практически ничего. 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 (забегая вперед, скажу, что именно эта функция обеспечивает подготовку и выдачу диалогового окна для выбора файла). Этап второй -непосредственно открытие файла и преобразование его в документ. Наверное, логично будет, если мы изложение материала так же разделим на две части. В первой части мы должны рассмотреть, каким образом формируется стандартное диалоговое окно для выбора открываемого файла. Кроме этого, мы должны определить, каким образом мы можем повлиять на процесс формирования этого окна. Во второй части нам необходимо понять, что происходит после того, как имя открываемого файла определено. Другими словами, нам нужно понять, как из файла рождается документ.
