Программисту-профессионалу/Windows 95 изнутри
PC Magazine/RE logo
©СК Пресс 11/96
PC Magazine, January 23, 1996, p.219

Создание контейнеров объектов OLE

Ричард Хейл Шоу


Воспользуйтесь технологией OLE и избавьтесь от ее сложностей с помощью VB 4.0 и VC++.

В основе технологии динамической компоновки объектов OLE (Object Linking and Embedding) лежит модель компонентных объектов (Component Object Model, COM) - протокол, определяющий способы взаимодействи прикладных программ OLE. Модель COM устанавливает сложный набор сценариев, позволяющих различным прикладным программам связываться между собой и обмениваться сообщениями. Используя правильно подобранную комбинацию стандартных и собственных прикладных программ, вы можете хранить информацию, сформулированную одной программой, в файлах данных другой программы и организовать их визуальное редактирование таким образом, что эти две прикладные программы будут восприниматься как единое целое.

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

Это были хорошие новости. К плохим можно отнести то, что собственно механизм OLE состоит в основном из чисто виртуальных функций на языке Си++, предоставляющих описания интерфейса, но не его реализацию: вы обращаетесь не просто к API (интерфейсу прикладного программирования), а к API, который вам предстоит самостоятельно реализовать. Следовательно, для того чтобы разработать Windows-программу на базе OLE, вам предстоит проделать большую работу. Даже если вы в совершенстве владеете языком Си++, для реализации полноценного набора интерфейсов OLE вам придетс усвоить огромное количество новых знаний, требующих понимания того, как OLE использует эти интерфейсы и какую роль они играют в реализации традиционной технологии динамической компоновки объектов.

К счастью, такие инструменты, как Visual Basic (VB) и Visual C++, позволяют создавать прикладные программы OLE без необходимости выдавать на-гора тонны исходных текстов и программировать непосредственно дл интерфейсов OLE. Эти инструменты выполняют за вас значительную часть работы по реализации интерфейса, давая возможность сосредоточиться на адаптации этих реализаций к вашим собственным сценариям, вместо того чтобы сначала решать задачу реализации интерфейсов и уж только потом задачу их использования. Реализации, предоставляемые каждым инструментом, соответствуют системе понятий, ранее установленной для данного инструмента, а не представлениям, принятым в OLE. Каков результат? Щадящий процесс усвоения новых знаний и более близкие к естественным средства создани прикладных программ, построение которых в противном случае было бы крайне затруднено. Используя эти инструменты, вы можете придерживаться привычных и удобных представлений, избегая гигантской по своим масштабам задачи реализации десятков интерфейсных функций, определенных спецификацией OLE.

Мы воспользуемся этими инструментами, чтобы показать, как можно строить контейнеры объектов OLE. Для этого рассмотрим сначала компоненты, предоставляемые Visual Basic для контейнеризации объектов OLE. Вы можете использовать их для создани простых контейнеров OLE, содержащих единственный объект и прекрасно сочетающихся с управляемой событиями парадигмой VB. Затем мы обратимся к Visual C++, который - наряду со своей инфраструктурой прикладных программ, библиотекой базовых классов Microsoft Foundation Classes (MFC) - позволяет создать более мощный контейнер OLE, способный вместить несколько объектов, наделенный полноценными средствами визуального редактирования и обладающий элегантным внешним видом полноценного коммерческого продукта.

Подход Visual Basic

В Visual Basic имеется простое решение дл обеспечения возможностей OLE-контейнера: контейнерный объект OLE в форме специального элемента управления. Этот орган управления первоначально поставлялся под названием VBX с версией 3.0, а теперь представлен в версии 4.0 в качестве элемента управления OLE. Элемент управления контейнера OLE дает возможность сохранять объекты OLE в файлах прикладных программ на языке VB. Хотя элемент управления позволяет одновременно работать лишь с одним объектом OLE, он предоставит вам возможности динамической компоновки объектов OLE и их редактирования с помощью методов визуального или открытого редактирования.

Ввести в прикладную программу Visual Basic элемент управления контейнера OLE несложно: достаточно создать форму VB и "перетащить" в нее элемент управления из палитры управления VB с помощью метода drag-and-drop. Можно создать экземпляр элемента управления и программным способом. После добавления в форму элемента управления вы сможете управлять поведением первой, видоизменяя ее свойства или обращаясь к ее методам.

