Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ООП / Заочники / ООП_ЛабРаб_Методичка

.pdf
Скачиваний:
30
Добавлен:
08.06.2015
Размер:
770.97 Кб
Скачать

свойству нового значения Value, необходимо запустить или остановить таймер (если свойство SemState изменятся на/из scPulse) и изменить состояние трех встроенных компонентов DdhLed.

procedure TDDHSemaphore.SetSemState(Value: TSemState); begin

if Value <> SemState then begin TurnOff;

if fSemState = scPulse then

StopPulse; case Value of

scRed: fRedL.Status := lsOn; scGreen: fGreenL.Status := lsOn; scYellow: fYellowL.Status := lsOn; scPulse: StartPulse;

scOff: ; // ничего не надо делать end;

fSemState:= Value; end;

end;

Процедура TurnOff в начале метода SetSemState устанавливает свойство Status всех DdhLed компонентов в состояние lsOff:

procedure TDDHSemaphore.TurnOff; begin

fRedL.Status := lsOff; fGreenL.Status := lsOff; fYellowL.Status := lsOff;

end;

Другие два метода StartPulse и StopPulse, вызываемые из SetSemState, динамически создают и уничтожают таймер, используемый для вспышек красного индикатора DdhLed: procedure TDdHSemaphore.StartPulse;

begin

fTimer:= TTimer.Create(self); fTimer.Interval:= fInterval; fTimer.OnTimer:= TimerOnTimer; fTimer.Enabled:= True;

end;

procedure TDDHSemaphore.StopPulse; begin

fTimer.Enabled := False; fTimer.Free;

fTimer:= nil; end;

Конструктор создает компонент семафора следующим образом: constructor TDdhSemaphore.Create(Owner: TComponent);

begin

inherited Create(Owner);

// создать LED и установить цвета

fGreenL := TDDHLed.Create(self); fGreenL.Parent := self; fGreenL.Color := clLime;

fYellowL := TDDHLed.Create(self);

fYellowL.Parent := self; fYellowL.Color := clYellow; fRedL := TDDHLed.Create(self); fRedL.Parent := self; fRedL.Color := clRed;

fGreenL.onClick:=GreenLedClick;

fYellowL.onClick:=YellowLedClick;

fRedL.onClick:=RedLedClick;

Width:=30;

Height:=90;

Interval:=500;

SemState:=scOff;

End;

Метод StopPulse также вызывается в деструкторе в случае свечения индикатора

DdhLed:

destructor TDDHSemaphore.Destroy; begin

if fSemState = scPulse then StopPulse;

inherited Destroy; end;

Действие таймера нужно для переключений красного индикатора DdhLed: procedure TDDHSemaphore.TimerOnTimer(Sender: TObject);

begin

if fRedL.Status = lsOn then fRedL.Status := lsOff else fRedL.Status := lsOn;

end;

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

когда SemState равен scPulse. Если создать таймер в конструкторе, можно использовать его свойство Interval и не объявлять его в классе TDdhSemaphore. Так как таймер может отсутствовать в момент установки значения свойства, необходимо создать внутреннее поле, а затем скопировать значение во встроенный компонент:

procedure TDDHSemaphore.SetInterval(Value: Integer); begin

if Value <> fInterval then begin fInterval := Value;

if Assigned(fTimer) then fTimer.Interval := fInterval; end;

end;

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

Установка любого из позиционных свойств TControl (Left, Top, Height и Width) всегда приводит к вызову метода SetBounds. Так как это виртуальный метод, его можно

просто переопределить, чтобы настроить установку размеров компонента TDdhSemaphore и встроенных в него компонентов DdhLed.

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

DdhLed:

procedure TDDHSemaphore.SetBounds(ALeft, ATop, AWidth, AHeight : Integer); var

LedSize: Integer; begin

//Установка минимального размера if AWidth < 20 then AWidth := 20;

if AHeight < 60 then AHeight:= 60;

inherited SetBounds(ALeft, ATop,AWidth, AHeight);

//Вычисление фактического размера образа светофора if AWidth * 3 > AHeight then LedSize:= AHeight div 3 else LedSize:= AWidth;

//Установка позиции и размера LED

LedSize := LedSize - 2; fRedL.SetBounds(1, 1,LedSize, LedSize);

fYellowL.SetBounds(1, LedSize + 3,LedSize, LedSize); fGreenL.SetBounds(1, LedSize *2+5,LedSize, LedSize);

end;

