Конференция "Компоненты" » Как правильно решить следуещее...
 
  • Handbrake (26.08.10 16:34) [0]
    Вот такой код:


    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;

    { TAntiqueItem }

    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;

    { TAntiqueCollection }

    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;

    { TMarket }

    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;

    { TCollector }

    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 возможностью выбора. После установки свойства - еще и с "+" справа.

    Помогите разобраться: подход-то хоть правильный??? Или так вообще не делают ???

    Заранее признателен за внимание!
  • DimaBr © (27.08.10 08:48) [1]
    Первое о чём следует задуматься, это как это свойство будет хранится в ресурсе формы. Предлагаю хранить в виде индекса элемента 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;

  • Handbrake (27.08.10 10:54) [2]
    To DimaBr

    Спасибо, идея понятна.

    А что тогда с раскрывающимся списком в ObjectInspector при выборе Collector.Desired ???
  • Handbrake (27.08.10 12:05) [3]
    Написал для свойства Desired


     TDesiredPropEditor = class(TClassProperty)
     public
       function GetAttributes: TPropertyAttributes; override;
       procedure GetValues(Proc:TGetStrProc); override;
       procedure SetValue(const Value: string); override;
     end;

     

    и зарегистрировал его. Вроде бы все работает...

    Как такое решение???
  • DimaBr © (27.08.10 12:31) [4]
    Для него (свойства) нужно зарегистрировать редактор по аналогии с 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;

  • DimaBr © (27.08.10 12:32) [5]
    Во, пока писал, вы уже и сами ответ нашли ! Это хорошо !
  • Handbrake (27.08.10 14:02) [6]
    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;

    { DesiredPropEditor }

    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;

     // Нет никакой надежды, что AntiqueCollection[i].DisplayName не будет пусто !!!
     // Потому формирую "суррогатные" имена.

     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;
    //  ShowMessage('Notify');
    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 меняешь местами элементы (опять индексы едут) ???
  • DimaBr © (27.08.10 14:35) [7]
    Тогда храните вместе с индексом (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;

  • Handbrake (27.08.10 15:03) [8]
    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( Такой "техник"  ) ???
  • DimaBr © (27.08.10 15:51) [9]
    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;



    А вообще, лучше всё же перехватить нотификацию удаления элемента коллекции и обнулить ссылку
  • Handbrake (27.08.10 16:14) [10]
    Меня в [9] вот это напрягает:


    if Assigned(Market) and Assigned(FDesiredItem) and (FDesiredIndex>=0)


    При загрузки формы блок then не сработает, со всеми вытекающими.
  • DimaBr © (27.08.10 16:32) [11]
    Напрягает не это, а то что FDesiredItem указывает на удалённый итем.
  • Handbrake (27.08.10 16:53) [12]

    > А вообще, лучше всё же перехватить нотификацию удаления
    > элемента коллекции и обнулить ссылку
    >


    Где перехватить понятно. А вот как красиво обнулить? Держать список (в Market"е ) подписавшихся на Market Collector"ов?
  • DimaBr © (30.08.10 08:44) [13]
    Очень просто, если удаляемый итем выбран, значит обнуляем
  • Handbrake (31.08.10 08:06) [14]
    To DimaBr ©
     
     Спасибо за помощь! Дальше сам буду пробовать.
  • имя (17.03.11 02:49) [15]
    Удалено модератором
 
Конференция "Компоненты" » Как правильно решить следуещее...
Есть новые Нет новых   [119159   +58][b:0][p:0.008]