Элемент управления контейнера OLE использует стандартную парадигму конечного пользователя Insert Object (вставка объекта) для создания новых экземпляров объектов OLE в прикладной программе на языке VB, а также допускает программное создание объектов OLE во время выполнения программы. Он также позволяет конечному пользователю вставлять объекты OLE из монтажного буфера (clipboard).

В приведенном здесь примере прикладной программы на языке Visual Basic показано применение элемента управления контейнера OLE в небольшой программе с интерфейсом MDI. Во время выполнения программы вы можете воспользоваться пунктом меню File|New (Файл|Новый), чтобы открыть новое дочернее окно MDI и вызвать диалоговое окно Insert Object. После того как из этого окна будет выбран новый объект дл встраивания, элемент управления контейнера вызовет соответствующий сервер и даст вам возможность выполнить визуальное редактирование объекта. При желании вы можете сохранить объект в файле на диске, а затем заново открыть файл и показать объект в окне. Повторно вызвать сервер объекта можно, дважды щелкнув на объекте мышью, а по щелчку правой клавиши мыши на экран выводятся команды OLE, предоставляемые с данным объектом.

Содержащая всего около 100 строк исходного текста на Visual Basic (лист. 1) демонстрационная программа показывает, что можно построить простой контейнер OLE, не прибегая к непосредственному программированию интерфейсов OLE. Действительно, программа сама по себе не очень сложна: в значительной степени она представляет собой VB-оболочку вокруг элемента управления контейнера, который и делает всю работу. Visual Basic - интерпретируемый язык, и, следовательно, его производительность будет существенно зависеть от соотношения используемых в прикладной программе на VB интерпретируемых программных модулей и компилируемых компонентов, таких, как элемент управления контейнера.


Лист. 1. Простая прикладная программа-контейнер на языке Visual Basic, где используется элемент управления контейнера OLE.
Attribute VB_Name = "ModOLECont" Option Explicit Public MDINew As Integer Sub NewObject() MDINew = True NewOleForm If MDIfrm.ActiveForm.Ole1.OLEType = vbOLENone Then Unload MDIfrm.ActiveForm End If End Sub Sub DisplayInctructions() ' Объявить локальные переменные. Dim MsgText Dim PB ' Инициализировать переменную конца абзаца. PB = Chr(10) & Chr(13) & Chr(10) & Chr(13) ' Вывести на экран инструкции. MsgText = "Для вставки нового объекта выберите пункт New меню File и затем выберите объект из диалогового окна Insert Object". MsgText = MsgText & PB & "После того как вставленный объект будет сохранен с помощью команды Save As, можно использовать команду Open меню File дл просмотра объекта в последующих сеансах работы". MsgText = MsgText & PB & "Для редактирования объекта дважды щелкните на объекте клавишей мыши, чтобы вывести на экран информацию о среде редактирования дл прикладной программы, в которой был создан объект". MsgText = MsgText & PB & "Щелкните на объекте правой клавишей мыши, чтобы увидеть исполнимые команды объекта". MsgText = MsgText & PB & "Используйте команды Copy, Delete и Paste Special для копирования, удаления и вставки объектов". MsgText = MsgText & PB & "Выберите команду Update, чтобы изменить содержимое вставляемого объекта". MsgBox MsgText, 64, "Руководство пользовател демонстрационной программы OLE Container Control". End Sub Sub NewOleForm Dim NewForm As New frmOLE Newform.Show ' Показать только диалоговое окно Insert Object, если пользователь выбрал пункт New меню File". If MDINew Then MDIfrm.ActiveForm.Ole1.InsertObjDlg End If UpdateCaption End Sub Sub OpenObject() MDINew = False NewOleForm OpenSave ("Open") If MDIfrm.ActiveForm.Ole1.OLEType = vbOLENone Then Unload MDIfrm.ActiveForm End If End Sub ' Открывание нового файла возможно лишь в случае, если файл содержит действительный объект автоматизации OLE. Чтобы наблюдать за работой программы, следуйте приведенным ниже указаниям во время ее выполнения. ' 1) Выберите пункт New меню File и укажите на объект. ' 2) Отредактируйте объект, затем выберите команду Save As из меню File. ' 3) Щелкните кнопкой мыши на окне управления объектом, чтобы закрыть его. ' 4) Выберите пункт Open меню File и затем выберите только что сохраненный файл. Sub OpenSave(Action As String) Dim Filenum Filenum = FreeFile ' Установить стандартные параметры и фильтры диалогового окна. MDIfrm.ActiveForm.CommonDialog1.Filter = "Встраиваемые объекты (*.OLE)|*.OLE|Все файлы (*.*)|*.*". MDIfrm.ActiveForm.CommonDialog1.FilterIndex = 1 MDIfrm.ActiveForm.Ole1.FileNumber = Filenum On Error Resume Next Select Case Action Case "Save" ' Вывести на экран диалоговое окно Save As. MDIfrm.ActiveForm.CommonDialog1.ShowSave If Err Then ' Пользователь выбрал пункт Cancel. If Err = 32755 Then Exit Sub Else MsgBox "Непредвиденная ошибка в диалоговом окне Save As". End If End If ' Открыть и сохранить файл. Open MDIfrm.ActiveForm.CommonDialog1.FileName For Binary As Filenum If Err Then MsgBox (Error) Exit Sub End If MDIfrm.ActiveForm.Ole1.SaveToFile Filenum If Err Then MsgBox (Error) Case "Open" ' Вывести на экран диалоговое окно File Open. MDIfrm.ActiveForm.CommonDialog1.ShowOpen If Err Then ' Пользователь выбрал пункт Cancel. If Err = 32755 Then Exit Sub Else MsgBox "Непредвиденная ошибка в диалоговом окне Save As". End If End If ' Открыть файл. Open MDIfrm.ActiveForm.CommonDialog1.FileName For Binary As Filenum If Err Then Exit Sub End If ' Вывести на экран указатель мыши в виде песочных часов. Screen.MousePointer = 11 MDIfrm.ActiveForm.Ole1.ReadFromFile Filenum If (Err) Then If Err = 30015 Then MsgBox "Недопустимый объект". Else MsgBox Error$ End If Unload MDIfrm.ActiveForm End If ' Теперь, когда элемент управления контейнером OLE содержит объект, установить свойства формы. UpdateCaption ' Восстановить указатель мыши. Screen.MousePointer = 0 End Select Close Filenum End Sub Sub UpdateCaption() ' Теперь, когда форма содержит объект, установить ее свойства. MDIfrm.ActiveForm.Caption = MDIfrm.ActiveForm.Ole1.Class + " Объект" On Error Resume Next End Sub

