Глава 21. Введение в OLE

В настоящей главе вы познакомитесь с основными концепциями технологии OLE(ObjectLinkingandEmbedding— связывание и внедрение объектов), которую можно определить как объектно-ориентированный протокол совместного доступа к данным и программному коду из разных процессов и даже из разных компьютеров в пределах локальной сети. OLE позволяет программистам создавать приложения для работы с составными документами, представляющими собой динамические связанные структуры, отдельные части которых могут разрабатываться в различных программах. Составной документ обычно включает в себя главный документ и ряд внедренных или связанных объектов.

Разработку OLE-контейнеров и серверов проще всего вести в среде VisualC++ при помощи специальных мастеров, а также библиотеки MFC. Делать такого рода работу вручную бессмысленно, так как придется писать тысячи строк кода, большая часть которого будет повторяться от программы к программе.

В этой главе мы поговорим о создании OLE-приложений с помощью мастера AppWizard, с которым вы уже познакомились в предыдущей главе. Благодаря этому мастеру программист избавляется от необходимости вводить однотипные программные блоки — они будут добавляться автоматически. Мастер AppWizard позволяет также не заботиться о деталях реализации многочисленных концепций технологии OLE. Вы убедитесь, что применение возможностей OLE-становится достаточно простой задачей.

Основные концепции

Объекты

Процедурные приложения для Windows базируются главным образом на использовании стандартных API-функций. Из-за этого иногда бывает трудно определить язык реализации программы (С или C++), поскольку вся она может состоять только из вызовов стандартных функций!

В главах 18 и 19 мы постепенно перешли от процедурных подходов в програм­мировании к объектно-ориентированной методике. Этот переход стал возможным благодаря использованию библиотеки MFC. Технология OLE открывает новые возможности для объектно-ориентированного программирования.

В основе OLE лежит модель компонентных объектов — COM (ComponentObjectModel), представляющая собой двоичный стандарт, который предназначен для орга­низации взаимодействия между двумя не связанными приложениями. Подобное взаимодействие организуется посредством интерфейсов, которые должны реализовываться объектами. Объекты, подчиняющиеся правилам СОМ, называются СОМ-объектами.

Каждый СОМ-объект имеет уникальный идентификатор класса (CLSID) и создается с помощью функций, содержащихся в специальной фабрике классов (classfactory), связанной с данным идентификатором. При создании Объекта приложение получает указатель на базовый интерфейс IUnknown данного объекта, и в дальнейшем все функции объекта вызываются через этот указатель. Такая схема позволяет создавать объекты независимо от языка программирования, на котором написано приложение. В обязанности библиотек OLE входит также передача параметров вызова функций и возвращаемых значений через границы процессов.

Структурированные файлы

Данные составных документов записываются на диск в виде структурированных файлов, в которых используются специальные объекты — потоки (stream) и хранилища (storage). Потоки напоминают обычные файлы, а хранилища аналогичны папкам. Структурированные файлы выполняют роль оболочки, скрывающей реальное разме­щение данных на диске и облегчающей пользователям процесс манипулирования документами.

Унифицированная передача данных

Унифицированная передача данных реализуется посредством объектов данных, инкапсулирующих сами данные. Наличие указателя на объект облегчает подключение к источнику данных различных клиентов. Объект данных, в свою очередь, осущест­вляет полный контроль за обменом данными между приложениями. Поэтому, с точки зрения программистов, обмен данными, осуществляемый методом drag-and-drop, ничем не отличается от передачи через буфер обмена.

Внедрение

В составных документах часто хранится информация из разных источников, не связанных между собой. Например, текстовый документ MicrosoftWordможет содержать электронную таблицу Excelи точечный рисунок из программы Paint.

До появления OLEдиаграммы и точечные рисунки можно было копировать в текстовый документ лишь через буфер обмена. Как только объект помещался в новый документ, он терял все связи с приложением, в котором был создан. Объект превращался в статическое, "мертвое" изображение. Если со временем возникала необходимость внести в такой объект изменения, пользователю приходилось возвра­щаться к исходному приложению, вносить изменения в оригинал, копировать новый объект и замещать им старый.

В описываемом случае Word является контейнером, a Excel и Paint— серверами. Иными словами, контейнер содержит объекты, созданные в других приложениях, а сервер — это приложение, являющееся источником самих объектов. В качестве примера мы рассмотрим внедрение в документ Word объекта Paint.

Запустите программу MicrosoftWord. Типичное окно Word с введенным текстом показано на рис. 21.1.


Рис. 21.1. Microsoft Word является приложением-контейнером

Далее в меню Insert выберите команду Object.... Перед вами откроется диалоговое окно вставки объекта (рис. 21.2).


