我们可以在运行时为表单加载dfm文件吗?

时间:2013-11-14 22:05:39

标签: delphi

Delphi应用程序是否有可能接收带有对象,其属性和事件分配的dfm文件,并加载所有这些信息,就像它们如何处理使用它编译的内部dfm一样?

我们怎么能这样做?有直接的方法吗?

注意:应用程序已经拥有包含正确类和方法的代码,包括事件。我们还可以远程接收某些可由我的应用程序读取的脚本,该脚本将创建匹配dfm文件规范所必需的对象。就像Web浏览器解释HTML,css和JS文件一样......

2 个答案:

答案 0 :(得分:5)

确实可以在运行时加载.dfm文件并创建该dfm文件所代表的表单。

我写了一些代码来做到这一点:

但是:请注意:您需要在RegisterNecessaryClasses过程中添加更多RegisterClass(TSomeComponent)行。如上所述,例如,如果您尝试加载包含TSpeedbutton的.dfm文件,则会出现异常:只需将RegisterClass(TSpeedbutton)添加到RegisterNecessaryClasses过程。

unit DynaFormF;  // This is a normal Delphi form - just an empty one (No components dropped on the form)

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TfrmDynaForm = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmDynaForm: TfrmDynaForm;

implementation

{$R *.dfm}

end.

//:

unit DynaLoadDfmU;
{$O-}
interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls, utils08, DynaFormF;

var
  DebugSL : TStrings;

procedure ShowDynaFormModal(Filename:String);

implementation

procedure RegisterNecessaryClasses;
begin
  RegisterClass(TfrmDynaForm);
  RegisterClass(TPanel);
  RegisterClass(TMemo);
  RegisterClass(TTimer);
  RegisterClass(TListBox);
  RegisterClass(TSplitter);
  RegisterClass(TEdit);
  RegisterClass(TCheckBox);
  RegisterClass(TButton);
  RegisterClass(TLabel);
  RegisterClass(TRadioGroup);
end;

type
  TCrackedTComponent = class(TComponent)
  protected
    procedure UpdateState_Designing;
  end;

var
  ClassRegistered : Boolean;

procedure RemoveEventHandlers(SL:TStrings);
const
  Key1 = ' On';
  Key2 = ' = ';

var
  i, k1,k2 : Integer;
  S        : String;

begin
  for i := SL.Count-1 downto 0 do begin
    S := SL[i];

    k1 := pos(Key1, S);
    k2 := pos(Key2, S);

    if (k1 <> 0) AND (k2 > k1) then begin
      // remove it:
      SL.Delete(i);
    end;

  end;
end;

procedure ReportBoolean(S:String; B:Boolean);
const
  Txts : Array[Boolean] of String = (
  'Cleared', 'Set'
  );

begin
  if Assigned(DebugSL) then begin
    S := S + ' : ' + Txts[B];
    DebugSL.Add(S);
  end;
end;

procedure SetComponentStyles(AForm:TForm);
var
  AComponent : TComponent;
  i          : Integer;
  B1, B2     : Boolean;

begin
  for i := 0 to AForm.ComponentCount-1 do begin
    AComponent := AForm.Components[i];
    if AComponent is TTimer then begin
      // TTIMER:
      B1 := csDesigning in AComponent.ComponentState;

      // Does not work: an attempt to make the TTimer visible like it is in Delphi IDE's form designer.
      TCrackedTComponent(AComponent).UpdateState_Designing;

      B2 := csDesigning in AComponent.ComponentState;
      ReportBoolean('Before setting it: ', B1);
      ReportBoolean('After  setting it: ', B2);
    end;
  end;
end;

procedure ShowDynaFormModalPrim(Filename:String);
var
  FormDyna : TfrmDynaForm;

  S1       : TFileStream;
  S1m      : TMemoryStream;
  S2       : TMemoryStream;
  S        : String;
  k1, k2   : Integer;
  Reader   : TReader;
  SLHelper : TStringlist;
  OK       : Boolean;

  MissingClassName, FormName, FormTypeName : String;

