根据需要在运行时更改组件类

kob*_*bik 16 delphi vcl delphi-5

我的问题类似于这里的想法:在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;
Run Code Online (Sandbox Code Playgroud)
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;
Run Code Online (Sandbox Code Playgroud)

注意:为了清楚起见,我想更改一些实例,但不是全部,所以内插器类不会这样做.

Ste*_*nke 21

这更容易思考(感谢Hallvard的博客 - Hack#14:在运行时更改对象的类):

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;
Run Code Online (Sandbox Code Playgroud)

  • +1.看起来正是我所希望的!Hallvard的精彩解决方案.谢谢 :) (2认同)
  • 我只想说,在未来的某个时候,您入侵 VMT 的决定会反过来咬您。这可能非常狡猾和棘手,但通常最好的解决方案是简单透明的解决方案。 (2认同)
  • 我向PatchInstanceClass添加了一些检查,以防止修补不兼容的类和可能具有没有分配内存的字段的类. (2认同)

Dav*_*nan 9

执行摘要:使用具有运行时切换行为的内插器类.


虽然@kobik正在使用Delphi 5并且无法完成我在下面描述的内容,但这个答案充实了支持的方式来更改实例的VMT TVirtualMethodInterceptor.梅森的评论激发了我写这篇文章.

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;
Run Code Online (Sandbox Code Playgroud)

我只是在脑海里编译这个,所以毫无疑问需要调试.有些东西告诉我捕获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;
Run Code Online (Sandbox Code Playgroud)