Рис. 21.2. Окно вставки позволяет выбрать тип внедряемого объекта

В списке типов объектов выделите элемент PaintbrushPicture. После щелчка на кнопке ОК будет автоматически запущена программа Paint, область рисования которой разместится поверх документа Word, а панели инструментов и меню будут интегрированы в окно Word(рис. 21.3). При этом сам Wordавтоматически перейдет в режим разметки страниц.


Рис. 21.3. В качестве внедряемого объекта выбран рисунок из приложения Paint

Теперь, используя все средства и возможности программы Paint, можно нарисовать изображение, которое вы хотите добавить в текстовый документ (рис. 21.4). После завершения работы над рисунком выберите в меню File команду Save.


Рис. 21.4. Рисунок, внедряемый в текстовый документ, создаетсяы с помощью программы Paint

Окончив работу над рисунком, щелкните мышью на текстовом документе за пределами области рисования, чтобы' возвратиться в Wordи закрыть редактор Paint. На рис. 21.5 показан документ Word, содержащий внедренный объект.


Рис. 21.5. Текстовый документ Word с внедренным рисунком

А теперь самое интересное. Предположим, вы решили, что внедренный объект требует изменений. Выполните на нем двойной щелчок. При этом запустится исходное приложение, в которое будет загружен объект для редактирования (рис. 21.6).


Рис. 21.6. При необходимости отредактировать внедренный объект выполните на нем двойной щелчок


Рис. 21.7. Отредактированный объект

Связывание

Технология OLE поддерживает также динамическое связывание объектов, созданных в разных приложениях. При наличии связи приложения могут одновременно использовать одни и те же объекты. Раньше связывание было довольно неустойчивым механизмом, поскольку связи легко разрывались при перемещении файлов на диске. В настоящий момент в OLEиспользуются псевдонимы (monikers), позволяющие успешно решать многие из существовавших прежде проблем. Псевдонимом называ­ется специальный СОМ-объект, в котором хранится имя связанного объекта и информация о его местоположении. Именно псевдонимы выполняют задачу поиска объектов, освобождая от этого приложение-контейнер.

Создание OLE-контейнера

Приложение-контейнер Cnt, созданием которого мы сейчас займемся, напоминает программу Graphс SDI-интерфейсом, созданную нами в предыдущей главе. В данном приложении будут использованы два важных OLE-класса: COleClientItem и COleDocument. Класс COleDocument управляет списком объектов класса COleClientItem. Класс СOleClientItem, в свою очередь, управляет внедренными или связанными объектами и поддерживает взаимодействие между контейнером и сервером.

Обратите внимание на тот факт, что код приложения полностью создан мастером AppWizard. Полученный шаблон впоследствии можно расширить дополнительными средствами, написав соответствующие фрагменты самостоятельно или воспользовав­шись мастером ClassWizard.

Работа с мастером приложений

Работа с мастером AppWizardдостаточно подробно рассматривалась в предыдущей главе. Поэтому сейчас мы сконцентрируем внимание лишь на наиболее важных моментах создания приложения Cnt.

1.    В первом окне установите опцию Singledocument.

2.    Во втором окне не задавайте поддержку баз данных.

3.   В третьем окне установите опцию Container, указывающую на то, что приложение будет OLE-контейнером. В этом же окне следует включить поддержку элементов управления ActiveX.

4.    В следующем, четвертом, окне необходимо оставить все опции, заданные по умолчанию.

5.    В пятом окне установите опцию MFCStandard, опцию включения комментариев в программу и опцию статической компоновки библиотеки MFC.

6.   Наконец, в шестом окне, просмотрите список классов, которые будут созданы автоматически, и щелкните на кнопке Finish.

Теперь осталось только построить исполняемый файл приложения, выбрав для этого в меню Build команду Rebuild All. В результате в папку DEBUG будет добавлен файлСМТ.ЕХЕ.

Анализ программного кода

Приложение включает пять основных исходных файлов, сгенерированных мастером AppWizard: CNT.CPP, MAINFRM.CPP, CNTDOC.CPP, CNTVIEW.CPP и CNTRITEM.CPP.

Файл CNT.CPP

Текст, содержащийся в файле CNT.CPP, почти идентичен тексту файлов GRAPH.CPP и EDITOR.CPP приложений Graphи Editor, описанных в предыдущей главе. Поэтому рекомендуем вам вернуться к той главе и еще раз прочитать пояснения к указанным файлам.

Листинг файла CNT.CPP показан ниже.

// Cnt.cpp: определяет работу приложения.

//

#include "stdafx.h"

#include "Cnt.h"

#include "MainFrm.h" #include "CntDoc.h" #include "CntView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static  char  THIS_FILE[]   = _FILE_;

