Delphi中TOwnedCollection与Owner类之间的通信

时间:2015-08-08 17:38:34

标签: delphi components

我有自己的组件(TNiftyRVFrameWithPopups),并将TOwnedCollection作为属性(TagList)。

每次我向TagList添加项目时,应将同一项目添加到另一个对象(FMenu)。这是由RefreshMenu在设计时调用的TNiftyRVFrameWithPopups.Loaded程序执行的。

我的问题是我无法在运行时添加项目,因为TNiftyRVFrameWithPopups.Loaded未被调用。

我认为一个解决方案是Postmessage,但我没有设法让它发挥作用。

以下是来源:

  TNiftyListTag = class(TCollectionItem)
  private
    FTagValue: string;
    FDisplayTextTag: string;
  public
    procedure Assign(Source: TPersistent); override;
  published
    property DisplayTag: string read FDisplayTextTag write FDisplayTextTag;
    property Value: string read FTagValue write FTagValue;
  end;

  TNiftyListTags = class(TOwnedCollection)
  protected
    function GetItem(Index: Integer): TNiftyListTag;
    procedure SetItem(Index: Integer; Value: TNiftyListTag);
  public
    constructor Create(AOwner: TPersistent; ItemClass: TCollectionItemClass);
    function Add: TNiftyListTag;
  end;

  TNiftyRVFrameWithPopups = class(TRVEditFrame)
  private
    FMenu: TAdvSmoothListBox;
    FMenuList: TStringList;
    FCollectionTags: TNiftyListTags;
    procedure SetCollectionTags(const Value: TNiftyListTags);
    procedure RefreshMenu;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    procedure Loaded; override;
  published
    property TagList: TNiftyListTags read FCollectionTags write SetCollectionTags;
  end;

implementation


constructor TNiftyRVFrameWithPopups.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FMenuList := TStringList.Create;
  FCollectionTags := TNiftyListTags.Create(Self, TNiftyListTag);
end;

procedure TNiftyRVFrameWithPopups.SetCollectionTags(const Value: TNiftyListTags);
begin
  FCollectionTags.Assign(Value);
end;

procedure TNiftyRVFrameWithPopups.RefreshMenu;
var
  i: Integer;
begin
    FMenu.Items.Clear;
    for i := 0 to FCollectionTags.Count - 1 do
    begin
      FMenu.Items.Add;
      FMenu.Items.Items[i].Caption := FCollectionTags.Items[i].FDisplayTextTag;
    end;
end;

procedure TNiftyRVFrameWithPopups.Loaded;
begin
  inherited Loaded;
  if Assigned(FRVEditor) then
  begin
    RefreshMenu;
  end;
end;


{ TNiftyListTag }

procedure TNiftyListTag.Assign(Source: TPersistent);
begin
  if Source is TNiftyListTag then
  begin
    FTagValue := TNiftyListTag(Source).FTagValue;
    FDisplayTextTag := TNiftyListTag(Source).FDisplayTextTag;
  end
  else
    inherited;
end;

{ TNiftyListTags }

function TNiftyListTags.Add: TNiftyListTag;
begin
  Result := TNiftyListTag(inherited Add);
end;

constructor TNiftyListTags.Create(AOwner: TPersistent; ItemClass: TCollectionItemClass);
begin
  inherited Create(AOwner, ItemClass);
end;

procedure TNiftyListTags.SetItem(Index: Integer; Value: TNiftyListTag);
begin
  inherited SetItem(index, Value);
end;

function TNiftyListTags.GetItem(Index: Integer): TNiftyListTag;
begin
  Result := TNiftyListTag(inherited GetItem(Index));
end;

修改