Visual C++: фундаментальный подход

Совершенно иной подход реализован в среде Visual C++, располагающей библиотекой базовых классов Microsoft Foundation Classes (MFC). Там, где Visual Basic предусматривает единственный элемент управления, работающий со специфическим подмножеством функций контейнера OLE, MFC предоставляет программисту множество классов, реализующих большое число интерфейсов OLE и инкапсулирующих эти реализации в наборе виртуальных функций, которые вы можете заменить другими в производном классе. Таким образом, можно составить прикладную программу на базе MFC, котора наследует реализацию интерфейсов, но частично или полностью изменит поведение этой реализации, создав новый класс наряду с новыми реализациями специфических виртуальных функций.

Вы можете подготовить простую прикладную программу-контейнер на Visual C++, используя "мастер" AppWizard. Шаг 3 "мастера" AppWizard позволяет включить в стандартную прикладную программу c интерфейсом MDI или SDI один из вариантов реализации простого контейнера объекта, дающий возможность добавлять динамически компонуемые объекты с помощью пункта Insert Object в меню Edit (Редактирование). AppWizard генерирует весь необходимый код MFC, в том числе специальное меню, содержащее лишь команды File (Файл) и Window (Окно), используемые контейнером во время визуального редактирования. После компиляции и выполнения сгенерированного кода вы можете использовать получившуюся в результате исполняемую программу дл динамической компоновки объектов OLE в любом окне прикладной программы.

Контейнер, созданный с помощью "мастера" AppWizard, работает благодаря встроенным в MFC средствам создани контейнера - в частности, реализации в MFC ряда интерфейсов OLE и инкапсуляции этих реализаций в некоторых ключевых классах MFC. Например, MFC содержит класс CDocument, дающий возможность определять форматы файлов документов и способы чтения и записи собственных файлов документов для прикладной программы. MFC расширяет этот класс, чтобы можно было создавать составные документы OLE, и предоставляет в ваше распоряжение производный класс COleDocument, позволяющий хранить внедренные объекты и информацию о связанных объектах в файлах документов. COleDocument составляет связанный список объектов, производный от еще одного класса MFC, COleClientItem, который используется для представления индивидуальных объектов OLE. По умолчанию COleDocument может сохранять объекты COleClientItem на диске и считывать их оттуда; как правило, вам нужно добавить к нему лишь программный текст и данные, необходимые для управления вашими собственными данными, с которыми вы будете работать в прикладной программе.