#endif

////////////////////////////////////////////////////////////////

// CCntApp

BEGIN_MESSAGE_MAP {CCntApp, CWinApp) //{(AFX_MSG_MAP (CCntApp) ON_COMMAND(ID_APP_ABOUT, OnAppAbout)

// ПРИМЕЧАНИЕ: мастер классов будет добавлять и удалять здесь

//       макросы схемы сообщений.

// НЕ РЕДАКТИРУЙТЕ то, что здесь находится.
//}}AFX_MSG_MAP
// Стандартные операции с документами
ON_COMMAND(ID_FILE_NEW, CWinApp :: OnFileNew)
ON__COMMAND(ID_FILE_OPEN, CWinApp: :OnFileOpen)
// Стандартная команда задания установок принтера


ON_COmAND(ID_FILE_PRINT_SETUP, CWinApp: :OnFilePrintSetup) END_MESSAGE_MAP()

////////////////////////////////////////////////////////////////
// КонструкторклассаCCntApp

CCntApp::CCntApp() {

// TODO: здесь добавьте код конструктора.

// Все наиболее важные команды инициализации

// разместите в методе Initlnstance.

}////////////////////////////////////////////////////////////////

// Единственный объект класса CCntAppCCntApptheApp;

////////////////////////////////////////////////////////////
// Инициализация класса CCntApp

BOOLCCntApp::Initlnstance() {

// Инициализация библиотек OLE

if(!Afx01eInit() )

{

AfxMessageBox(IDP_OLE_INIT_FAILED); return FALSE; }

AfxEnableControlContainer ();

// Стандартнаяинициализация.

// Если вам не нужны используемые здесь возможности
// и вы хотите сократить размер исполняемого файла,
// удалите ненужные команды.

tifdef _AFXDLL

Enable3dControls();
// эта функция вызывается при
// динамической компоновке MFC

#else

Enable3dControlsStatic();
// эта функция вызывается при

// статической компоновке MFC

#endif

// Измените раздел реестра, где будут храниться

// параметры программы.

SetRegistryKey(_T("Local AppWizard-Generated Applications"));

LoadStdProfileSettings();
// загрузка параметров из INI-файла

// Регистрация шаблонов документов приложения

CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate (

IDR_MAINFRAME,

RUNTIME_CLASS (CCntDoc) ,

RUNTIME_CLASS (CMainFrame) , // основное SDI-окно>>

RUNTIME_CLASS (CCntView) ) ;

pDocTemplate->SetContainer!nfo(IDR_CNTR_INPLACE) ;
AddDocTemplate (pDocTemplate) ;

// Анализ командной строки на предмет поиска системных команд, // DDE-команд или команд открытия файлов. CCommandLinelnfo cmdlnfo; ParseCommandLine (cmdlnf о) ;
// Обработка команд, указанных в командной строке

if ( ! ProcessShellCommand (cmdlnfo) ) return FALSE;

// Отображение окна приложенияm
_pMainWnd->ShowWindow (SW_SHOW) ; m_pMainWnd->UpdateWindow() ;

return TRUE;

}

////////////////////////////////////////////////////////////////
// Класс CAboutDlg, управляющий окном About

class CAboutDlg : public CDialog { public:

CAboutDlg ( ) ;

// Данные диалогового окна
//{{AFX_DATA (CAboutDlg) enum { IDD = IDD_ABOUTBOX }; //}}AFX_DATA

// Виртуальные функции, сгенерированные мастером ClassWizard

//{{AFX_VIRTUAL (CAboutDlg)

protected:

virtual void DoDataExchange (CDataExchange* pDX) ;

//})AFX_VIRTUAL

// Реализация

protected:

// { { AFX_MSG (CAboutDlg)

// Обработчики сообщений отсутствуют

//})AFXMSG

DECLARE_MESSAGE_MAP()

};

CAboutDlg: : CAboutDlg () : CDialog (CAboutDlg: : IDD) {

//{fAFX_DATA_INIT (CAboutDlg)

//}}AFX DATA INIT

}

void CAboutDlg::DoDataExchange(CDataExchange* pDX) {

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CAboutDlg)

/ / } } AFX_DATA_MAP }

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) //{{AFX_MSG_MAP(CAboutDlg)

// Обработчики сообщений отсутствуют //}}AFX_MSG_MAPEND_MESSAGE_MAP()

// Функция, управляющая выводом окна About

void CCntApp::OnAppAbout()

{

CAboutDlg aboutDlg;

aboutDlg.DoModal(); }

////////////////////////////////////////////////////////////////
// Другие функции класса CCntApp

