我的问题类似于这里的想法:Replacing a component class in delphi 但是我需要根据需要更改特定的组件类 这是一些伪演示代码:
unit Unit1;
TForm1 = class(TForm)
ImageList1: TImageList;
ImageList2: TImageList;
private
ImageList3: TImageList;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ImageList3 := TImageList.Create(Self);
// all instances of TImageList run as usual
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Unit2.MakeSuperImageList(ImageList2);
Unit2.MakeSuperImageList(ImageList3);
// from now on ONLY ImageList2 and ImageList3 are TSuperImageList
// ImageList1 is unchanged
end;
unit Unit2;
type
TSuperImageList = class(Controls.TImageList)
protected
procedure DoDraw(Index: Integer; Canvas: TCanvas; X, Y: Integer;
Style: Cardinal; Enabled: Boolean = True); override;
end;
procedure TSuperImageList.DoDraw(Index: Integer; Canvas: TCanvas; X, Y: Integer;
Style: Cardinal; Enabled: Boolean = True);
var
Icon: TIcon;
begin
Icon := TIcon.Create;
try
Self.GetIcon(Index, Icon);
Canvas.Draw(X, Y, Icon);
finally
Icon.Free;
end;
end;
procedure MakeSuperImageList(ImageList: TImageList);
begin
// TImageList -> TSuperImageList
end;
注意:为了清楚起见,我想更改部分实例,但不是所有,因此interposer class不会做。
答案 0 :(得分:20)
这更容易思考(感谢Hallvard's Blog - Hack#14: Changing the class of an object at run-time):
procedure PatchInstanceClass(Instance: TObject; NewClass: TClass);
type
PClass = ^TClass;
begin
if Assigned(Instance) and Assigned(NewClass)
and NewClass.InheritsFrom(Instance.ClassType)
and (NewClass.InstanceSize = Instance.InstanceSize) then
begin
PClass(Instance)^ := NewClass;
end;
end;
type
TMyButton = class(TButton)
public
procedure Click; override;
end;
procedure TMyButton.Click;
begin
ShowMessage('Click!');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
PatchInstanceClass(Button1, TMyButton);
end;
答案 1 :(得分:9)
执行摘要:使用具有运行时切换行为的插入器类。
尽管@kobik正在使用Delphi 5并且无法完成我在下面描述的内容,但这个答案充实了使用TVirtualMethodInterceptor
更改实例的VMT的受支持方式。梅森的评论激发了我写这篇文章。
procedure MakeSuperImageList(ImageList: TImageList);
var
vmi: TVirtualMethodInterceptor;
begin
vmi := TVirtualMethodInterceptor.Create(ImageList.ClassType);
try
vmi.OnBefore := procedure(Instance: TObject; Method: TRttiMethod;
const Args: TArray<TValue>; out DoInvoke: Boolean; out Result: TValue)
var
Icon: TIcon;
Canvas: TCanvas;
Index: Integer;
X, Y: Integer;
begin
if Method.Name<>'DoDraw' then
exit;
DoInvoke := False;//don't call TImageList.DoDraw
Index := Args[0].AsInteger;
Canvas := Args[1].AsType<TCanvas>;
X := Args[2].AsInteger;
Y := Args[3].AsInteger;
Icon := TIcon.Create;
try
ImageList.GetIcon(Index, Icon);
Canvas.Draw(X, Y, Icon);
finally
Icon.Free;
end;
end;
vmi.Proxify(ImageList);
finally
vmi.Free;
end;
end;
我只是在脑海里编译了这个,所以毫无疑问需要调试。有人告诉我,捕获ImageList
可能不起作用,在这种情况下你需要写Instance as TImageList
。
除非您使用基于VMT修改的解决方案,否则您必须创建新实例(根据Mason的建议)。这意味着您还必须在创建新实例的同时修改对图像列表实例的所有引用。在我看来,基于实例化替换对象排除任何提议的解决方案。
因此,我的结论是,为了完全通用地实现您提出的解决方案,您需要运行时VMT修改。如果您没有以受支持的方式提供此类设施的现代Delphi,您将需要破解VMT。
现在,在我看来,即使使用虚拟方法拦截器,修改VMT也是相当令人反感的。我想你可能会以错误的方式解决这个问题。我建议您使用内插器类(或其他一些子类技术)并在运行时使用子类的属性切换行为。
type
TImageList = class(ImgList.TImageList)
private
FIsSuper: Boolean;
protected
procedure DoDraw(Index: Integer; Canvas: TCanvas; X, Y: Integer;
Style: Cardinal; Enabled: Boolean = True); override;
public
property IsSuper: Boolean read FIsSuper write FIsSuper;
end;
TImageList.DoDraw(Index: Integer; Canvas: TCanvas; X, Y: Integer;
Style: Cardinal; Enabled: Boolean = True);
var
Icon: TIcon;
begin
if IsSuper then
begin
Icon := TIcon.Create;
try
Self.GetIcon(Index, Icon);
Canvas.Draw(X, Y, Icon);
finally
Icon.Free;
end;
end
else
inherited;
end;
....
procedure TForm1.Button1Click(Sender: TObject);
begin
ImageList2.IsSuper := True;
ImageList3.IsSuper := True;
end;
答案 2 :(得分:3)
没有自动的方法,但你可以尝试这样的事情:
procedure MakeSuperImageList(var ImageList: TImageList);
var
new: TImageList;
begin
if ImageList is TSuperImageList then
Exit;
new := TSuperImageList.Create(ImageList.Owner);
new.Assign(ImageList);
ImageList.Free;
ImageList := new;
end;
根据Assign
的实现方式,它可能无法按预期工作,但您可以覆盖TSuperImageList上的Assign
或AssignTo
以获得所需的行为。