-
Вот такой код:
unit Problem;
interface
Uses
Classes;
Type
TAntiqueItem = class(TCollectionItem)
private
FDescription: string;
FdisplayName: string;
procedure SetDescription(const Value: string);
protected
function GetDisplayName: string;override;
procedure SetDisplayName(const Value: string); override;
published
property DisplayName;
property Description:string read FDescription write SetDescription;
end;
TAntiqueItemClass = class(TAntiqueItem);
TAntiqueCollection = class(TCollection)
private
function GetItem(index:integer): TAntiqueItem;
procedure SetItem(index:integer; const Value: TAntiqueItem);
public
function Add:TAntiqueItem;
property Items[index:integer]:TAntiqueItem read GetItem write SetItem;default;
end;
TMarket=class(TComponent)
private
FAntique: TAntiqueCollection;
procedure SetAntique(const Value: TAntiqueCollection);
public
constructor Create(AOwner: TComponent);override;
destructor Destroy;override;
published
property Antique : TAntiqueCollection read FAntique write SetAntique;
end;
TCollector=class(TComponent)
private
FDesired: TAntiqueItem;
FMarket: TMarket;
procedure SetDesired(const Value: TAntiqueItem);
procedure SetMarket(const Value: TMarket);
published
property Market:TMarket read FMarket write SetMarket;
property Desired:TAntiqueItem read FDesired write SetDesired;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Problem',[ TMarket, TCollector ]);
end;
function TAntiqueItem.GetDisplayName: string;
begin
result := FDisplayName;
end;
procedure TAntiqueItem.SetDescription(const Value: string);
begin
FDescription := Value;
end;
procedure TAntiqueItem.SetDisplayName(const Value: string);
begin
inherited;
if Value <> ''
then FDisplayName := Value
else FDisplayName := 'Unnamed';
end;
function TAntiqueCollection.Add: TAntiqueItem;
begin
result := TAntiqueItem(inherited Add);
result.SetDisplayName('');
end;
function TAntiqueCollection.GetItem(index:integer): TAntiqueItem;
begin
result := TAntiqueItem(inherited GetItem(index));
end;
procedure TAntiqueCollection.SetItem(index:integer; const Value: TAntiqueItem);
begin
inherited SetItem(Index, TAntiqueItem( Value ));
end;
constructor TMarket.Create(AOwner: TComponent);
begin
inherited;
FAntique := TAntiqueCollection.Create( TAntiqueItemClass );
end;
destructor TMarket.Destroy;
begin
FAntique.Free;
inherited;
end;
procedure TMarket.SetAntique(const Value: TAntiqueCollection);
begin
FAntique := Value;
end;
procedure TCollector.SetDesired(const Value: TAntiqueItem);
begin
FDesired := Value;
end;
procedure TCollector.SetMarket(const Value: TMarket);
begin
FMarket := Value;
end;
end.
Кидаем на форму экземпляры классов TMarket и TCollector. (Market1 & Collector1) Свойство Collector1.Market в ObjectInspector обрабатывается IDE по-умолчанию идеально, т.е. пока не определено - раскрывающийся список, если установлено - с "+" справа. Все Ок! Теперь, собственно, о Collector1.Desired. Посыл такой: Хочется, что бы при установленном свойстве TCollector.Market свойство TCollector.Desired обрабатывалось в ObjectInspector подобно TCollector.Market. Т.е. раскрывающийся список всех элементов коллекции конкретного Маркета c возможностью выбора. После установки свойства - еще и с "+" справа. Помогите разобраться: подход-то хоть правильный??? Или так вообще не делают ??? Заранее признателен за внимание!
-
Первое о чём следует задуматься, это как это свойство будет хранится в ресурсе формы. Предлагаю хранить в виде индекса элемента Antique : TAntiqueCollection. Тогда
TCollector=class(TComponent)
private
FDesiredIndex: integer;
FMarket: TMarket;
procedure SetDesired(const Value: TAntiqueItem);
function GetDesired : TAntiqueItem;
procedure SetMarket(const Value: TMarket);
published
property Market:TMarket read FMarket write SetMarket;
property Desired:TAntiqueItem read GetDesired write SetDesired stored false;
end;
constructor TCollector.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FDesiredIndex := -1;
end;
function TCollector.GetDesired: TAntiqueItem
begin
if Assigned(Market) and (FDesiredIndex>=0)
then Result := Market.Antique[FDesiredIndex]
else Result := nil;
end;
procedure TCollector.SetDesired(const Value: TAntiqueItem);
begin
if Assigned(Value) and Assigned(Market)
then FDesiredIndex := Market.IndexOf(Value)
else FDesiredIndex := -1;
end;
Далее, нужно сохратить этот FDesiredIndex в ресурс через DefineProperties
procedure ReadIndex(Reader: TReader);
procedure WriteIndex(Writer: TWriter);
procedure DefineProperties(Filer: TFiler); override;
procedure TCollector.DefineProperties(Filer: TFiler);
begin
Filer.DefineProperty('DesiredIndex', ReadIndex, WriteIndex, FDesiredIndex<>-1);
end;
procedure TCollectorReadIndex(Reader: TReader);
begin
FDesiredIndex := Reader.ReadInteger;
end;
procedure TCollectorWriteIndex(Writer: TWriter)
begin
Writer.WriteInteger(FDesiredIndex)
end;
Ну и осталось проверять при изменении в самой коллекции правильное состояние FDesiredIndex. Ещё. Процедура procedure SetMarket должна выглядеть так procedure TCollector.SetMarket(const Value: TMarket);
begin
if Assigned(FMarket) then FMarket.RemoveFreeNotification(self);
FMarket := Value;
if Assigned(FMarket) then FMarket.FreeNotification(self);
end;
procedure TCollector.Notification(AComponent: TComponent;Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if Operation = opRemove then
if AComponent = Market then Market := nil;
end;
-
To DimaBr
Спасибо, идея понятна.
А что тогда с раскрывающимся списком в ObjectInspector при выборе Collector.Desired ???
-
Написал для свойства Desired
TDesiredPropEditor = class(TClassProperty)
public
function GetAttributes: TPropertyAttributes; override;
procedure GetValues(Proc:TGetStrProc); override;
procedure SetValue(const Value: string); override;
end;
и зарегистрировал его. Вроде бы все работает... Как такое решение???
-
Для него (свойства) нужно зарегистрировать редактор по аналогии с DesignEditors.TComponentProperty (писал налету, так что могут быть небольшие неточности)
TDesiredProperty = class(TPropertyEditor)
protected
function GetAttributes: TPropertyAttributes; override;
procedure GetProperties(Proc: TGetPropProc); override;
function GetValue: string; override;
procedure GetValues(Proc: TGetStrProc); override;
procedure SetValue(const Value: string); override;
function Collector: TCollector;
function FilterFunc(const ATestEditor: IProperty): Boolean;
end;
function TDesiredProperty.Collector: TCollector;
begin
Result := GetComponent(0) as TCollector;
end;
function TDesiredProperty.GetAttributes: TPropertyAttributes;
begin
Result := [paMultiSelect,paValueList, paSortList, paRevertable];
if Assigned(Collector.Desired) then
Result := Result + [paSubProperties, paVolatileSubProperties];
end;
procedure TDesiredProperty.GetProperties(Proc: TGetPropProc);
var LComponents: IDesignerSelections;
LDesigner: IDesigner;
begin
LComponents := GetSelections;
if LComponents <> nil then
begin
if not Supports(FindRootDesigner(LComponents[0]), IDesigner, LDesigner) then
LDesigner := Designer;
GetComponentProperties(LComponents, tkAny, LDesigner, Proc, FilterFunc);
end;
end;
function TDesiredProperty.GetValue: string;
begin
if Assigned(Collector.Desired)
then Result := Collector.Desired.Description
else Result := '';
end;
procedure TDesiredProperty.GetValues(Proc: TGetStrProc);
begin
if Assigned(Collector.Market) then
for i := 0 to Collector.Market.Antique.Count-1 do
Proc(Collector.Market.Antiqu[i].Description);
end;
procedure TDesiredProperty.SetValue(const Value: string);
var Item: TAntiqueItem;
i: integer;
begin
Item := nil;
if (Value <> '') and Assigned(Collector.Market) then
for i := 0 to Collector.Market.Antique.Count-1 do
if SameText(Collector.Market.Antiqu[i].Description,Value) then begin
Item := Collector.Market.Antiqu[i];
Break;
end;
Collector.Desired := Item;
end;
function TDesiredProperty.FilterFunc(const ATestEditor: IProperty): Boolean;
begin
Result := not (paNotNestable in ATestEditor.GetAttributes);
end;
procedure Register;
begin
RegisterPropertyEditor(TypeInfo(TAntiqueItem), TCollector, 'Desired', TDesiredProperty);
end;
-
Во, пока писал, вы уже и сами ответ нашли ! Это хорошо !
-
To DimaBr © Все равно, спасибо за [4] ! Я сравниваю со своим кодом и покрываюсь испариной:). Тут вот еще какая штука. Вы опираетесь на то, что AntiqueItem.DisplayName (в Вашем коде Description, не суть - мы понимаем друг друга) не пусто и уникально внутри коллекции. Хорошо подумав, я понял, что в рамках мой задачи это даже хорошо. Но все же, для универсальности, попробую так:
unit DesiredEditor;
interface
Uses
SysUtils,Classes, DesignEditors, DesignIntf, Dialogs;
Type
TDesiredPropEditor = class(TClassProperty)
public
function GetAttributes: TPropertyAttributes; override;
function GetValue: string; override;
procedure GetValues(Proc:TGetStrProc); override;
procedure SetValue(const Value: string); override;
end;
procedure Register;
implementation
Uses
Problem;
procedure Register;
begin
RegisterPropertyEditor( TypeInfo( TAntiqueItem ), TCollector, 'Desired', TDesiredPropEditor );
end;
function TDesiredPropEditor.GetAttributes: TPropertyAttributes;
begin
result := [ paValueList, paSubProperties, paAutoUpdate, paVolatileSubProperties ];
end;
function TDesiredPropEditor.GetValue: string;
begin
if Assigned( TCollector( GetComponent(0) ).Desired )
then Result := '[' +
inttostr( TCollector( GetComponent(0) ).Desired.Index )+
'] '+TCollector( GetComponent(0) ).Desired.DisplayName
else Result := '';
end;
procedure TDesiredPropEditor.GetValues(Proc: TGetStrProc);
Var
Collector:TCollector;
Market:TMarket;
AntiqueCollection:TAntiqueCollection;
I:integer;
begin
Collector := TCollector( GetComponent(0) );
Market := Collector.Market;
if Market = nil then exit;
AntiqueCollection := Market.Antique;
for I := 0 to AntiqueCollection.Count - 1 do
Proc( '[' +inttostr( i )+ '] ' + AntiqueCollection[i].DisplayName );
end;
procedure TDesiredPropEditor.SetValue(const Value: string);
Var
Collector:TCollector;
Market:TMarket;
AntiqueCollection:TAntiqueCollection;
I:integer;
Index:integer;
_Index:string;
Ai:TAntiqueItem;
begin
Collector := TCollector( GetComponent(0) );
if Value = ''
then
begin
SetOrdValue(0);
Modified;
exit;
end;
Market := Collector.Market;
if Market = nil then exit;
AntiqueCollection := Market.Antique;
Index := -1;
_Index:= '';
for I := 2 to length( Value ) do
if Value[ I ] in ['0'..'9']
then _Index := _Index + Value[i]
else Break;
Index := StrToInt( _Index );
Ai := TAntiqueItem( AntiqueCollection[ Index ] );
SetOrdValue( Longint( Ai ) );
Modified;
end;
end.
Осталось решить проблему модификации коллекции. Смотрю на TCollection.Notify(Item: TCollectionItem; Action: TCollectionNotification);virtual; надо бы ее Override:
procedure TAntiqueCollection.Notify(Item: TCollectionItem;
Action: TCollectionNotification);
begin
inherited;
case Action of
cnAdded :begin
end;
cnExtracting :begin
end;
cnDeleting :begin
end;
End;
end;
Читаю в справке The following table lists the values for TCollectionNotification: Value Meaning cnAdded An item was just added to the collection. cnExtracting An item is about to be removed from the collection (but not freed). cnDeleting An item is about to be removed from the collection and then freed. cnAdded - понятно, а вот остальное не очень. И плюс еще, что делать, когда в CollectionEditor меняешь местами элементы (опять индексы едут) ???
-
Тогда храните вместе с индексом (FDesiredIndex) ещё и указатель на элемент коллекции.
TCollector=class(TComponent)
private
FDesiredIndex: integer;
FDesiredItem: TAntiqueItem;
function TCollector.GetDesired: TAntiqueItem;
begin
Result := nil;
if Assigned(Market) and Assigned(FDesiredItem) then begin
FDesiredIndex := FDesiredItem.Index;
Result := FDesiredItem;
end;
end;
procedure TCollector.SetDesired(const Value: TAntiqueItem);
begin
if Assigned(Value) and Assigned(Market) then begin
FDesiredIndex := Value.Index;
FDesiredItem := Value;
end
else begin
FDesiredIndex := -1;
FDesiredItem := nil;
end;
end;
-
To DimaBr © Круть, Cпасибо! Еще чуть подправил и все утряслось:
function TCollector.GetDesired: TAntiqueItem;
begin
Result := nil;
if Assigned(Market) and Assigned(FDesiredItem) and (FDesiredIndex>=0)
then
begin
FDesiredIndex := FDesiredItem.Index;
Result := FDesiredItem;
end;
end;
procedure TCollector.SetDesired(const Value: TAntiqueItem);
begin
if Assigned(Value) and Assigned(Market)
then
begin
FDesiredIndex := Value.Index;
FDesiredItem := Value;
end
else
begin
FDesiredIndex := -1;
FDesiredItem := nil;
end;
end;
Плюс
procedure TCollector.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if Operation = opRemove
then if AComponent = Market
then
begin
Market := nil;
FDesiredIndex := -1;
FDesiredItem := nil;
end;
end;
procedure TCollector.SetMarket(const Value: TMarket);
begin
if Assigned(FMarket)
then FMarket.RemoveFreeNotification(Self);
FMarket := Value;
if Assigned(FMarket)
then FMarket.FreeNotification(self)
else
begin
FDesiredIndex := -1;
FDesiredItem := nil;
end;
end;
Ну, и какое резюме? IsValid( Такой "техник" ) ???
-
GetDesired нужно переписать, на случай если выбранный элемент удалили
function TCollector.GetDesired: TAntiqueItem;
var i: integer;
begin
Result := nil;
if Assigned(Market) and Assigned(FDesiredItem) and (FDesiredIndex>=0) then
for i := 0 to Market.Antique.Count-1 do
if Market.Antique[i] = FDesiredItem then begin
FDesiredIndex := FDesiredItem.Index;
Result := FDesiredItem;
Break;
end;
end; А вообще, лучше всё же перехватить нотификацию удаления элемента коллекции и обнулить ссылку
-
Меня в [9] вот это напрягает:
if Assigned(Market) and Assigned(FDesiredItem) and (FDesiredIndex>=0)
При загрузки формы блок then не сработает, со всеми вытекающими.
-
Напрягает не это, а то что FDesiredItem указывает на удалённый итем.
-
> А вообще, лучше всё же перехватить нотификацию удаления > элемента коллекции и обнулить ссылку >
Где перехватить понятно. А вот как красиво обнулить? Держать список (в Market"е ) подписавшихся на Market Collector"ов?
-
Очень просто, если удаляемый итем выбран, значит обнуляем
-
To DimaBr © Спасибо за помощь! Дальше сам буду пробовать.
-
Удалено модератором
|