Использование Microsoft ScriptControl
Оператор For Each
Удобным средством, предоставляемым VBScript, является оператор For Each, организующий цикл по всем элементам заданной коллекции. Добавим поддержку этого оператора в наш компонент.
Интерфейс IEnumVariant
Реализация For Each предусматривает следующее:
- Исполняющее ядро ScriptControl вызывает метод Invoke объекта, по элементам которого должен производиться цикл с DispID = DISPID_NEWENUM (-4).
- Объект должен вернуть интерфейс IenumVariant.
- Далее ядро использует методы IEnumVariant для получения элементов коллекции.
Интерфейс IEnumVariant определен как:
IEnumVariant = (IUnknown) ['{00020404-0000-0000-C000-000000000046}'] Next(celt: LongWord; rgvar: OleVariant; pceltFetched: PLongWord): HResult; ; Skip(celt: LongWord): HResult; ; Reset: HResult; ; Clone(out Enum: IEnumVariant): HResult; ; ;
В модуле ActiveX.pas в оригинальной поставке Delphi5 ошибочно определен метод Next
Next(celt: LongWord; rgvar: OleVariant; pceltFetched: LongWord): HResult; ;
поэтому для корректной реализации интерфейс должен быть переопределен.
Класс TVCLEnumerator
Создадим класс, инкапсулирующий функциональность IEnumVariant.
TVCLEnumerator = (TInterfacedObject, IEnumVariant) FEnumPosition: Integer; FOwner: TPersistent; FScriptControl: TVCLScriptControl; { IEnumVariant } Next(celt: LongWord; rgvar: OleVariant; pceltFetched: PLongWord): HResult; ; Skip(celt: LongWord): HResult; ; Reset: HResult; ; Clone(Enum: IEnumVariant): HResult; ; Create(AOwner: TPersistent; AScriptControl: TVCLScriptControl); ;
Конструктор устанавливает свойства FOwner и FScriptControl.
TVCLEnumerator.Create(AOwner: TPersistent; AScriptControl: TVCLScriptControl); Create; FOwner := AOwner; FScriptControl := AScriptControl; FEnumPosition := 0; ;
Метод Reset подготавливает реализацию интерфейса к началу перебора.
TVCLEnumerator.Reset: HResult; FEnumPosition := 0; Result := S_OK; ;
Главная функциональность сосредоточена в методе Next, который получает следующие переменные:
- celt – количество запрашиваемых элементов;
- rgvar – адрес первого элемента массива переменных типа OleVariant;
- pceltFetched – адрес переменной, в которую должно быть записано количество реально переданных элементов. Этот адрес может быть равен NIL, в этом случае не потребуется ничего записывать.
Метод должен заполнить запрошенное количество элементов rgvar и вернуть S_OK, если это удалось, и S_FALSE, если элементов не хватило.
TVariantList = [0..0] OleVariant;
TVCLEnumerator.Next(celt: LongWord; rgvar: OleVariant; pceltFetched: PLongWord): HResult; I: Cardinal; Result := S_OK; I := 0;
Для объекта TWinControl возвращаем интерфейсы IDispatch для компонентов из свойства Controls.
FOwner TWinControl TWinControl(FOwner) (FEnumPosition < ControlCount) (I < celt) TVariantList(rgvar)[I] := FScriptControl.GetProxy(Controls[FEnumPosition]); Inc(I); Inc(FEnumPosition); ; ;
Для TCollection организуется перебор элементов коллекции.
FOwner TCollection TCollection(FOwner) (FEnumPosition < Count) (I < celt) TVariantList(rgvar)[I] := FScriptControl.GetProxy(Items[FEnumPosition]); Inc(I); Inc(FEnumPosition); ; ;
Для TStrings перебираются строки и возвращаются их значения.
FOwner TStrings TStrings(FOwner) (FEnumPosition < Count) (I < celt) TVariantList(rgvar)[I] := TStrings(FOwner)[FEnumPosition]; Inc(I); Inc(FEnumPosition); ; ; Result := S_FALSE; I <> celt Result := S_FALSE; Assigned(pceltFetched) pceltFetched^ := I; ;
Метод Skip пропускает запрошенное количество элементов и возвращает S_OK, если еще остались элементы для перебора.
TVCLEnumerator.Skip(celt: LongWord): HResult; Total: Integer; Result := S_FALSE; FOwner TWinControl Total := TWinControl(FOwner).ControlCount FOwner TCollection Total := TCollection(FOwner).Count FOwner TStrings Total := TStrings(FOwner).Count Exit; FEnumPosition + celt <= Total Result := S_OK; Inc(FEnumPosition, celt) ; ;
Метод Clone клонирует объект, возвращая интерфейс его копии.
TVCLEnumerator.Clone( Enum: IEnumVariant): HResult; NewEnum: TVCLEnumerator; NewEnum := TVCLEnumerator.Create(FOwner, FScriptControl); NewEnum.FEnumPosition := FEnumPosition; Enum := NewEnum IEnumVariant; Result := S_OK; ;
Для того чтобы класс TVCLProxy мог вернуть интерфейс IEnumVariant, требуется дополнить метод Invoke следующим кодом:
DispId DISPID_NEWENUM: // У объекта запрашивают интерфейс IEnumVariant для ForEach // создаем класс, реализующий этот интерфейс OleVariant(VarResult^) := TVCLEnumerator.Create(FOwner, FScriptControl) IEnumVariant; ;
Компонент TVCLScriptControl
Текст этого компонента приведен на CD-ROM. Данный компонент является наследником TScriptControl и реализует функциональность по работе с TVCLProxy.
Заключение
Microsoft ScriptControl – качественное решение для задач, требующих включения в программу интерпретирующего ядра. Интегрировав его с VCL, мы получаем мощный и гибкий инструмент, позволяющий наращивать возможности в любом направлении. Приведенной в данной статье информации вполне достаточно, чтобы на основе помещенного на компакт-диске компонента TVCLScriptControl создать решение, удовлетворяющее любой конкретной задаче.
КомпьютерПресс 7'2001