根据Deltics的建议,我修改了我的代码:

    TNiftyListTag = class(TCollectionItem)
      private
        FTagValue: string;
        FDisplayTextTag: string;
      public
        procedure Assign(Source: TPersistent); override;
      published
        property DisplayTag: string read FDisplayTextTag write FDisplayTextTag;
        property Value: string read FTagValue write FTagValue;
      end;

      TNiftyListTags = class(TOwnedCollection)
      private
        fOnChanged: TNotifyEvent;
        procedure DoOnChanged;
      protected
        function GetItem(Index: Integer): TNiftyListTag;
        procedure SetItem(Index: Integer; Value: TNiftyListTag);
      public
        constructor Create(AOwner: TPersistent; ItemClass: TCollectionItemClass);
        function Add: TNiftyListTag;
        procedure AppendItem(const aDisplayText, aTag: string);
      end;

      TNiftyRVFrameWithPopups = class(TRVEditFrame)
      private
        FMenu: TAdvSmoothListBox;
        FMenuList: TStringList;
        FCollectionTags: TNiftyListTags;
        procedure RefreshMenu;
        procedure SetCollectionTags(const Value: TNiftyListTags);
      public
        { Public declarations }
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
        procedure Loaded; override;
      published
        property TagList: TNiftyListTags read FCollectionTags write SetCollectionTags;
      end;

    implementation


    constructor TNiftyRVFrameWithPopups.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);
      FMenuList := TStringList.Create;
      FCollectionTags := TNiftyListTags.Create(Self, TNiftyListTag);
      FCollectionTags.fOnChanged := RefreshMenu;
    end;

    destructor TNiftyRVFrameWithPopups.Destroy;
    begin
      FreeAndNil(FMenuList);
      FCollectionTags.Free;
      inherited;
    end;

    procedure TNiftyRVFrameWithPopups.RefreshMenu;
    var
      i: Integer;
    begin
        FMenu.Items.Clear;
        for i := 0 to FCollectionTags.Count - 1 do
        begin
          FMenu.Items.Add;
          FMenu.Items.Items[i].Caption := FCollectionTags.Items[i].FDisplayTextTag;
        end;
    end;

    procedure TNiftyRVFrameWithPopups.Loaded;
    begin
      inherited Loaded;
      RefreshMenu(Self);
    end;

    procedure TNiftyRVFrameWithPopups.RefreshMenu;
    var
      i: Integer;
    begin
      if Assigned(FRVEditor) then
      begin
        (FRVEditor as TCustomRichViewEdit).OnRVMouseUp := OnMouseUp;
        FMenu.Parent := FRVEditor;
        fmenu.Items.Clear;
        for i := 0 to FCollectionTags.Count - 1 do
        begin
          FMenu.Items.Add;
          FMenu.Items.Items[i].Caption := FCollectionTags.Items[i].FDisplayTextTag;
        end;
      end;
    end;

   procedure TNiftyRVFrameWithPopups.SetCollectionTags(const Value: TNiftyListTags);
begin
  FCollectionTags.Assign(Value);
end;


    { TNiftyListTag }

    procedure TNiftyListTag.Assign(Source: TPersistent);
    begin
      if Source is TNiftyListTag then
      begin
        FTagValue := TNiftyListTag(Source).FTagValue;
        FDisplayTextTag := TNiftyListTag(Source).FDisplayTextTag;
      end
      else
        inherited;
    end;

    { TNiftyListTags }

    function TNiftyListTags.Add: TNiftyListTag;
    begin
      Result := TNiftyListTag(inherited Add);
    end;

    procedure TNiftyListTags.AppendItem(const aDisplayText, aTag: string);
    var
      a: TNiftyListTag;
    begin
      a := TNiftyListTag(inherited Add);
      a.FTagValue := aTag;
      a.FDisplayTextTag := aDisplayText;
      DoOnChanged;
    end;

    constructor TNiftyListTags.Create(AOwner: TPersistent; ItemClass: TCollectionItemClass);
    begin
      inherited Create(AOwner, ItemClass);
    end;

    procedure TNiftyListTags.DoOnChanged;
    begin
      if Assigned(fOnChanged) then
        fOnChanged(self);
    end;

    procedure TNiftyListTags.SetItem(Index: Integer; Value: TNiftyListTag);
    begin
      inherited SetItem(index, Value);
    end;

    function TNiftyListTags.GetItem(Index: Integer): TNiftyListTag;
    begin
      Result := TNiftyListTag(inherited GetItem(Index));
    end;

    procedure TNiftyListTags.SetItem(Index: Integer; Value: TNiftyListTag);
    begin
      inherited SetItem(index, Value);
      DoOnChanged;
    end;