Метод Paint делегирует работу компонентам DdhLed. Это не очевидно из исходного текста, потому что Delphi автоматически вызывает методы Paint из трех подкомпонентов:

procedure TDDHSemaphore.Paint; var

LedSize: Integer; begin

// Вычисление фактического размера образа светофора

if Width *

3 >

Height then LedSize := Height div 3

else LedSize

:=

Width;

// рисование фона

Canvas.Brush.Color := clBlack; Canvas.FillRect(Rect(0, 0,LedSize, LedSize * 3) ) ; end;

В компоненте DdhSemaphore используются заказные события, т.е. вместо простого переобъявления стандартных событий определяются новые события. Нам нужно создать события для щелчка мышью на любом из компонентов DdhLed. Это не просто заказное событие, а заказной тип события (то есть заказной тип указателя метода) TLightClickEvent.

TLightClickEvent = procedure(Sender: TObject; var Active: Boolean) of object;

Обратите внимание, что, в дополнение к обычному параметру Sender, определен второй параметр со значением типа Boolean, переданным через ссылку. Этот параметр используется, чтобы позволить драйверу события передать информацию назад в компонент, основываясь на некотором условии, определенном в программе, использующей компонент.

Чтобы обработать заказные события, добавлены три новых поля TLightClickEvent (fGreenClick, fRedClick, fYellowClick), три метода для перехвата событий компонента

TDdhLED (GreenLedClick, RedLedClick, YellowLedClick) и три новых свойства для действительных событий (GreenClick, RedClick,YellowClick)

Теперь исследуем код метода GreenLedClick. Обычно, если метод присвоен соответствующему свойству события, то событие вызывает этот метод. Необычно, что необходимо предоставлять начальное значение для параметра, определяемого ссылкой (переменная status, которая становится параметром Active при вызове метода), а затем нужно проверить конечное значение параметра, который может быть изменен обработчиком события:

procedure TDDHSemaphore.GreenLedClick(Sender: TQbject); var

Status: Boolean; begin

if Assigned(fGreenClick) then begin Status := (fGreenL.Status = lsOn); fGreenClick(self, Status);

if Status then SemState := scGreen; end;

end;

Свойство Active позволяет обработчику события возвращать изменение значений в соответствующие методы, благодаря использованию ссылочного параметра. Смысл этого подхода состоит в том, что при щелчке мышью пользователем на одном из компонентов DdhLed, программа сообщит компоненту, что необходимо установить LED в состояние On. Поддержка противоположной операции выключения DdhLed при повторном щелчке мышью пользователем отсутствует, потому что она перевела бы компонент DdhSemaphore в неопределенное состояние. Запомните, что это событие определено не для одного из компонентов DdhLed, а как событие компонента DdhSemaphore, работающего как единый объект. Действительно, вышеприведенный код изменяет Status светофора, а не встроенного компонента ТDdhLed.

Большое преимущество этого подхода заключается в том, что нужно писать меньше кода для реализации требуемого специального поведения.

3. Методика выполнения

3.1.Из главного меню Delphi выберите Component->New Component…

3.2.Введите в окно редактирования мастера компонентов Component Wizard (диалоговое окно New Component) имя класса предка, имя нового класса, имя закладки панели компонентов и новое имя модуля файла. Затем щелкните мышью кнопку «Установить».

3.3.В диалоговом окне «Установить» выберите существующий пакет или перейдите на следующую страницу и введите имя и описание нового пакета. Затем нажмите кнопку

«ОК».

3.4.Редактор пакета попросит подтверждения установки пакета и включение компонента.

3.5.После нажатия кнопки «OK» редактор пакета инсталлирует компонент и отобразит его в палитре компонентов Delphi.

3.6.Используя редактор пакета, откройте исходный текст компонента в редакторе кода и измените определение компонента и программный код его методов. Чтобы отменить инсталляцию компонента и убрать его из палитры, удалите его из пакета и нажмите кнопку «Построить». Чтобы повторно включить компонент в пакет и палитру компонентов, необходимо добавить в пакет модуль или новый компонент (кнопка «Добавить») и нажать кнопку «Построить». Для изменения существующего пакета

Редактор пакета можно открыть, используя меню Component->Install Pakages. Далее выбрать в списке нужный пакет и нажать кнопку «Изменить».

3.7. Разработайте тестовое приложение, демонстрирующее все возможности нового компонента.

4. Дополнительные указания

