【问题标题】:Dynamic TForm creation Idiosyncrasies(bugs?) in Delphi XE5Delphi XE5 中的动态 TForm 创建特性(错误?)
【发布时间】:2014-04-01 22:01:38
【问题描述】:

这个把我逼上墙了。从 Delphi 6 到 XE5 的大部分转换都在顺利进行,但我有各种例程来动态构建各种 TForm 后代(没有 DFM),将其弹出并通常返回一个值。我有一些在 D6 中运行良好。一般来说,我会选择一个我想弹出的地方(比如在面板上),以及我想弹出的内容(编辑框、备忘录、列表框...)。我创建表单,设置初始值并调用 showmodal 并返回一些结果。

在 XE5 中编译的相同代码有执行(故障)。一种是创建的表单接受left,top等,但不会在那里显示。这些值在属性中是正确的,但表单在错误的位置。第二个可能相关(故障)是当我创建一个 TMemo 或 TListbox 并在其中存储一些文本时,“ShowModal”正确显示数据,但“Show”没有。

我花了几个小时将问题消化为最简单的形式,删除了我所有的个人代码。如此处所示,它运行良好

如果我注释掉这一行,它就不起作用 - 表单显示在错误的位置

XX.ClientToScreen(Point(0,0));      // EXTREMELY WEIRD PATCH

这一行是一个函数调用,不应该影响其他任何东西,我不使用返回值。

注释掉的“显示”行说明了另一个问题(数据未显示)。

我在各种地方都尝试过Application.ProcessMessages,但它从来没有让事情变得更好,而且有时会让事情变得更糟。

让我感到“困惑”。

//-----------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------

type TMemoForm = class(TForm)
   private
   public
      XMemo         : TMemo;
   end;


Function  PopUpMemoStr(txt : AnsiString; x : integer = 200; y : integer = 200; w : integer = 400 ; h : integer = 400 ) : AnsiString;  // more or less a dummy for testing on XE5 2/28/14
var XX : TMemoForm;
     begin
     XX := TMemoForm.CreateNew(Application);

     XX.ClientToScreen(Point(0,0));      // *** EXTREMELY WEIRD FIX ***
     XX.Left := X; XX.Top := Y; XX.Width := w;  XX.height := h;
     XX.caption := 'Dummy PopUpMemo';

     XX.XMemo := TMemo.create(XX);
     XX.XMemo.parent := XX;
     XX.XMemo.align := alClient;
     XX.XMemo.text := txt;

     //logit('PopUpMemoStr R='+TRectToStr(MyGetScreenRect(XX)));
     XX.showmodal;
     //XX.show; delay(3.00);  // other "no data" problem
     XX.free;
     end;

//exercise code -- Panel2 is just a visible spot to see if positioning works correctly 
var s : AnsiString;
var R : TRect;
     begin
     //R := MyGetScreenRect(Panel2);                    
     R := Rect(414,514,678,642);        // just a useful screen location for testing
     s := 'One'+CRLF+'Two'+CRLF+'Three'+CRLF+'Four';    // "CRLF is #13#10
     PopUpMemoStr(s,R.Left,R.Top,R.Right-R.Left,R.Bottom-R.Top);


//-----------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------