Данный листинг содержит один фрагмент, заслуживающий особого внимания. В OLE используется концепция непосредственного редактирования (in-placeediting). Это означает, что после двойного щелчка на объекте, внедренном в документ контейнера, такой как наш, строка меню и панели инструментов соответствующего OLE-сервера замещают меню и панели инструментов контейнера. Например, если в документ приложения Cnt будет внедрена электронная таблица Excel, то после двойного щелчка на ней строка меню и панели инструментов программы Excel появятся в окне программы Cnt.

Смена меню происходит автоматически и обрабатывается библиотекой MFC с помощью средств OLE. Этот процесс становится возможным благодаря тому, что в приложение добавляется два ресурса меню: idr_mainfraimeи idr_cntr_inplace (имя последнего идентификатора уникально для данного приложения). По умолчанию отображается меню IDR_MAINFRAIME. Но когда внедренный объект активизируется для непосредственного редактирования, загружается меню idr_cntr_in-PLACE.

Файл MAINFRM.CPP

Опять-таки, текст файла MAINFRM.CPP, по сути, идентичен тексту одноименного файла приложения Editor из предыдущей главы. И мы рекомендуем вам еще раз вернуться к той главе и прочесть комментарии к нему. Но для полноты изложения текст файла все же приведем.

// MainFrm.cpp: реализация класса CMainFrame

//

#include "stdafx.h"

#include "Cnt.h"

#include "MainFrm.h" tifdef _DEBUG

#define new DEBUG_NEW

tundef THIS_FILE

Static char THIS_FILE[] = _FILE_;

#endif

////////////////////////////////////////////////////////////////

// CMainFrame

IMPLEMENT_DYNCREATE (CMainFrame, CFrameWnd)

i BEGIN_MESSAGE_MAP (CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP (CMainFrame)

// ПРИМЕЧАНИЕ: мастер классов будет добавлять и удалять здесь

//       макросы схемы сообщений.

// НЕ РЕДАКТИРУЙТЕ то,что здесь находится.
//}}AFX_MSG_MAPEND_MESSAGE_MAP ( )

static UINT indicators!] = {

ID_SEPARATOR,       // поля строки состояния

ID_INDICATOR_^CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

////////////////////////////////////////////////////////////////

IIКонструктор и деструктор класса CMainFrame

CMainFrame::CMainFrame() {

// TODO: здесь добавьте код конструктора.

}

CMainFrame::-CMainFrame() f


int CMainFrame: :OnCreate (LPCREATESTRUCT IpCreateStruct) { 

if (CFrameWnd: :OnCreate (IpCreateStruct) == -1) return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD |

WS_VISIBLE I CBRS_TOP | CBRS_GRIPPER I CBRSJTOOLTIPS I CBRS_FLYBY | CBRS_SIZE_DYNAMIC) | !m_wndToolBar.LoadToolBar(IDR_MAINFRAME) ) {

TRACEO ("Failed to create toolbar\n") ;

return-1;   // создать панель инструментов не удалось

}

if (!m_wndStatusBar.Create(this)||

!m_wndStatusBar.Setlndicators(indicators,

sizeof(indicators)/sizeof(UINT))) (

TRACEO("Failedto create status bar\n"); return -1;    // создать строку состояния неудалось}

// TODO: удалите следующие три строки, если вы не хотите,
//      чтобы панель инструментов была перемещаемой.
m_wndToolBar . EnableDocking (CBRS_ALIGN_ANY) ;
EnableDocking(CBRS_ALIGN_ANY) ;
DockControlBar (Sm_wndToolBar) ;

return 0;

}

BOOL CMainFrame: : PreCreateWindow (CREATESTRUCT Ses) {

if(ICFrameWnd: : PreCreateWindow (cs)) return FALSE;

// TODO: здесь можно модифицировать класс окна, изменяя поля структуры cs.

return TRUE; }

////////////////////////////////////////////////////////////////
// Диагностика класса CMainFrame

#ifdef _DEBUG

void CMainFrame: :AssertValid () const

{

CFrameWnd: :AssertValid() ;

}

void CMainFrame::Dump(CDumpContext Sdc) const

{

CFrameWnd::Dump(dc); }

#endif //_DEBUG

////////////////////////////////////////////////////////////////
// Обработчики сообщений класса CMainFrame

Файл CNTDOC.CPP

Файл CNTDOC.CPPсодержит ряд дополнительных фрагментов, с которыми ранее мы не встречались.

// CntDoc.cpp: реализация класса CCntDoc//

#include "stdafx.h" #include "Cnt.h"

# include "CntDoc.h"

#include "Cntrltem.h"

tifdef _DEBUG

#define new DEBUG_NEW

lundef THIS_FILE

static char THIS_FILE[] = _ FILE _ ;

