КАТЕГОРИИ:
АстрономияБиологияГеографияДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРиторикаСоциологияСпортСтроительствоТехнологияФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника
|
Создание самой коллекцииСоздание элемента коллекции полностью закончено. Возвращаемся в раздел interface и сразу же после объявления класса TSpot пишем две следующие строки. TDappledShape = class; TItemChangeEvent = procedure(Item: TCollectionItem) of object; Первая строка - это так называемое опережающее объявление класса. При вставке коллекции в компонент этот прием является стандартным и позволяет использовать еще не объявленный класс самого компонента в объявлении класса коллекции (что, в свою очередь, дает возможность реализовать метод GetOwner). Вторая строка определяет так называемый тип обработчика события. Наше событие будет означать, что произошло какое-то изменение элемента коллекции (параметр Item). Собственно говоря, введение такого события совсем не обязательно и сделано лишь с целью иллюстрации. Теперь мы можем объявить класс самой коллекции. TSpotCollection = class(TCollection) private FDappledShape: TDappledShape; FOnItemChange : TItemChangeEvent; protected function GetOwner: TPersistent; override; procedure Update(Item: TCollectionItem); override; procedure DoItemChange(Item: TCollectionItem); dynamic; public constructor Create(DappledShape: TDappledShape); function Add: TSpot; property Items[Index: Integer]: TSpot read GetItem write SetItem; default; published property OnItemChange: TItemChangeEvent read FOnItemChange write FOnItemChange; end; Если не учитывать добавленное нами событие (поле FOnItemChange, метод DoItemChange и свойство OnItemChange), то можно сказать, что такое объявление коллекции является практически стандартным. Описатель default для свойства Items здесь имеет иной смысл, чем ранее. Он означает, что само свойство Items является «свойством по умолчанию» - то есть, что, если в программе объявлена, например, переменная MySpotCollection: TSpotCollection, то синтаксические конструкции MySpotCollection[i] и MySpotCollection.Items[i] будут эквивалентны. Теперь поступаем так же, как и прежде - ставим курсор куда-то внутрь этого объявления, нажимаем Ctrl+Shift+C, получаем скелет реализации и дописываем код. Обратите внимание, что и в этом случае Delphi добавляет в раздел private два метода доступа - GetItem (чтение) и SetItem (запись), которые мы ввели при объявлении свойства Items. Однако коллекции требуют, чтобы эти два метода были доступны классам-потомкам и поэтому они должны быть объявлены в разделе protected, куда нам и следует их перенести вручную. В итоге получим следующее. function TSpotCollection.Add: TSpot; begin // Получаем общий TCollectionItem и приводим его к нашему TSpot Result := TSpot(inherited Add) end; constructor TSpotCollection.Create(DappledShape: TDappledShape); begin // Создаем коллекцию и запоминаем ссылку на ее владельца inherited Create(TSpot); FDappledShape := DappledShape end; procedure TSpotCollection.DoItemChange(Item: TCollectionItem); begin // Стандартный вызов пользовательского обработчика события if Assigned(FOnItemChange) then FOnItemChange(Item) end; function TSpotCollection.GetItem(Index: Integer): TSpot; begin // Получаем общий TCollectionItem и приводим его к нашему TSpot Result := TSpot(inherited GetItem(Index)) end; function TSpotCollection.GetOwner: TPersistent; begin // Возвращаем ранее запомненную ссылку на владельца коллекции Result := FDappledShape end; procedure TSpotCollection.SetItem(Index: Integer; const Value: TSpot); begin // Просто используем унаследованный метод записи inherited SetItem(Index, Value) end; procedure TSpotCollection.Update(Item: TCollectionItem); begin // Вызов унаследованного метода здесь лишний, но это грамотный стиль. Он // гарантирует верную работу даже при изменениях в новых версиях Delphi. inherited Update(Item); // Даем запрос на перерисовку компонента-владельца FDappledShape.Invalidate; // Возбуждаем событие - сигнал об изменении элемента DoItemChange(Item) end; Практически весь приведенный код реализации коллекции можно рассматривать, как совершенно стандартный и использовать его аналог чуть ли не для всех коллекций. Как видим, замещение методов класса-предка нужно, в общем-то, лишь для поддержки работы с конкретными используемыми классами элемента коллекции и ее владельца. Замещение метода Update позволяет обновить компонент-владелец при изменении любого элемента коллекции (а также при их добавлении к коллекции и удалении из нее). Использованный в данном примере способ обновления не является оптимальным (поскольку при изменении всего лишь одного элемента перерисовывается весь компонент) и выбран лишь из-за своей простоты. В том же методе Update возбуждается введенное нами событие. При этом пользовательский обработчик вызывается не напрямую, а через так называемый метод диспетчеризации события - в данном случае, DoItemChange. Это стандартный подход. Он позволяет потомкам класса заместить метод диспетчеризации и, таким образом, встроить в цепочку обработки события свой код, не затрагивая никаких других аспектов. Но такая необходимость возникает все же достаточно редко и потому, с целью некоторой экономии памяти, методы диспетчеризации событий практически всегда объявляются, как динамические, а не виртуальные. Итак, коллекция создана. Но для того, чтобы использовать ее по назначению, нужно сначала «вживить» ее в компонент.
|