4.1.Нельзя установить два компонента Delphi с одним и тем же именем. Использование префикса - хороший способ делать имена класса уникальными. Для обеспечения уникальности нового компонента используйте префикс, образованный инициалами ФИО одного из членов бригады (например, VIK).

4.2.Используя Редактор пакета создайте вначале компонент «Световой индикатор» и протестируйте его работу, а затем компонент «Светофор».

4.3.Компоненты включите в состав пакета Borland User Components. Исходный файл пакета - ….\Borland\Delphi5\Lib\dclusr.dpk.

4.4.Компоненты разместите на закладке панели компонентов Delphi с именем, образованным из обозначения группы и номер бригады, например, 2AIT3BR6.

4.5.Создайте пиктограммы, которые Delphi будет использовать для каждого компонента. Пиктограмма должна быть битовым образом 24 х 24 пикселя с тем же именем, что и класс компонента (но с использованием только прописных букв, например, TVIKSEMAPHORE). Для разработки используйте графический редактор Tools->Image Editor. Тип рисунка – Component Resource File (.dcr). Файл включите в пакет либо директивой {$R *.DCR} в модуле компонента, либо с помощью Редактора пакета (допустим только ОДИН из этих способов).

4.6.Поля, методы доступа и свойства называйте в соответствии со стандартом, определенным фирмой Borland в руководстве Component Writer's Guide. Основной руководящий принцип: используйте для свойств содержательные имена, добавляйте f к именам полей, соответствующих свойствам, и называйте методы доступа, как Get или Set плюс имя свойства (например, SetStatus).

4.7.Для изменения размеров компонента используйте замещение метода SetBounds. Пользователь может изменять свойства компонента Width и Height, используя инспектор объектов, растягивая и сжимая границы компонента или устанавливая значения в программе. Установка любого из позиционных свойств TControl (Left, Top, Height и Width) всегда приводит к вызову метода SetBounds. Так как это виртуальный метод, его можно просто переопределить, чтобы настроить установку размеров компонента «светофор» и встроенных в него компонентов DdhLed.

5. Содержание отчета

Отчет готовится один на бригаду в рукописном или печатном виде и должен содержать следующие разделы:

-задание;

-листинг программы с подробными комментариями;

-копия экранной формы для тестирования компонента;

-на всех копии экранной формы должны быть указаны идентификаторы всех объектов формы.

6.Контрольные вопросы

6.1.Что такое конструктор и деструктор класса? Как они используются в программах?

6.2.Что такое свойства класса?

6.3.Каким образом в программах используются обработчики событий?

6.4.Расскажите о замещении и перегрузке методов классов.

6.5.Какие существуют правила именования полей, методов и свойств классов и компонентов Delphi?

6.6.В чем разница между статическими и виртуальными методами классов?

6.7.Как используются разделы в объявлении класса? Как выглядит окно инспектора объектов для новых компонентов? Как это связано с секциями класса?

6.8.Как выполняется перерисовка объектов?

Лабораторная работа № 9

Использование офисных приложений как серверов автоматизации COM

Цель работы: освоение на практике способов управления офисными приложениями из программ на языке Object Pascal.

1. Задание

Пользуясь средствами Borland Delphi, разработать Windows-приложение c графическим интерфейсом пользователя (окно Windows) для выполнения следующих функций:

задать имя книги Excel, в которой хранятся произвольные числовые данные в виде таблицы;

переписать эти данные в объект типа TStringGrid формы;

вычислить для каждой строки и столбца минимальное, максимальное, среднее значения и сумму чисел;

добавить полученные результаты в таблицу Excel;

переформатировать полученную общую таблицу Excel таким образом, чтобы отрицательные числа выводились красным цветом, а добавленные строки и столбцы курсивом;

задать имя документа Word, в котором содержится некоторый текст;

прочитать текст документа и поместить в поле TМемо;

«инвертировать» каждое слово и предложение;

результат записать в документ Word, причем слова, которые не изменились в результате преобразования, вывести курсивом синим цветом, а размер символов увеличить на 2 пункта.

2.Краткая теория

Офисные программы как серверы автоматизации СОМ

Все приложения, входящие в состав последних версий Microsoft Office, являются серверами автоматизации СОМ. Например, можно программно вызвать редактор Word, загрузить в него нужный документ, выполнить проверку орфографии или заполнить поля документа значениями из базы данных, а потом вывести документ на печать, после чего закрыть редактор. Аналогичным способом организуется и работа с электронной таблицей Excel, с базой данных Access, с почтовой программой Outlook и т. д.

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