#endif

////////////////////////////////////////////////////////////////
// CCntDoc

IMPLEMENT_DYNCREATE (CCntDoc, COleDocument)

BEGIN_MESSAGE_MAP (CCntDoc, COleDocument) //{{AFX_MSG_MAP (CCntDoc)

// ПРИМЕЧАНИЕ: мастер классов будет добавлять и удалять здесь
//         макросы схемы сообщений.
// НЕ РЕДАКТИРУЙТЕ то, что здесь находится.
//}}AFX_MSG_MAP

// Используется стандартная реализация OLE-контейнера ON_UPDATE_COMMAHD_UI ( ID_EDIT_PASTE,

COleDocument: :OnUpdatePasteMenu) ON_OPDATE_COMMAMD_OI ( ID_EDIT_PASTE_LINK ,

COleDocument: :OnUpdatePasteLinkMenu) ON_UPDATE_COMMMJD_UI ( ID_OLE_EDIT_CONVERT ,

COleDocument: :OnUpdateObjectVerbMenu) ON_COMMAND (ID_OLE_EDIT_CONVERT,

COleDocument: :OnEditConvert) ON_OPDATE_COMMAHD_OI (ID_OLE_EDIT_LINKS ,

COleDocument: :OnUpdateEditLinksMenu)

ON_COMMAND (ID_OLE_EDIT_LINKS ,

COleDocument: :OnEditLinks)

ON_0PDATE_COMMAND_UI_RftNGE ( ID_OLE_VERB_FIRST ,

ID_OLE_VERB_IAST,

COleDocument: :OnUpdateObjectVerbMenu) END_MESSAGE_MAP ()

////////////////////////////////////////////////////////////////
// Конструктор и деструктор класса CCntDoc    '

CCntDoc: : CCntDoc( ) {

* // включается поддержка составных файлов EnablecompoundFile ( ) ;

// TODO: здесь добавьте код конструктора.

}

CCntDoc::-CCntDoc()

{

}

BOOLCCntDoc: : OnNewDocument( )

{  

if ( ! COleDocument : : OnNewDocument ( ) ) return FALSE;

// TODO: здесь добавьте код повторной инициализации
//      (специфика SDI-приложений)

return TRUE;

}

////////////////////////////////////////////////////////////////
// Сериализация класса CCntDoc

void CCntDoc::Serialize(CArchive sar)         {

if (ar. IsStoringO )

{

// TODO: здесь добавьте код сохранения.

}    

else.

// TODO: здесь добавьте код загрузки. 
}          

// Вызов функции базового класса COleDocument обеспечивает
// сериализацию объектов COleClientltem, содержащихся
// в документе контейнера.

COleDocument : : Serialize (ar ) ;

}
////////////////////////////////////////////////////////////////

// Диагностика класса CCntDoc

tifdef _DEBUG

void CCntDoc::AssertValid() const

{

COleDocument::AssertValid(); }

void CCntDoc: : Dump (CDumpContext .&dc) const {

COleDocument: :Dump(dc) ; } #endif //_DEBUG

////////////////////////////////////////////////////////////////
// Другие функции класса CCntDoc

Наиболее важное отличие кода рассматриваемого файла от уже знакомых нам состоит в расширении схемы сообщений. В программу добавлены обработчики сообщений, реализующие работу стандартного контейнера.

Обратите внимание на функцию EnableCompoundFile() , вызываемую в конструкторе. Эта функция осуществляет поддержку составных файлов, что позволяет сохранять документы с внедренными объектами в специальном структурированном формате.

Файл CNTVIEW.CPP

Файл CNTVIEW.CPPтакже имеет ряд существенных особенностей по сравнению
с соответствующим файлом приложения Graph.

//  CntView.cpp   :  реализация класса CCntView

//

#include "stdafx.h"

#include "Cnt.h"

#include "CntDoc.h"

#include "Cntrltem.h"

#include "CntView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = _FILE_;

#endif

////////////////////////////////////////////////////////////////

// CCntView

IMPLEMENT_DYNCREATE(CCntView, CView)

BEGIN_MESSAGE_MAP(CCntView, CView) //{{AFX_MSG_MAP(CCntView)

// ПРИМЕЧАНИЕ: мастер классов будет добавлять и удалять здесь

//         макросы схемы сообщений.

//НЕРЕДАКТИРУЙТЕ то, что здесь находится. ON_WM_DESTROY() ON_WM_SETFOCUS() ON_WM_SIZE()

ON_COMMAND (ID_OLE_INSERT_NEW, OnlnsertObject) ON_COMMAND(ID_CANCEL_EDIT_CNTR, OnCancelEditCntr) //}}AFX_MSG_MAP // Стандартныекомандыпечати

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) END_MESSAGE_MAP ()