end.

可以通过以下方式在运行时添加项目:

var
  a:TNiftyRVFrameWithPopups;
begin
  a:=TNiftyRVFrameWithPopups.Create(self);
.....
  a.TagList.AppendItem('a','b');
  a.TagList.AppendItem('c','d');
end

1 个答案:

答案 0 :(得分:3)

您的 TNiftyListTags TNiftyRVFrameWithPopups 所有。

你唯一的问题'是 TOwnedCollection 类不提供对所有者的类型化引用,通过该引用可以在集合更改时调用必要的方法来刷新所有者。

有很多方法可以达到你想要的效果。但是,在提出选项之前,无论您做什么,我建议您不要调用已加载来实现更新/刷新,因为此方法具有特定含义。虽然重写方法中的代码在此上下文中可能是安全的,但继承的实现可能不是。

我建议将if Assigned(fRVEditor)前置条件检查移至 RefreshMenu 本身。 已加载然后只需调用 RefreshMenu ,也可能需要同时调用 RefreshMenu 的任何其他代码,并且方法本身会检查必要的前置条件

现在,关于如何以及何时调用 RefreshMenu 方法,一种简单的机制是,只要集合的内容发生更改,就直接调用refresh方法。例如在集合的添加方法中。由于您使用 TOwnedCollection 作为基类,因此您只需键入所有者

function TNiftyListTags.Add: TNiftyListTag;
begin
  Result := TNiftyListTag(inherited Add);

  TNiftyRVFrameWithPopups(Owner).RefreshMenu;
end;

但是,这会将您的集合类直接耦合到充当所有者的特定组件。如果你的收藏专门针对这个类,那么这可能是有效的,但它仍然是不可取的。

要从集合中取消集合,您可以在集合中引入 OnChange 事件。一个简单的 TNotifyEvent 通常就足够了。

然后,无论哪个组件拥有该集合,都可以为此事件安装处理程序。每当集合发生变化时,都会调用 OnChange 处理程序。在这种情况下, TNiftyRVFrameWithPopups 组件将通过调用自己的 RefreshMenu 方法来响应这些更改。

procedure TNiftyListTags.DoOnChanged;
begin
  if Assigned(fOnChanged) then
    fOnChanged(self);
end;


function TNiftyListTags.Add: TNiftyListTag;
begin
  Result := TNiftyListTag(inherited Add);
  DoOnChanged;
end;


procedure TNiftyRVFrameWithPopups.OnTagsChanged(Sender: TObject);
begin
  RefreshMenu;
end;

这通常是我采用的方法,我将 OnChange 事件作为私有实现细节,构造函数中的处理程序由实例化集合的组件指定。这可以防止任何人无意中通过任何公共财产等替换事件处理程序。

constructor TNiftyRVFrameWithPopups.Create(Owner: TComponent);
begin
  inherited Create(self);

  fTags := TNiftyListTags.Create(self, OnTagsChanged);
  ..
end;

为方便起见,你显然需要一个自定义构造函数来接受事件处理程序:

TNiftyListTags = class(TOwnedCollection)
..
private
  fOnChanged: TNotifyEvent;
public
  constructor Create(aOwner: TPersistent; aOnChange: TNotifyEvent); reintroduce;
..
end;


constructor TNiftyListTags.Create(aOwner: TPersistent;
                                  aOnChange: TNotifyEvent);
begin
  inherited Create(aOwner, TNiftyListTag);

  fOnChange := aOnChange;
end;

请注意,继承的构造函数也接受两个参数,第二个是集合项的类。您正在介绍一个自定义构造函数,您可以从自己的构造函数的参数中删除它,只需在继承的创建调用中指定项目类。

注意:这不会增加集合和项类之间的耦合 - 根据定义(和设计),它们已经紧密耦合。