В прикладных программах на языке Visual C++ "мастер" AppWizard будет генерировать класс, производный от класса MFC CView, для управления выводом содержимого документов в окно на экране и взаимодействия с пользователем, редактирующим и просматривающим документ. Когда вы выбираете вариант контейнера OLE, AppWizard добавляет в просмотровый класс код, позволяющий ему выводить на экран встроенные или скомплектованные объекты документа - это значит, что просмотровый класс может отображать элементы OLE, хранящиеся в связанном списке объектов COleClientItem класса COleDocument.

Однако, хотя созданный с помощью AppWizard контейнер обеспечивает функции простого контейнера объектов, он имеет свои ограничения. Например, отсутствует возможность выбирать связанные или встроенные объекты одним щелчком мыши или активизировать их серверы двойным щелчком: выведенный в данный момент на дисплей объект можно активизировать, лишь воспользовавшись элементом меню Edit. И, хотя программа может компоновать или встраивать несколько объектов, генерируемый AppWizard код позволяет отображать лишь один объект за один раз. Если вы вставляете более чем один объект, то на экран выводится лишь последний из них; если составной документ сохраняется на диске и затем вновь открывается, то контейнер показывает лишь первый объект. И наконец, генерируемый с помощью AppWizard код не может запросить объект (или сервер) о его размерах; вместо этого программа выводит на экран образ метафайла объекта с фиксированным размером и положением в окнах контейнера. В результате при воспроизведении метафайлов объекты выглядят искаженными. Необходимо иметь возможность отслеживать и корректировать положение и размер встроенного или скомпонованного объекта и хранить эту информацию вместе с самим объектом.

Расширение функциональных возможностей

Ограничения, свойственные созданному с помощью AppWizard контейнеру, можно преодолеть, изменив текст. Чтобы сделать это, от вас потребуется близкое знакомство с библиотекой MFC и классами, используемыми в ней для реализации интерфейсов OLE, а также с некоторыми попутными предметами. Но эти знания вряд ли окажутся лишними.

Например, созданный с помощью AppWizard контейнер не управляет положением и размерами встроенных или скомпенсированных объектов, что позволило бы перемещать или менять их размеры в окне. Чтобы иметь такую возможность, вам необходимо добавить прямоугольный объект Windows - инкапсулированный в классе MFC CRect - в качестве элемента данных класса, производного от COleClientItem, созданного "мастером" AppWizard. Впоследствии этот элемент - m_rect - может быть использован, когда MFC и OLE известят контейнер об изменении положения или размера объекта (в переопределенной функции OnChangeItemPosition), и может сохранить эту информацию. Кроме того, указанный элемент класса CRect может оказаться полезным при определении положения и размера объекта OLE в случаях, когда контейнеру необходимо их знать (в переопределенной функции OnGetItemPosition) (лист. 2).

Благодаря тому что каждый объект OLE содержит информацию о своем положении и размерах, можно видоизменить просмотровый код контейнера таким образом, чтобы иметь возможность выводить на экран любые встроенные или скомпенсированные объекты, где бы они ни были размещены пользователем. По умолчанию генерируемый "мастером" AppWizard программный текст для рисовани объектов отображает лишь последний вставленный объект; AppWizard добавляет производный от COleClientItem указатель, m_pSelection, к просмотровому классу, который использует этот указатель для вызова функции COleClientItem::Draw, чтобы вывести на экран метафайл объекта. Операция рисования инкапсулирована в функции OnDraw класса View, которая вызывается всякий раз, когда контейнеру нужно перерисовать окно, содержащее объекты. Вместо этого, пройдя по связанному списку, составляемому производным от COleDocument документным классом, вы можете сделать так, чтобы каждый объект "рисовал сам себя". Кроме того, вы можете воспользоваться другим классом MFC, CRectTracker, чтобы создать точно определенную прямоугольную границу вокруг каждого объекта и четко очертить каждый объект в том месте, где он выводится на экран (лист. 3). Обратите внимание, что вызов функции COleClientItem::Draw использует элемент m_rect класса CRect (описанный выше), который отслеживает положение и размер объекта.