////////////////////////////////////////////////////////////////
//  Конструктор и деструктор класса CCntView

CCntView::CCntView() {

m_pSelection = NULL;

// TODO: здесь добавьте код конструктора.

}

CCntView::-CCntView()

}

BOOL CCntView::PreCreateWindow(CREATESTRUCT Ses) {

// TODO: здесь можно модифицировать класс окна,

//      изменяя поля структуры сз.

return CView::PreCreateWindow(cs); }

////////////////////////////////////////////////////////////////

// Отображение документа

void CCntView::OnDraw(CDC* pDC) {

CCn.tDoc* pDoc = GetDocument () ;

ASSERT_VALID(pDoc);

// TODO: здесь добавьте код для отображения собственных данных.

// TODO: должны отображаться все OLE-объекты,

//    содержащиеся в документе.

// Выделенный элемент может быть нарисован в произвольном месте.

// Этот код следует удалить в том случае, если вы вводите

// собственный код рисования. Указанные ниже координаты

// вточности соответствуют координатам, возвращаемым

// объектом CCntCntrltem, что создает эффект непосредственного

// редактирования.

// TODO: удалите следующий код, если реализуете собственный // код рисования.

if (m_pSelection == NULL)

{

POSITION pos = pDoc-X3etStartPosition() ;

m_pSelection = (CCntCntrItem*) pDoc-XSetNextClientltern(pos); > if (m_pSelection != NULL)

m_pSelection->Draw(pDC, CRect(10, 10,210,210));

}

void CCntView::OnInitialUpdate() {

CView::OnInitialUpdate(); 

// TODO: удалите следующий код, если реализуете собственный
//      код инициализации.

m_pSelection = NULL;
// инициализация переменной, содержащей
// указатель на выделенный объект

}

////////////////////////////////////////////////////////////////
// Печать документа

BOOL CCntView: :OnPreparePrinting (CPrintlnf о* plnfo) {

// стандартные действия по подготовке к печати

return DoPreparePrinting (plnfo) ;

}

void CCntView::OnBeginPrinting (CDC* /*pDC*/, CPrintlnfo* /*plnfo*/) {

// TODO: добавьте код дополнительной инициализации перед печатью. }

void CCntView::OnEndPrinting(CDC* /*pDC*/, CPrintlnfo* /*plnfo*/) ""

{

// TODO: добавьте код очистки после печати.

}

void CCntView::OnDestroy()

{

// Деактивиэировать объект при удалении; это важно,
// если используется режим разделения окна просмотра.
CView::OnDestroy(); COleClientltem* pActiveltem = GetDocument()->

GetlnPlaceActiveltem(this); if (pActiveltem != NULL && pActive!tem->

GetActiveView() == this) {

pActive!tem->Deactivate0; ASSERT(GetDocument()->

GetlnPlaceActiveltem(this) == NULL);
}
 }

////////////////////////////////////////////////////////////////

// Поддержка OLE-клиентов

BOOL CCntView::IsSelected(const CObject* pDocItem) const

{

// Представленная ниже реализация подходит, если выделенными
// являются только объекты CCntCntrltem. В противном случае
// данный код следует заменить.

// TODO: реализуйте функцию, которая проверяет тип выделенного объекта.

return pDocItem == m_pSelection; }

void CCntView::0nlnsert0bject() (

// Вызов стандартного диалогового окна InsertObject

// для получения информации о новом объекте CCntCntrltem.

COlelnsertDialog dig;

if (dlg.DoModaK) != IDOK) return;

BeginWaitCursor() ;

CCntCntrltem* pltem = NULL;

TRY

{

// Создаем новый объект, связанный с этим документом.

CCntDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pltem = new CCntCntrltem(pDoc);

ASSERT_VALID(pltem);

// Инициализация объекта на основании данных,
// полученных из диалогового окна.
if (!dig.Createltem(pltem))

AfxThrowMemoryException();
// подойдет исключение любого типа
ASSERT_VALID(pltem);    

// Если объект был выбран из списка классов, а не загружен
// из файла, запускаем сервер для редактирования объекта,
if (dlg.GetSelectionType() ==

COlelnsertDialog::createNew!tem) p!tem->DoVerb(OLEIVERB_SHOW, this);

ASSERT_VALID(pltem);

// Последний введенный объект выделяется.

// TODO: введите код, соответствующий требованиям вашего приложения.

m_pSelection = pltem;
// Указатель устанавливается на

// последний введенный объект
pDoc->UpdateAHViews (NULL) ;

CATCH(CException, e) if (pltem != NULL)

ASSERT_VALID(pltem) ; p!tem->Delete() ;

AfxMessageBox(IDP_FAILED_TO_CREATE); END_CATCH

EndWaitCursor(); >

// Следующий обработчик позволяет с помощью клавиатуры
// прерывать сеанс непосредственного редактирования.
// Инициируется это контейнером, а не сервером,
voidCCntView::OnCancelEditCntr()

// Редактируемый объект закрывается.
COleClientltem* pActiveltem= GetDocument()->

GetlnPlaceActiveltem(this); if (pActiveltem != NULL)

pActive!tem->Close();
ASSERT(GetDocument()->GetInPlaceActiveItem(this) == NULL);

}

// Обработчики OnSetFocusи OnSize требуются контейнеру
// в случае непосредственного редактирования объекта.


void CCntView: :OnSetFocus (CWnd* pOldWnd)
{   

COleClientltem* pActiveltem = GetDocument ( ) ->

GetlnPlaceActiveltem(this) ; if (pActiveltem != NULL &&

pActive!tem->GetItemState () ==   

COleClientltem: :activeUIState) {

// Фокус необходимо установить на объект, если он находится

// в той же области просмотра.

CWnd* pWnd = pActiveltem- >GetInPlaceWindow( );

if (pWnd != NULL)

{

pWnd->SetFocus( ) ;  // метод SetFocus базового класса не вызывается return;

}}

CView::OnSetFocus(pOldWnd); }

void CCntView: :OnSize (UINT nType, int ex, int cy)
{                 • '
CView: :OnSize (nType, ex, cy) ;
COleClientltem* pActiveltem = GetDocument ()->

GetlnPlaceActiveltem(this) ; if (pActiveltem != NULL)

pActiveItem->SetItemRects () ;

}

/////////////////////////////////////////////////
// ДиагностикаклассаCCntView

tifdef _DEBUG

void CCntView::AssertValid() const

{

CView::AssertValid(); }

void CCntView::Dump(CDumpContext Sdc) const

CView::Dump(dc); }

CCntDoc* CCntView::GetDocument() // отладочнаяверсия

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CCntDoc))); return (CCntDoc*)m_pDocument;