Интерфейсы представляют собой частный случай описания типов. Они объявляются с помощью зарезервированного слова interface. Например:

type

2

IEdit = interface

procedure Copy; stdcall; procedure Cut; stdcall; procedure Paste; stdcall; function Undo: Boolean; stdcall;

end;

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

В отличие от классов интерфейс не может содержать поля. Все объявляемые в интерфейсе члены размещаются в единственной секции public. Методы не могут быть абст- рактными (abstract), виртуальными (virtual), динамическими (dynamic) или перекрываемыми (override). Интерфейсы не могут иметь конструкторов или деструкторов, т.к. описываемые в них методы реализуются только в рамках поддерживающих их классов, которые называются интерфейсными.

Если какой-либо класс поддерживает интерфейс, имя этого интерфейса указывается при объявлении класса в списке его родителей:

TEditor = class (TInterfacedObject, IEdit) procedure Copy; stdcall;

procedure Cut; stdcall; procedure Paste; stdcall; function Undo: Boolean; stdcall;

end;

В отличие от обычного класса интерфейсный класс может иметь более одного родительского интерфейса.

Все интерфейсы порождены от общего глобального интерфейса IUnknown, в котором объявлены методы QueryInterface, AddRef и Release. Поле FRefCount интерфейсного класса служит счетчиком вызовов интерфейсного объекта и используется по принятой в Windows схеме: при каждом обращении к методу AddRef интерфейса IUnknown счетчик наращивается на единицу, при каждом обращении к Release - на единицу сбрасывается. Когда значение этого поля становится равно 0, интерфейсный объект уничтожается и освобождается занимаемая им память.

Метод QueryInterface интерфейса IUnknown позволяет вернуть ссылку на любой интерфейс, поддерживаемый интерфейсным классом. Если объект не поддерживает указанный интерфейс, возникает исключительная ситуация.

Интерфейсы, рассчитанные на использовании в удаленных объектах, должны снабжаться глобально-уникальным идентификатором (GUID). Например:

IPaint = interface ['{A4AFEB60-7705-11D2-8B41-444553540000}'] procedure CirclePaint(Canva: TCanvas; X,Y,R: Integer);

procedure RectPaint(Canva: TCanvas; X1,Y1,X2,Y2: Integer); end;

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

Объекты Автоматизации представляют собой экземпляры интерфейсных классов, родительским интерфейсом которых является другой специальный интерфейс IDispatch, который также порожден интерфейсом IUnknown. Отличительной особенностью IDispatch является то обстоятельство, что методы объекта Автоматизации никогда не вызываются напрямую, а только с помощью метода Invoke интерфейса IDispatch. Для объявления класса Автоматизации используется специальное зарезервированное слово dispinterface, а

перечисляемые в нем методы и свойства должны снабжаться целочисленными идентификаторами, которые вставляются в конце описания методов (свойств) после заре- зервированных слов dispid:

3

type

IStringsDisp = dispinterface ['{EE05DFE2-5549-11D0-9EA9-0020AF3D82DA}']

property ControlDefault[Index: Integer]: OleVariant dispid 0; default; function Count: Integer; dispid 1;

property Item[Index: Integer]: OleVariant dispid 2; procedure Remove(Index: Integer); dispid 3; procedure Clear; dispid 4;

function Add(Item: OleVariant): Integer; dispid 5; function _NewEnum: IUnknown; dispid -4;

end;

В отличие от обычного интерфейсного класса класс Автоматизации не может иметь родительского класса, и поэтому за словом dispinterface нельзя указать список родителей. Идентификаторы методов (свойств) должны быть уникальными в пределах объявления класса. Все возвращаемые функциями и свойствами результаты, а также все параметры обращения к методам должны иметь один из следующих типов: Byte, Currency, Real, Double, Longint, Integer, Single, Smallint, AnsiString, WideString, TDateTime, Variant, OleVariant, WordBool или любой интерфейсный тип. За исключением директивы default, которую можно указать для свойства-массива, никакие другие директивы доступа в объявлении методов и свойств не допускаются.

Если интерфейс предполагается использовать в технологиях COM/DCOM или CORBA, его методы должны описываться с директивой stdcall или (для объектов Автоматизации) safecall.

