Создание документа типа X, на основе данных, полученных при работе юзера с документом типа Y, в MFC Multiple Documents приложении и дальнейший обмен между этими документами данными, внятно не описывается ни в одной книге по MFC программированию. В жизни же, обычно, требуется именно это.
К примеру создать документ-отчет или документ-график, на основе данных другого документа, который является электронной таблицей, базой данных - да бог ведает чем.
В принципе подобную задачу можно решить на основе так называемого MVSD (Multiple Views Single Document) приложения, когда к одному документу относятся несколько видов (разного естественно типа). См. View Management.
Допустим мы в виде X наколотили какие нибудь данные, в виде Y рисуем на основе этих данных график, а в виде Z - отчет. И вид X и вид Y и вид Z берут данные из одного и того же документа. Вроде все довольны. Но. MVSD - техника очень хорошая, но к сожалению, только до того времени пока не приходится сохранять содержимое какого нибудь производного вида. Для сохранения содержимого производного вида обычно в классе этого вида приходится делать CYView::OnFileSaveAsY(). В случае же когда нам (не дай бог) нужно считывать данные этого вида из файла и на их основе инициализировать данные нашего единственного документа – такое может довести кого угодно до белого каления. Особенно когда вид должен сохранятся в графическом каком нибудь формате (WMF к примеру – ну не сковородец ли?).
Выход в таком случае один – использовать несколько типов документов, и пускай каждый сам себя сохраняет и грузит. Но прикол MFC состоит в том, что по-умолчанию, в многодокументном приложении, документ типа X ничерта не знает о документе типа Y. И приходится бедному программеру лезть в дебри создания документа, рамки и вида. Не волнуйтесь и не надейтесь – я не стану тут этого описывать – тем более что есть книга Фрэнка Крокета «MFC Мастерская разработчика». Она дает ответы на многие вопросы, но не на наш текущий – как шустро и понятно создавать один документ на основе другого и в дальнейшем обмениваться между ними данными?
Один из ответов – использование библиотеки Objective Chart от фирмы Stingray. Вернее очень маленькой, но очень важной ее части, которая прекрасно живет без самого Objective Chart. Это 4 небольших файла: ComDoc.h и ComDoc.cpp, DocMngr.h и DocMngr.cpp. В нашем нижеследующем примере эти файлы немножко переименованы и немножко (непринципиально) подредактированны. Библиотека хорошая, отдается в исходниках, но за деньги L. Конечно, то что я привожу здесь кусок ее кода – не есть хорошо. Вообще - «Тот, у кого в компе весь софт лицензионный – пусть первый бросит в меня процессор!»
1. Делаем MFC Multiple Documents приложение. С названием к примеру Foo. В нем у нас получился вид CFooView (допустим CFormView–based) и CFooDoc документ.
2. Добавляем к этому приложению еще один тип (шаблон) документа. Можно создать все это по-кускам – отдельно создаем документ, и вид, и строку-шаблон, и меню, и иконки – но это для мазохистов. Гораздо проще сделать еще один MFC Multiple Documents проект, назовем его Bar. И выдерем из него все вышеуказанное. В результате должно получится приложение, которое при запуске спрашивает вопрос : какой документ хотите видеть? И при нажатии кнопки ID_FILE_NEW – та же история.
3. Кидаем в папку проекта файлы SECComDoc.h SECComDoc.cpp SECDocManager.h SECDocManager.cpp и добавляем эти файлы в проект.
4. Вставляем в StdAfx.h ближе книзу
#include "SECComDoc.h" #include "SECDocManager.h"
5. В CFooApp определяем следующие функции:
//файл Foo.h virtual CDocument* OnFileNew(LPCSTR DocIdent, UINT nOpCode, UINT nSubCode,DWORD dwData, SECComDoc *pParent); virtual void OnFileNew(); virtual void AddDocTemplate(CDocTemplate* pTemplate); //файл Foo.cpp void CFooApp::AddDocTemplate(CDocTemplate* pTemplate) { if (m_pDocManager == NULL) m_pDocManager = new SECDocManager; m_pDocManager->AddDocTemplate(pTemplate); } void CFooApp::OnFileNew() { if (m_pDocManager != NULL) ((SECDocManager *)m_pDocManager)->OnFileNew("Foo",0,0,0l,NULL); // May be only One! } CDocument *CFooApp::OnFileNew(LPCSTR DocIdent,UINT nOpCode, UINT nSubCode, DWORD dwData, SECComDoc *pParent) { if (m_pDocManager != NULL) return ((SECDocManager*)m_pDocManager)->OnFileNew(DocIdent,nOpCode, nSubCode, dwData, pParent); return NULL; } //В том же файле (вверху) после BEGIN_MESSAGE_MAP(CFooApp, CWinApp) // Вместо ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) ON_COMMAND(ID_FILE_NEW, OnFileNew) //В том же файле в CFooApp::InitInstance() // Это – есть в проекте по-умолчанию pDocTemplate = new CMultiDocTemplate( IDR_FOOTYPE, RUNTIME_CLASS(CFooDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CFooView)); AddDocTemplate(pDocTemplate); //////////////////////////////////////////////////////// // Шаблон второго документа – выдранный (вместе с нужными файлами и ресурсами) из проекта Bar pDocTemplate = new CMultiDocTemplate( IDR_BARTYPE, RUNTIME_CLASS(CBarDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CBarView)); AddDocTemplate(pDocTemplate); // Дабы по-умолчанию создавался только один тип документа (это опционально) if(cmdInfo.m_nShellCommand == CCommandLineInfo::FileNew) { if(OnFileNew("Foo", 0, 0, 0l, NULL) == NULL) return FALSE; } else { if (!ProcessShellCommand(cmdInfo)) return FALSE; }
6. В нашем приложении есть два типа документа CFooDoc и CBarDoc - мы должны в файлах где эти классы декларированы и дефинированы заменить везде (можно нажать Ctrl+H и далее Replace All) CDocument на SECComDoc.
7. Перегрузить в обоих наших документах следующие защищенные виртуальные функции
//Декларации: protected: virtual BOOL SecDocUpdate(UINT nSubCode, DWORD dwData); virtual BOOL ProcessUserCommand(UINT nOpCode, UINT nSubCode, UINT dwData); virtual BOOL ClipboardText(void); virtual BOOL GmemInit(UINT code, HANDLE hMem); //А в дефинициях можно просто пока везде вернуть TRUE
8. Все! Ничего полезного прога еще не делает – но поставить компилироватся уже можно.
9. Теперь можно и занятся собственно задачей – создаем из документа CFooDoc документ CBarDoc
// Создаем документ другого типа и инициализируем его данными из текущего void CFooDoc::OnWorkCreateBarDocument() // Это обработчик пункта меню или кнопки – как угодно { CDocument *OnFileNew(LPCTSTR DocIdent,UINT nOpCode, UINT nSubCode, DWORD dwData,SECComDoc pParent); CBarDoc *pDoc = (CBarDoc*) ((CFooApp*)AfxGetApp())->OnFileNew("Bar", SEC_GMEM_INIT, 0, (DWORD)&m_Data, this); /* LPCTSTR DocIdent в нашем случае == "Bar" это второй параметр==(CDocTemplate::fileNewName) строки- шаблона для документа CBarDoc UINT nOpCode может принимать нижеследюущие значения: SEC_NOOP - юзается когда надо просто создать документ такого типа и ничем не инициализировать его данные SEC_CLIPBOARD_TEXT - означает что в создающемся CBarDoc вызовется функция ClipboardText() SEC_GMEM_INIT - означает что в создающемся CBarDoc вызовется функция GmemInit(nSubCode, (HANDLE)dwData) SEC_DOC_UPDATE - означает что в создающемся CBarDoc вызовется функция SecDocUpdate(nSubCode, dwData); в принципе - разницы между GmemInit(nSubCode, (HANDLE)dwData) и SecDocUpdate(nSubCode, dwData); никакой SEC_USER_COMMAND - означает что в создающемся CBarDoc вызовется функция ProcessUserCommand(nOpCode, nSubCode, dwData) UINT nSubCode - туда можно кидать что душе угодно DWORD dwData - туда обычно кидают указатель на какую-нибудь структуру с (инициализирующими или просто) данными SECComDoc *pParent - в нашем случае = this, благодаря чему создавшийся CBarDoc будет знать своего создателя и сможет до него добраться путем ->m_pParent */ }
Чтобы принять инициализирующие данные через SEC_GMEM_INIT в CBarDoc должна быть переопределена была
BOOL CBarDoc::GmemInit(UINT code, HANDLE hMem) // примерно так { if(hMem) { CFooData *p = reinterpret_cast(hMem); m_Data = *p; // предполагается что у CFooData есть оператор= } return TRUE; }
10. А теперт финт ушами в другом направлении – из созданного выше CBarDoc шлем данные в связанный с ним (создавший его) CFooDoc
void CBarDoc::OnWorkUpdateFooDocument() // Это обработчик пункта меню или кнопки – как угодно { m_Data.m_Age = 40; m_Data.m_Name = "Shtirlitz"; UpdateParentDocument(SEC_DOC_UPDATE, (DWORD)&m_Data); }
Чтобы в свою очередь CFooDoc мог принять данные послатые в него посредством UpdateParentDocument(SEC_DOC_UPDATE ......) в нем надо переопределить:
BOOL CFooDoc::SecDocUpdate(UINT nSubCode, DWORD dwData) { if(dwData) { CFooData *p = reinterpret_cast(dwData); m_Data = *p; // предполагается что у CFooData есть оператор= UpdateAllViews(NULL, DATA_TO_FORM); } return TRUE; }
Уф! Все! Смотрите исходники.
Шаг прислал Bulat.