#endlf //_DEBUG

////////////////////////////////////////////////
// Обработчики сообщений класса CCntView

Чтобы приложение могло отображать внедренные объекты, были внесены изменения в функцию OnDraw( ). Обратите внимание на такой фрагмент:

if   (m_pSelection   !=NULL)

m_pSeleotion->Draw(pDC,   CRectdO,   10,210,   210));

}

По умолчанию объект размещается в окне приложения в заранее заданной области, которая определяется конструктором CRect( ) . Указанные координаты можно изменить вручную.

В файл были также добавлены функции OnlnitialUpdate() , IsSelectedO, OnlnsertObject() , OnCancelEditCntr() , OnSetFocusOИ OnSize(). В частности, функция OnlnsertObject() вызывает стандартное диалоговое окно класса COlelnsertDialog, предназначенное для вставки объектов.

Файл CNTRITEM.CPP

Файл CNTRITEM.CPP, листинг которого приведен ниже, содержит реализацию класса CCntCntrltem.

// Cntrltem.cpp: реализация класса CCntCntrltem

//

#include "stdafx.h"

#include "Cnt.h"  

#include "CntDoc.h"

#include "CntView.h"

#include "Cntrltem.h"  

#ifdef _DEBUG

#define new DEBUG_NEW

tundef THIS_FILE

static char THIS_FILE[] = _ FILE _ ;

#endif

//////////////////////////////////////////////////
// CCntCntrltem   

IMPLEMENT_SERIAL (CCntCntrltem, COleClientltem, 0) 

CCntCntrltem: : CCntCntrltem (CCntDoc* pContainer)

: COleClientltem (pContainer) {

// TODO: здесь добавьте код конструктора.

CCntCntrltera::~CCntCntrItem () {

// TODO: здесь добавьте код очистки.

}

void CCntCntrltem::OnChange(OLE_NOTIFICATION nCode, DWORD dwParam)

ASSERT_VALID(this); COleClientltem::OnChange(nCode, dwParam);

// Редактируемому объекту посылается уведомление OnChange,

// свидетельствующее об изменении его состояния или внешнего вида.

// TODO: обозначьте рабочую область объекта как недействительную //      путем вызова функции UpdateAllViews.

GetDocument()->UpdateAllViews(NULL);

BOOL CCntCntrltem::OnChangeItemPosition(const CRect SrectPos) ASSERT_VALID(this);

// В процессе непосредственного редактирования данная функция

// вызывается сервером для изменения позиции окна редактирования.

// По умолчанию вызывается метод базового класса, который,

// в свою очередь, задействует функцию COleClientltem::SetItemRects

// для перемещения объекта в новую позицию.

if (!COleClientltem::OnChangeItemPosition(rectPos)) return FALSE;

// TODO: обновите всю кэшированную информацию,
//      связанную с размерами объекта.

return TRUE; )