Для доступа к объектам Автоматизации используются переменные типа вариант. Инициация такой переменной осуществляется вызовом функции CreateOleObject, определенной в модуле ComObj. Эта функция возвращает ссылку на интерфейс IDispatch, с помощью которой можно обращаться к методам и свойствам класса Автоматизации так, как если бы они были методами и свойствами варианта. Например:

Uses ComObj; var

Word: Variant; begin

Word := CreateOleObject('Word.Basic'); Word.FileNew('Normal'); Word.Insert('Первая строка'#13); Word.Insert('Вторая строка ' #13); Word.FileSaveAs('c:\temp\test.txt', 3)

end;

Параметром обращения к CreateOleObject является имя сервера Автоматизации, которое должно быть предварительно зарегистрировано в реестре Windows 32. Характерно, что в данном случае используется позднее связывание, и методы сервера не известны на этапе компиляции программы. Поэтому компилятор никак не контролирует правильность их вызовов. Названия методов не подчиняются правилам построения идентификаторов Delphi, и в них могут использоваться символы национальных алфавитов.

Передаваемые методам параметры могут быть позиционными и именованными. Позиционные параметры являются обычными для подпрограмм Object Pascal параметрами- значениями. Именованные параметры записываются в виде

Имя_параметра := Значение

Например, при обращении к методу FileSaveAs (см. выше) были использованы два позиционных параметра. Это же обращение можно было бы записать с использованием именованных параметров следующим образом:

Word.FileSaveAs(Format := 3, Name := 'c:\temp\test.txt');

4

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

В документации корпорации Microsoft подробно описаны все свойства и методы соответствующих серверов СОМ (редактора и документа Word, программы работы с электронными таблицами Excel и пр.). Справочная система по всем методам и свойствам объектов MS Office входит в стандартную поставку этого продукта, хотя и не инсталлируется по умолчанию. В каталоге, где установлены программы MS Office, надо поискать файлы .HLP, начинающиеся с префикса VBA. Если их нет, придется выполнить пользовательскую установку пакета MS Office. Например, описание объекта COM Microsoft Excel 97 хранится в файле vbaxl8.hlp.

С помощью Редактора библиотеки интерфейсов можно получить описание всех свойств и методов соответствующих объектов на языке Паскаль. В состав системы Delphi 7 входят 20 файлов, сгенерированных автоматически и содержащих подробные описания всех интерфейсов для продуктов MS Office 97/2000. Эти файлы расположены в каталоге \Delphi 7\Ocx\Servers.

Более предпочтительным вариантом для доступа к серверам Автоматизации является использование механизмов раннего связывания (на этапе компиляции) методов и свойств с сервером СОМ. Когда конкретные типы используемых методов и списки их аргументов известны заранее, возникает возможность избежать ошибок преобразования типов и множества других неточностей.

Раннее связывание выполняется прямым обращением к таблицам виртуальных функций (Vtable). Эта технология создавалась специально для использования раз- работчиками встроенного во все продукты MS Office языка программирования Visual Basic for Applications. Она реализована, начиная с 5-й версии системы Delphi.

На панели Services (Службы) расположены готовые компоненты, позволяющие быстро и легко обращаться к наиболее популярным офисным приложениям Microsoft, а также к созданным с их помощью документам, как к серверам автоматизации СОМ.

Все эти компоненты являются наследниками класса TOleServer: type

TOleServer = class(TComponent, IUnknown);

Он сочетает в себе свойства обычного компонента Delphi и возможность организации доступа через интерфейс IUnknown.

3. Методика выполнения

3.1. В качестве примера использования из программы на Delphi редактора Word решите следующее задачу: загрузить редактор MS Word, открыть в нем документ c:\doc\MyDoc.doc, ввести в него строку 'Документ для печати', затем распечатать документ, сохранить его под именем dd.doc и закрыть редактор.

Создайте новый проект, разместите на нем компонент TWordApplication, ответствен- ный за запуск редактора, и компонент TWordDocument, ответственный за обработку конкретного документа. Добавьте кнопку Button1, по щелчку на которой будут выполнены описанные действия.

Свойствам AutoConnect (автоматический запуск сервера СОМ, в нашем случае редак- тора Word) и AutoQuit (автоматическое завершение работы сервера СОМ) компонента TWordApplication надо присвоить значение True. В свойстве ConnectKind (вид связи с сервером) остается значение по умолчанию ckRunningQrNew. Оно означает, что используется уже запущенный сервер СОМ (копия редактора). В противном случае автоматически запускается новый:

procedure TForm1.Button1Click(Sender: TObject);