Лист. 2. Координаты и размеры встроенного или скомпонованного элемента хранятся в элементе CRect класса элементов контейнера и используются в функциях OnChangeItemPosition и OnGetItemPosition.
class CCntrItem : public COleClientItem { public CRect m_rect; ... }; BOOL CCntrItem::OnChangeItemPosition(const CRect& rectPos) { ASSERT_VALID (this); if (!COleClientItem::OnChangeItemPosition(rectPos)) return FALSE; InvalidateItem(); m_rect = rectPos; InvalidateItem(); GetDocument()->SetModifiedFlag(); return TRUE; } void CCntrItem::OnGetPosition(CRect& rPosition) { ASSERT_VALID(this); rPosition = m_rect; }
Лист. 3. Элемент-функция OnDraw класса View вызываетс всякий раз, когда контейнеру нужно заново вывести на экран встроенные или скомпонованные объекты. В программном цикле просматривается связанный список, чтобы каждый объект заново отобразился на экране.
void CCntrView::OnDraw(CDC* pDC) { CCntrDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); POSITION pos = pDoc ->GetStartPosition(); while (pos != NULL) { // нарисовать элемент CCntrItem* pItem = (CCntrItem*)pDoc->GetNextItem(pos); pItem->Draw(pDC, pItem->m_rect); // вычертить ограничивающий прямоугольник вокруг элемента CRectTracker tracker; InitTracker(pItem, &tracker); tracker.Draw(pDC); } }

Создание ограничивающих прямоугольников

Класс CRectTracker был добавлен в MFC, с тем чтобы обслуживать операции OLE, но им можно пользоватьс всегда в тех случаях, когда вы хотите:

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

Например, CRectTracker::Track даст пользователю возможность растягивать, перемещать и изменять размеры прямоугольника, передавая в вызывающую программу значение, указывающее на произведенные изменения. Так, перехватив сообщение WM_LBUTTONDOWN, посылаемое, когда пользователь щелкает в окне левой клавишей мыши, вы можете вывести на экран прямоугольник, которым можно воспользоваться при выборе и перемещении встроенного или скомпенсированного объекта в окне.

Имея дело с MFC, вы создаете обработчик OnLButtonDown в просмотровом классе, с тем чтобы обработать сообщение WM_LBUTTONDOWN; если вы работаете с Visual C++, то используете компонент ClassWizard, чтобы, подведя курсор и щелкнув мышью, создать и установить свою функцию OnLButtonDown. Убедившись в том, что скомпенсированный или встроенный объект выбран, вы можете инициализировать CRectTracker и вызвать принадлежащую функцию Track для того, чтобы пользователь мог перемещать и изменять размеры прямоугольника, нарисованного вокруг объекта. Если в процессе работы прямоугольник был изменен пользователем, то CRectTracker::Track передаст в вызывающую программу ненулевое значение, давая вам возможность получить новые размеры и координаты прямоугольника из объекта CRectTracker для последующего автоматического перечерчивания просмотрового окна (лист. 4). Две вспомогательные функции, CheckHitTest и SelectItem, инкапсулируют процесс обнаружения факта выбора объекта пользователем и последующего действительного выбора этого элемента.

Убедиться, что пользователь действительно выбрал встроенный или скомпонованный элемент, может оказатьс непростым делом. Для этого требуется просмотреть связанный список объектов, хранящийся в документном объекте и проверить, не был ли курсор мыши (в момент щелчка левой клавишей) размещен над каждым из внесенных в список объектов. Но порядок наложения объектов друг на друга на экране соответствует порядку, в котором они встречаются в связанном списке, последний объект списка выводится на экран в последнюю очередь и находитс поверх всех остальных. Поэтому для выяснения, какой объект был выбран (если был выбран хоть один), в действительности потребуется два шага. Сначала следует определить, был ли курсор размещен над объектом (или, вернее, внутри прямоугольника, определяющего его размер и положение), и сохранить указатель на этот объект. Затем нужно проверить остальные объекты в списке, на случай если вместо него был выбран объект, находящийс ниже по списку, который будет выведен на экран поверх ранее выбранного объекта.

В нашем примере контейнера Visual C++ этот процесс инкапсулирован во вспомогательной функции CheckHitTest (лист. 5). Данная функция использует параметр CPoint, передаваемый обработчику OnLButtonDown, чтобы идентифицировать точку, в которой пользователь щелкнул левой клавишей мыши, и тем самым определить выбранный объект и передать в вызывающую программу указатель на этот объект.

Если щелчок левой клавишей мыши представляет попытку пользователя выбрать объект для активизации, то необходимо сбросить указатель выбора объекта просмотрового класса, m_pSelection. Дополнительна вспомогательная функция, SelectItem (лист. 6), обеспечивает закрытие любого активного в данный момент объекта OLE и затем выбирает объект, на котором щелкнул пользователь. Функция сначала проверяет, не занят ли контейнер визуальным редактированием другого объекта; если это так, то данный объект должен быть деактивирован. Затем она удаляет объект, выбранный пользователем (с помощью CheckHitTest), из списка, обслуживаемого классом COleDocument, и вставляет этот объект в конец списка; этим гарантируется, что он окажется поверх всех остальных объектов при выводе на экран. И наконец, функция SelectItem устанавливает просмотровый указатель выбора на данный элемент, определяя его в качестве вновь выбранного объекта OLE.


Лист. 4. Обработчик сообщения WM_LBUTTONDOWN, OnLButtonDown, отвечает на щелчок левой кнопкой мыши в просмотровом классе контейнера, рису прямоугольник выбора вокруг объекта OLE, а затем давая возможность пользователю перемещать и изменять размеры объекта.
void CCntrView::OnLButtonDown(UINT nFlags, CPoint point) { // Необходимо добавить: CheckHitTest... CCntrItem* pItem = CheckHitTest(point); SelectItem(pItem); // ...и SelectItem... if(pItem) { // ...и SetupTracker CRectTracker rectTracker; InitTracker(pItem,&rectTracker); if (rectTracker.Track(this, point)) { pItem->InvalidateItem(); pItem->m_rect = rectTracker.m_rect; pItem->InvalidateItem(); GetDocument()->SetModifiedFlag(); } CView::OnLButtonDown(nFlags, point); }
Лист. 5. Функция CheckHitTest класса View идентифицирует объект, включающий в себя конкретную точку на экране.
CCntrItem* CCntrView::CheckHitTest(CPoint point) { CCntrDoc* pDoc = GetDocument(); CCntrItem* pItemHit = NULL; POSITION pos = pDoc->GetStartPosition(); while(pos != NULL) { CCntrItem* pItem = (CCntrItem*)pDoc->GetNextItem(pos); if (pItem->m_rect.PtInRect(point)) pItemHit = pItem; } return pItemHit; // передать в вызывающую программу элемент, занимающий верхнее положение в данной точке экрана }
Лист. 6. Функция SelectItem закрывает любой активный в данный момент элемент OLE, заново вставляет только что выбранный элемент в список объектов OLE и затем устанавливает просмотровый указатель выбора на выбранный объект.
void CCntrView::SelectItem(CCntrItem* pItem) { // если не указан ни один элемент и отсутствуют ранее выбранные элементы if ((pItem == NULL) && (m_pSelection == NULL)) return; // если уже был выбран другой элемент, закрыть активный элемент по месту if (m_pSelection != pItem) { // получить указатель на текущий элемент и закрыть его COleClientItem* pActive = GetDocument()->GetInPlaceActiveItem(this); if ((pActive != NULL) && (pActive != pItem)) pActive->Close(); if ((pActive != pItem) && (pItem != NULL)) { GetDocument()->RemoveItem(pItem); GetDocument()->AddItem(pItem); GetDocument()->SetModifiedFlag(); } if (m_pSelection != NULL) OnUpdate (NULL, HINT_UPDATE_ITEM, m_pSelection); m_pSelection = pItem; if (m_Selection != NULL) OnUpdate(NULL, HINT_UPDATE_ITEM, m_pSelection); } }

Активизация и удаление объекта

Для активизации объекта контейнеру нужен обработчик сообщения WM_LBUTTONDBLCLK. Обработчик будет использовать просмотровый указатель выбора, m_pSelection, для обращения к объекту через COleClientItem::DoVerb и, в свою очередь, вызывать сервер объекта, чтобы активизировать объект дл визуального или открытого редактирования. На лист. 7 показано, как обработчик OnLButtonDblClk выполняет эти действия.

И наконец, созданные с помощью AppWizard контейнеры не обеспечивают никакого механизма, позволяющего удалять встроенные или скомпонованные объекты из составного документа контейнера. Но вы можете добавить команду Delete (Удалить) в просмотровое меню Edit (Редактировать) и создать функцию-обработчик команд, удаляющую выбранный объект, как показано на лист. 8.

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


Лист. 7. Обработчик сообщения WM_LBUTTONDBLCLK дает пользователю возможность активизировать объект через его основную команду OLE или вербальную команду.
void CCntrView::OnLButtonDblClk(UINT nFlags, CPoint point) { if (m_pSelection != NULL) { m_pSelection->DoVerb((::GetKeyState(VK_CONTROL) < 0 ? OLEIVERB_OPEN : OLEIVERB_PRIMARY), this); } CView::OnLButtonDblClk(nFlags, point); }
Лист. 8. Функция OnEditClear позволяет пользователю удалить выбранный объект с помощью пункта Delete в просмотровом меню Edit.
void CCntrView::OnEditClear() { if (m_pSelection != NULL) // в действительности не нужен { m_pSelection->Delete(); m_pSelection = NULL; GetDocument()->UpdateAllViews(NULL); GetDocument()->SetModifiedFlag(); } }

Некоторые общие замечания

При подготовке обсуждавшихся в данной статье программ VB и VC++ возник ряд интересных проблем. Во-первых, я был удивлен, как много различных компонентов необходимо поместить в VB-контейнер, чтобы он мог работать на "голой" машине с Windows 95 (т. е. на машине, на которой не был заранее установлен VB 4.0). Например, программный фрагмент на лист. 1 вместе с двумя формами VB компилируется в 23-Кбайт файл OLECONT.EXE. (Сам по себе Visual Basic не располагает компилятором, но содержит средства, позволяющие создать исполнимую программу, которая будет вызывать процессор интерпретируемого языка для динамического исполнени кода.) Для выполнения программы OLECONT вам понадобятс библиотеки MSVCRT40.DLL (библиотека DLL рабочих программ Microsoft Visual C++ 4.0) и инсталлированна VB40032.DLL; вместе эти файлы занимают более одного мегабайта дискового пространства. Для управлени контейнером OLE вам также понадобится библиотека OLEPRO32.DLL (72 Кбайт), а для вывода на экран диалогового окна Common File Open вам будет нужен набор средств управления OLE - COMDLG32.OCX (91 Кбайт). Чтобы они могли работать, вам понадобится библиотека MFC40.DLL, которая содержит MFC 4.0 и добавляет еще 900 Кбайт.

В целом прикладная программа на языке VB занимает более 2 Мбайт на жестком диске. С другой стороны, дл контейнера Visual C++, построенного с помощью VC++ 2.2, требуется лишь 31 Кбайт. (Во время подготовки данной статьи Visual C++ 4.0 еще не поставлялся.) Хотя дл контейнера VC++ требуются библиотеки MFC30.DLL (316 Кбайт) и MFCO30.DLL (131 Кбайт), обе они входят в состав Windows 95, а чтобы опробовать контейнер VC++ на компьютере с Windows 95, вам понадобится лишь модуль CNTR.EXE. Даже если вы перекомпилируете программу с помощью бета-версии VC++ 4.0, объем дистрибутивных файлов составит лишь около 900 Кбайт. Обе приведенные здесь в качестве примеров программы вместе с файлами, необходимыми для их выполнения, содержатся в двух файлах BASCONT.ZIP и CPPCONT.ZIP. Вы можете загрузить эти файлы из сети службы PC Magazine Online.

Безусловно, для работы с такой инфраструктурой прикладных программ на Си++, как Visual C++ и Microsoft Foundation Classes, вам необходимо больше знать о том, каким образом создаются прикладные программы OLE, чем когда вы имеете дело с Visual Basic. Но производительность исполнимых программ на Си++ превосходна, а расширяемость языка Си++ позволяет применять реализации OLE на базе MFC к самым разнообразным контейнерам и серверам. В следующей статье мы рассмотрим, каким образом можно применить эти инструменты для построения клиентов и серверов, использующих автоматизацию OLE - мощное, совершенно оригинальное функциональное средство OLE, предназначенное для разработчиков.

Ричард Хейл Шоу - внештатный редактор PC Magazine.