void CCntCntrltem::OnGetItemPosition(CRect SrPosition) ASSERT_VALID(this);

// В процессе непосредственного редактирования данная функция
// позволяет определить координаты области, занимаемой объектом.
// По умолчанию заданы фиксированные координаты.

// TODO: запишите правильные координаты (в пикселях)
//      в переменную rPosition.

rPosition.SetRect(10, 10,210, 210);

}

void CCntCntrltem: :OnActivate() {

// Допускается только один сеанс редактирования в окне приложения.

CCntView* pView = GetActiveView ( ) ;

ASSERT_VALID(pView) ;

COleClientltem* pltem = GetDocument ( ) ->

GetlnPlaceActiveltem(pView) ; if (pltem !- NULL ss-pltem != this) p!tem->Close () ;

COleClientltem: : OnAct ivate ( ) ;

}

void CCntCntrltem: :OnDeactivateUI (BOOL bUndoable) {

COleClientltem: :OnDeactivateUI (bUndoable) ;

// Скрывает объект, если он бьш . активизирован обычным способом DWORDdwMisc = 0;

m_lpObject->GetMiscStatus (GetDrawAspect () , SdwMisc) ;
if (dwMisc & OLEMISC_INSIDEOUT)
DoVerb (OLEIVERB_HIDE, NULL) ;

void CCntCntrltem: : Serialize (CArchive Sar) {

ASSERT_VALID(this) ;

// Вызов функции базового класса для считывания данных,

// связанных с объектом COleClientltem. При этом

// инициализируется указатель m_j>Document, возвращаемый

// функцией CCntCntrltem: :GetDocument, поэтому функцию базового класса

// лучше вызывать вначале.

COleClientltem: : Serialize (ar);

// Теперь сериализуем данные, специфичные для объекта CCntCntrltem.

if (ar.IsStoringO )

{

// TODO: здесь добавьте код сохранения. }

else{

// TODO: здесь добавьте код загрузки.

}

}

///////////////////////////////////////////////
//Диагностика класса CCntCntrltem

#if<fef _DEBUG

void CCntCntrltem: :AssertValid () const

COleClientltem::&ssertValid0;

void CCntCntrltem: :Dump(CDumpContext' &dc) const

COleClientltem::Dump(dc);
#endif

Основное назначение этого файла состоит в отслеживании координат и размеров области, занимаемой внедренным объектом. Обратите внимание на фрагмент, выделенный полужирным шрифтом. Именно здесь задаются координаты, о которых говорилось при рассмотрении файла CNTVIEW.CPP.

Проверка работы контейнера

А теперь давайте проверим, как функционирует разработанное нами приложе­ние-контейнер. Вспомните, что в данном случае мы имеем дело лишь с шаблоном приложения. Никакие дополнительные функциональные возможности в него не добавлялись, тем не менее такая программа является вполне работоспособной.

Окно нашей программы показано на рис. 21.8. В нее можно внедрять объекты, созданные в любом другом приложении Windows, поддерживающем технологию OLE. В качестве примера мы внедрим в программу электронную таблицу Excel. Выберите в меню Edit команду InsertNewObject..., в результате чего откроется стандартное диалоговое окно вставки объекта. В этом окне выделите элемент, соответствующий электронной таблице Excel(рис. 21.9)


Рис.21.8. Окно приложения Cnt

.
Рис. 21.9. Выбор типа внедряемого объекта


Рис. 21.10. В документ приложения Cnt внедряется электронная таблица Excel

На рис 21.10 показан результат внедрения в документ электронной таблицы Excel со значениями, введенными пользователем.

Еще раз напомним, что все функциональные возможности программы были заданы автоматически. Мы не изменили ни единой строчки в тексте, сгенерированном мастером AppWizard. Итак, вы убедились, что с помощью данного мастера можно легко создавать OLE-контейнеры. Теперь попробуйте самостоятельно создать простое приложение-сервер, а затем внедрите его объект в контейнер Cnt.

Таким образом технология OLE действительно является непростым предметом для изучения, но, безусловно, достойным вашего внимания. Как вы могли убедиться, с помощью мастера приложений и библиотеки MFC совсем не трудно создать простое приложение, поддерживающее технологию OLE. Собственно говоря, существенная помощь в создании OLE-приложений — это основное достоинство мастера AppWizard.

В следующей главе мы поговорим о таких важных средствах создания современных приложений, как элементы управления ActiveX.