【问题讨论】:

    标签: delphi delphi-xe5


    【解决方案1】:

    要解决表单定位问题,需要将表单的Position设置为poDesigned

    对于你的第二个问题,你不能那样拖延。您没有给表单处理消息的机会。将其更改为类似下面的代码可以正确显示数据(尽管您也不应该这样做):

    begin
      XX := TMemoForm.CreateNew(nil);
      try
        XX.Position := poDesigned; // This line needs to be added for the positioning
        XX.SetBounds(X, Y, w, h);
        XX.Caption := 'Dummy PopUpMemo';
    
        XX.XMemo := TMemo.Create(XX);
        XX.XMemo.Parent := XX;
        XX.XMemo.Align := alClient;
        XX.XMemo.Text := txt;
    
        //logit('PopUpMemoStr R='+TRectToStr(MyGetScreenRect(XX)));
        // XX.ShowModal;
        // This displays the data correctly but is not advisable
        XX.Show;
        for I := 1 to 6 do
        begin
          Sleep(500);
          Application.ProcessMessages;
        end;
      finally
        XX.Free;
      end;
    end;
    

    如果您想将Show() 用于这样的表单,您应该使用表单的OnClose 事件并将其Action 参数设置为caFree,然后在您的代码中执行Show()。在表单上放置一个计时器 x 秒,并在计时器完成时 Close() 它。有点像这样:

    type
      TMemoForm = class(TForm)
      public
         XMemo : TMemo;
         XTimer: TTimer;
         procedure FormClose(Sender: TObject; var Action: TCloseAction);
         procedure TimerElapsed(Sender: TObject);
      end;
    
    procedure TMemoForm.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
      Action := caFree;
    end;
    
    procedure TMemoForm.TimerElapsed(Sender: TObject);
    begin
      Close;
    end;
    

    begin
      XX := TMemoForm.CreateNew(nil);
      try
        XX.Position := poDesigned; // This line needs to be added for the positioning
        XX.SetBounds(X, Y, w, h);
        XX.Caption := 'Dummy PopUpMemo';
        XX.OnClose := XX.FormClose;
    
        XX.XMemo := TMemo.Create(XX);
        XX.XMemo.Parent := XX;
        XX.XMemo.Align := alClient;
        XX.XMemo.Text := txt;
    
        XX.XTimer := TTimer.Create(XX);
        XX.XTimer.Interval := 3000;
        XX.XTimer.OnTimer := XX.TimerElapsed;
        XX.Active := True;
    
        XX.Show; // Just show the form. The rest is in the Form itself.
      except
        XX.Free;
        raise;
      end;
    end;
    

    【讨论】:

    • 使用Show()ShowModal() 的经验法则是,如果您需要显示表单并等待它关闭,请使用ShowModal()(它会在内部为您泵送消息队列),否则请改用Show(),让 VCL 为您处理正常的消息循环。
    • 如果需要手动循环,不要盲目调用Application.ProcessMessages()!使用MsgWaitForMultipleObjects()GetQueueState() 在调用Application.ProcessMessages() 之前检查待处理的消息。如果您需要等待一段时间过去,CreateWaitableTimer()MsgWaitForMultipleObjects() 的组合很适合。
    • 谢谢,谢谢,谢谢。我看到 TForm.Position 是 Delphi 6 中的一个属性,但它的默认值必须不同,因为我以前从未见过这种行为。 Show/ShowModal 从来都不是好的代码,我只是想澄清位置问题,这似乎是循环测试运行的更快方法。回想起来,这是一个“嗯,Duh!” - 在整个测试结束之前,备忘录从来没有得到消息来刷新自己,并且表单被释放。
    【解决方案2】:

    您极其奇怪的补丁,在新创建的表单上调用ClientToScreen,应该可以解决问题,即使您不使用返回的点。

    在你不使用它的情况下,当你设置窗体的边界时,由于窗体的窗口还没有被创建,VCL 会保留这个信息,以便稍后在窗口大约时传递给 API要显示。但由于Position 属性的poDefaultPosOnly 设置,VCL 还告诉API 使用默认窗口位置,因此此信息将被丢弃。

    在您使用它的情况下,为了能够确定窗体在屏幕中的位置,VCL 首先创建窗体的窗口。因此,当您稍后设置表单的边界时,它们实际上是通过SetWindowPos 实现的。

    因此,如果您使用过

    XX.HandleNeeded;
    

    而不是

    XX.ClientToScreen(Point(0,0));
    

    这将是一个更直接的解决方法。

    当然正确的解决方案在 Graymatter 的answer


    我无法评论 Show 不显示数据,您在问题中发布的代码不应表现出这种行为。

    【讨论】:

    • 感谢您的澄清。在我的原始代码中,有几层我自己的控件修改例程,我只是开始逐行简化和删除它们,直到窗体停止正确定位。 XX.ClientToScreen(Point(0,0)) 是最后一行。您的解释更清楚地说明了为什么它会产生这种效果。谢谢。
    • @HNR - 不客气。我不得不对答案进行一些更正。顺便说一句,你说得对,默认值被改变了,我检查了 D7,默认是poDesigned 那里。
    • 它可能比更改默认值更微妙,因为获得我想要的行为的唯一设置是将位置属性显式设置为 poDesigned。其他人得到合理的结果(poScreenCenter ...),但 poDesigned 似乎让我设置了位置属性并获得了预期的结果。这并不是我通过阅读每个值的帮助定义所期望的。 “poDesigned”可能链接到一个dfm,在这种情况下没有dfm。可能是设置它的行为造成了不同 - 非常类似于 ClientToScreen 的效果。
    • 是的,XX.HandleNeeded 有效,它比 Position 属性更有意义。我在 Delphi 中编码已经很多很多年了,但我倾向于忽略我没有遇到任何问题的领域。运行时表单创建一直工作得很好,但现在我开始看到我缺少的一些细微之处。
    • @HNR - docs 只是表示表单将使用您分配给它的任何位置。不要过多考虑所提到的“设计时间”。我更喜欢“poDesigned”而不是“HandleNeeded”,它允许在显示窗口时创建窗口,并且创建早期句柄可能会导致某些极端情况出现一些问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多