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活动就太好了,但是我们没有,所以你必须自己做一些事情。选项包括:
我很想知道更好的方法。使用的典型模式是属性中的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.