begin
  FormName     := 'frmDynaForm';
  FormTypeName := 'TfrmDynaForm';

  FormDyna := NIL;
  OK       := False;

  S1 := TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite);
  try
    S1m := TMemoryStream.Create;
    try
      SLHelper := TStringlist.Create;
      try
        SLHelper.LoadFromStream(S1);

        S := SLHelper[0];

        k1 := pos(' ', S);
        k2 := pos(': ', S);
        if (k1 <> 0) AND (k2 > k1) then begin
          // match:
          SetLength(S, k2+1);
          S := 'object ' + FormName + ': ' + FormTypeName;
          SLHelper[0] := S;
        end;

        RemoveEventHandlers(SLHelper);
        SLHelper.SaveToStream(S1m);
      finally
        SLHelper.Free;
      end;

      S1m.Position := 0;
      S2 := TMemoryStream.Create;
      try
        ObjectTextToBinary(S1m, S2);
        S2.Position := 0;

        Reader := TReader.Create(S2, 4096);
        try
          try
            FormDyna := TfrmDynaForm.Create(NIL);
            Reader.ReadRootComponent(FormDyna);
            OK       := True;
            SetComponentStyles(FormDyna);
          except
            on E:Exception do begin
              S := E.ClassName + '    ' + E.Message;
              if Assigned(DebugSL) then begin
                DebugSL.add(S);
                if (E.ClassName = 'EClassNotFound') then begin
                  // the class is missing - we need one more "RegisterClass" line in the RegisterNecessaryClasses procedure.
                  MissingClassName := CopyBetween(E.Message, 'Class ', ' not found');
                  S := '    RegisterClass(' + MissingClassName + ');';
                  DebugSL.Add(S);
                end;
              end;
            end;
          end;
        finally
          Reader.Free;
        end;
      finally
        S2.Free;
      end;
    finally
      S1m.Free;
    end;
  finally
    S1.Free;
  end;

  if OK then begin
    try
      FormDyna.Caption := 'Dynamically created form: ' + ' -- ' + FormDyna.Caption;
      FormDyna.ShowModal;

    finally
      FormDyna.Free;
    end;
  end else begin
    // failure:
    S := 'Dynamic loading of form file failed.';
    if Assigned(DebugSL)
      then DebugSL.Add(S)
  end;
end;

procedure ShowDynaFormModal(Filename:String);
begin
  if NOT ClassRegistered then begin
    ClassRegistered := True;
    RegisterNecessaryClasses;
  end;

  ShowDynaFormModalPrim(Filename);
end;

{ TCrackedTComponent }

procedure TCrackedTComponent.UpdateState_Designing;
begin
  SetDesigning(TRUE, FALSE);
end;

end.

答案 1 :(得分:3)

您将看到一个DFM表单设计文件,并且想要实例化它?

如果没有附带的PAS源文件,则无法进行此操作。您需要实现类的行为和交互方式。 (如果没有实现,即DFM没有引用事件处理程序,那么如果您拥有或解析DFM中的类名,则可以create a class at runtime。但是您必须知道或解析表单中所有已发布的成员阶级,从而使这种解决方案成为一种学术性的。)

即使你有源文件,你也需要在编译时才能创建类。

如果您在编译时同时拥有设计文件和源文件,那么将它们添加到项目中,您就不需要从文件中加载表单,因为它包含在可执行文件中资源。只需在运行时使用默认构造函数Create即可创建表单。

如果您有单个PAS源文件的辅助DFM表单文件,则使用this kind of trick创建CreateNew构造函数的替代表单对象(例如,某些控件隐藏或样式化)基于相同的代码。

如果您的DFM文件是在运行时从表单的特定状态创建的,则创建表单正常但还原该特定状态,其中一个答案为this question。< / p>