Delphi 监督对象属性值的更改

Delphi 监督对象属性值的更改,delphi,Delphi,我想监督一个类的实例。每当该对象的属性发生更改时,我希望能够检查该属性,而无需自己实现该特性。特别是当类有许多属性时 我有一门课是这样的: TMyClass = class private FTest1: Integer; ... FTestN: Integer; public property Test1: Integer read FTest1 write FTest1; ... property TestN: Integer read FTest1 write F

我想监督一个类的实例。每当该对象的属性发生更改时,我希望能够检查该属性,而无需自己实现该特性。特别是当类有许多属性时

我有一门课是这样的:

TMyClass = class
private 
  FTest1: Integer;
  ...
  FTestN: Integer;
public
  property Test1: Integer read FTest1 write FTest1;
  ...
  property TestN: Integer read FTest1 write FTest1;
end.
使用该类时:

c := TMyClass.Create;
有这样的东西会很棒:

c.changed // -> false
c.Test1 := 1;
c.changed // -> true

有没有标准的方法可以做到这一点?

我知道有两种方法可以做到这一点,但它们都不整洁。如果我们有一个OnPropertyChanged活动就太好了,但是我们没有,所以你必须自己做一些事情。选项包括:

  • 在属性设置程序过程中为每个属性设置已更改的布尔值

  • 使用RTTI保留所有属性数据的卷影副本,并与计时器上的副本进行比较,以设置更改的标志(如果不同)


  • 我很想知道更好的方法。

    使用的典型模式是属性中的setter方法,正如Brian在选项1中所说的那样。我想给你写一些示例代码,这样你就可以看到人们在做什么

    请注意,NameChanged是一个虚拟方法,因为我可能希望声明一个基类TPersonInfo,然后稍后为TJanitorInfo创建一个子类,而TJanitorInfo对于
    NameChanged
    可能有一个更复杂的实现。因此,处理属性值更改的一个计划级别是子类可以重写方法。但是对于不是子类的东西,您在问题中建议将布尔标志设置为true。然后需要在某个地方“重复检查该标志”(称为轮询)。这最终可能是一项不值得做的工作。也许您需要的是下面显示的“事件”,也称为“回调”或“指向方法的指针”。在delphi中,这些属性以上的单词
    开头
    OnNameChanged
    就是这样一个事件

     type
       TPersonInfo = class
            private
              FName:String;
              FOnChangedProperty:TNotifyEvent;
            protected 
               procedure SetName(aName:String);
               procedure NameChanged; virtual;
            published
               property Name:String read fName write SetName;
    
               property OnChangedProperty:TNotifyEvent read FOnChangedProperty write FOnChangedProperty;
    
       end;
    
     ...
     implementation 
    
       procedure TPersonInfo.SetName(aName:String);
       begin 
          if aName<>FName then begin
            aName := FName;
            NameChanged;
          end;
       end;
    
       procedure NameChanged; virtual;
       begin
          // option A: set a boolean flag. Exercise for reader: When does this turn off?
          FNameChanged := true;
          // option B: refresh visual control because a property changed:
          Refresh;
          // option C: something else (math or logic) might need to be notified
          if Assigned(FOnChangedProperty) then
                  FOnChangedProperty(Self);
       end;
    
    类型
    TPersonInfo=类
    私有的
    FName:字符串;
    FOnChangedProperty:TNotifyEvent;
    受保护的
    过程集合名(aName:String);
    程序名称变更;事实上的
    出版
    属性名称:字符串读取fName写入SetName;
    OnChangedProperty属性:TNotifyEvent读取FOnChangedProperty写入FOnChangedProperty;
    结束;
    ...
    实施
    过程TPersonInfo.SetName(aName:String);
    开始
    如果是aNameFName,则开始
    aName:=FName;
    更名;
    结束;
    结束;
    程序名称变更;事实上的
    开始
    //选项A:设置布尔标志。读者练习:何时关闭?
    FNameChanged:=真;
    //选项B:刷新可视控件,因为属性已更改:
    刷新
    //选项C:可能需要通知其他内容(数学或逻辑)
    如果已分配(FOnChangedProperty),则
    FontChangedProperty(自我);
    结束;
    
    我对这一主题做了一些研究,并与来自的游戏一起实现了这一目标:

    unit Aspects.ChangeDetection;
    
    interface
    
    uses
      DSharp.Aspects,
      Rtti,
      SysUtils,
      StrUtils;
    
    type
      TChangeDetectionAspect = class(TAspect)
      private
        class var IsChanged : Boolean;
      public
        class procedure DoAfter(Instance: TObject; Method: TRttiMethod;
          const Args: TArray<TValue>; var Result: TValue); override;
        class procedure DoBefore(Instance: TObject; Method: TRttiMethod;
          const Args: TArray<TValue>; out DoInvoke: Boolean;
          out Result: TValue); override;
        class procedure DoException(Instance: TObject; Method: TRttiMethod;
          const Args: TArray<TValue>; out RaiseException: Boolean;
          Exception: Exception; out Result: TValue); override;
      end;
    
      ChangeDetectionAttribute = class(AspectAttribute)
      public
        constructor Create;
      end;
    
      [ChangeDetection]
      IChangeable = interface
      ['{59992EB4-62EB-4A9A-8216-1B14393B003B}']
        function GetChanged: Boolean;
        procedure SetChanged(const Value: Boolean);
        property Changed : boolean read GetChanged write SetChanged;
      end;
    
      TChangeable = class(TInterfacedObject, IChangeable)
      private
        FChanged : Boolean;
        function GetChanged: Boolean;
        procedure SetChanged(const Value: Boolean);
      public
        property Changed : boolean read GetChanged write SetChanged;
      end;
    
    
    implementation
    
    { TChangeDetectionAspect }
    
    class procedure TChangeDetectionAspect.DoAfter(Instance: TObject; Method: TRttiMethod;
      const Args: TArray<TValue>; var Result: TValue);
    
    var ic : IChangeable;
    
    begin
     if Supports(Instance, IChangeable, ic) then
      ic.Changed := IsChanged;
    end;
    
    class procedure TChangeDetectionAspect.DoBefore(Instance: TObject; Method: TRttiMethod;
      const Args: TArray<TValue>; out DoInvoke: Boolean; out Result: TValue);
    
    var ctx  : TRttiContext;
        typ  : TRttiType;
        meth : TRttiMethod;
        Res  : TValue;
    
    begin
     IsChanged := False;
     if StartsText('set', Method.Name) then
      begin
       ctx := TRttiContext.Create;
       typ := ctx.GetType(Instance.ClassType);
       // call Getxxx counterpart
       meth := typ.GetMethod('G'+ Copy(Method.Name, 2, Maxint));
       if Assigned(meth) then
        try
         Res := meth.Invoke(Instance, []);
         IsChanged := Res.AsVariant <> Args[0].AsVariant;
        except
        end;
      end;
    end;
    
    class procedure TChangeDetectionAspect.DoException(Instance: TObject; Method: TRttiMethod;
      const Args: TArray<TValue>; out RaiseException: Boolean; Exception: Exception;
      out Result: TValue);
    begin
    
    end;
    
    { ChangeDetectionAttribute }
    
    constructor ChangeDetectionAttribute.Create;
    begin
      inherited Create(TChangeDetectionAspect);
    end;
    
    { TChangeable }
    
    function TChangeable.GetChanged: Boolean;
    begin
     Result := FChanged;
    end;
    
    procedure TChangeable.SetChanged(const Value: Boolean);
    begin
     FChanged := Value;
    end;
    
    end.
    
    请注意,您应该至少拥有Delphi2010才能使其正常工作


    不过,我更喜欢沃伦的答案(不那么神奇),我只是想证明这是可能的(使用虚拟函数代理)

    你的要求并不完全清楚。您是否正在寻找一种方法来检测对象的属性何时被修改,而不必为每个属性的getter触发事件?请注意,您的示例并不清楚,因为
    changed
    是一个临时属性:在第二次
    c.changed
    之后它应该返回什么?假设只有一个客户端检查更改,是否应该将其重置为false,还是应该保持为true?另外,您是否愿意更改
    TMyClass
    或是否希望从外部感知更改(这是不可能的)?请精确您的Delphi版本(添加适当的特定标记)!虚拟代理功能是另一种方式(见我的答案)
    unit u_frm_main;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, Aspects.ChangeDetection, DSharp.Aspects.Weaver;
    
    type
      TForm1 = class(TForm)
        procedure FormCreate(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
      IMyObject = interface(IChangeable)
        function GetName: String;
        procedure SetName(const Value: String);
        property Name : String read GetName write SetName;
      end;
    
      TMyObject = class(TChangeable, IMyObject)
      private
        FName : String;
      public
        function GetName: String;
        procedure SetName(const Value: String); virtual;
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    { TMyObject }
    
    function TMyObject.GetName: String;
    begin
     Result := FName;
    end;
    
    procedure TMyObject.SetName(const Value: String);
    begin
     FName := Value;
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    
    var MyObject : IMyObject;
    begin
     MyObject := TMyObject.Create;
     MyObject.Changed := False;
     AspectWeaver.AddAspect(TMyObject, TChangeDetectionAspect, '^Set');
     MyObject.Name := 'yee';
     if MyObject.Changed then
      ShowMessage('yep changed');
     MyObject.Name := 'yee';
     if MyObject.Changed then
      ShowMessage('oops, not changed should not display');
     MyObject.Name := 'yeea';
     if MyObject.Changed then
      ShowMessage('yep changed');
    end;
    
    end.