Шаг 374 - SECComDoc – как пользоваться

Создание документа типа 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.


Загрузить проект | Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Каев Артем.