【问题标题】:Delphi: Repaint form in FormShowDelphi:在 FormShow 中重绘表单
【发布时间】:2015-07-21 23:26:58
【问题描述】:

我在 FormMain 中打开 Form2.ShowModal。我希望应用程序在进行一些数据库访问时完整地显示 Form2(这与要显示的新数据无关)。但是,在执行 FormShow 时,只显示了外边框和一些损坏的部分,而 FormMain 的一些损坏的部分则显示出来了。太丑了。

我无法找到一种方法让 Delphi 立即重新绘制表单,然后执行耗时的 MyOpenData 过程。结束 MyOpenData 后一切正常。

procedure TForm2.FormShow(Sender: TObject);
begin
  Invalidate; 
  Refresh;
  MyOpenData; { needs some seconds of database accesses }
end;

替代方案:

procedure TForm2.FormShow(Sender: TObject);
begin
  Invalidate;
  Refresh;
  SendMessage(Handle, wm_paint, 0, 0);
  PostMessage(Handle, wm_OpenMyData, 0, 0); { executes well, but no solution)
end;

这也不起作用。我认为 SendMessage() 等待消息完成。但是在 MyOpenData 之前没有完成绘制。在程序完成之前,表格总是看起来很破损。除此之外,例程执行得很好。我尝试了所有这些命令的组合或单独。

我错过了什么?提前致谢!

如何启动打开表单时需要运行的耗时例程?

(Windows 7 64 位上的 Delphi XE7)

【问题讨论】:

  • 您应该使用class(TThread) 在后台准备此数据,而不会破坏用户与表单的交互。我很多年都不做Delphi了,所以我就让你去做。这里有几个例子:stackoverflow.com/a/3456816/156811
  • alternative 中,删除前三行,这就是您的解决方案。 没有解决方案是什么意思?
  • @Havenard:MyOpenData() 不是线程安全的,但会经常访问 Form 和几个对象。但是,它应该在表单重新绘制以使其看起来完整之后运行。这就是我的问题所在。
  • @HJay - 推迟处理是正确的做法,您的表单甚至在 OnShow 中都不可见。删除第二个 sn-p 中的前三行,在过程 OpenMyData 中调用 'Update' 作为第一条语句。然后打开你的数据。
  • 延迟执行将允许绘制窗口。当然,您仍然会阻塞主线程,因此您的窗口将无响应并且可能出现重影。将长时间运行的代码移至线程。永远不要尝试合成这样的绘画信息。你不能。只有系统可以。

标签: delphi delphi-xe7


【解决方案1】:
uses
WinApi.Windows;

const
WM_AFTER_SHOW     = WM_USER + 1; // custom message
WM_AFTER_CREATE   = WM_USER + 2; // custom message

private

procedure WmAfterCreate(var Msg: TMessage); message WM_AFTER_CREATE;
procedure WmAfterShow(var Msg: TMessage); message WM_AFTER_SHOW;


procedure TForm1.WmAfterCreate(var Msg: TMessage);
begin
 DoSomeThingAfterCreate();
 ShowMessage('WM_AFTER_CREATE received!');
end;

procedure TForm1.WmAfterShow(var Msg: TMessage);
begin
  DoSomeThingAfterShow();
  ShowMessage('WM_AFTER_SHOW received!');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 // Some code...
PostMessage(Self.Handle, WM_AFTER_CREATE, 0, 0);
end;

procedure TForm1.FormShow(Sender: TObject);
begin
 // Some Code...
 PostMessage(Self.Handle, WM_AFTER_SHOW, 0, 0);
end;

【讨论】:

  • 'PostMessage(Self.Handle, WM_AFTER_SHOW, 0, 0);'这正是我在替代代码示例中所做的。它没有解决问题。但是,包括“更新”;在 AfterShow 中几乎解决了。
  • 你不需要两个,两个消息都会被连续检索。
【解决方案2】:

IMO,这里没有足够的信息来提出任何具体的建议。

我猜 MyOpenData() 设置了 Form2 所依赖的某种数据状态。如果是这种情况,那么您可能希望在调用 Form2.ShowModal 之前调用它。在任何情况下,您都不应该在 OnShow 处理程序中调用 invalidate 或 refresh,因为它们都会触发 OnShow。

观看我为 CodeRage 9 制作的视频,标题为“你最近接受你的软件管道工了吗?” (在 YouTube 上搜索“编码软件管道工”)因为这正是我在此视频中要解决的主题——初始化表单和对象所涉及的大量问题,以及特定于表单的时间问题。

我没有专门讨论数据感知控件的问题,但它们几乎相同。从依赖于该状态的表单内部设置 DB 状态以正确初始化自身可能会出现问题。通过首先进行依赖初始化,然后将该依赖项注入表单,可以轻松避免固有的竞争条件。

如果涉及 DB,则必须将 SOMETHING 注入表单:要么是 DB 引用(通常通过全局变量);要么一个表(全局变量或表单变量);或当前记录(通常是表单变量)。使用 DB 感知控件的好处是初始化始终是隐式的,您不必注入任何东西。使用 DB 感知控件的坏处是,初始化始终是隐式的,并且您无法对初始化序列进行显式控制。通过对数据库依赖项进行显式注入,您可以回避时间问题。这需要更多的工作(不多),但您不必处理这样的事情。

在任何情况下,如果表单需要当前记录来初始化其字段,则在选择记录之前您无法显示表单,并且您不能将记录选择作为表单初始化过程的一部分而不冒险并发问题。可以做到,但你把它弄得一团糟。

【讨论】:

  • 为什么 Invalidate 会触发 OnShow。我希望它会触发 OnPaint。此外,提问者面临的问题似乎与您的视频无关。这个问题只不过是我们的老朋友,一个长时间运行的任务阻塞了 GUI 线程。
  • 这个问题主要不是关于阻止 GUI,而是关于如何让表单在进入下一个命令之前完全绘制自己。遗憾的是,没有命令可以强制表格在那个时刻实际绘制自己。这就是问题所在。 - 当然你是对的,如果必要的操作是线程安全的,那么耗时的例程可以在单独的线程中完成,而在我的情况下它们不是。这就是为什么线程在这里没有解决方案。
  • 线程就是解决方案。如果线程被阻塞,GUI 将无法绘制自己。
  • “问题是......关于如何让表单在进入下一个命令之前完全绘制自己。”了解表单字段是如何被初始化的会很有帮助。它们是支持 DB 的组件,还是只是以其他方式填充的普通组件?您有一个经典的竞争条件,其中表单上的字段在表单尝试呈现它们的同时被初始化。 OnShow 是放置初始化字段的代码的错误位置!它们需要在调用 OnShow 之前进行初始化,恕我直言。 OnCreate 可能是最好的选择。数据来自哪里?
  • 线程当然是一种解决方案。但是稍微重组你的设计,让事情按照预期的顺序发生可能会更容易。
【解决方案3】:

这应该可行。

  • 在表单上放置一个计时器,并在 Object Inspector 中将 Enabled 设置为 False。
  • 在 FormCreate 中将 Enabled 设置为 True
  • 在 OnTimer 事件中放入您的代码。

您必须猜测 Interval 值。如果 FormCreate 中的组件或代码不多,那么 100 毫秒就足够了。否则放入有效的 - 500 甚至 1000 毫秒。

【讨论】:

  • 这和你的第一次尝试一样糟糕。我们仍然有可重入消息处理。现在我们可以在表单开始工作之前与它进行交互。让我再次投反对票。
  • 您对 ProcessMessages 答案(您已删除)投了赞成票,对此答案也投了赞成票。这两个答案通常都会吸引大量反对票。我觉得这很难解释。
  • @DavidHeffernan,这不是可重入消息处理。另一个是,这就是我删除的原因。新消息进入队列并按顺序处理。在我看来,你让你对我的看法影响了你对我回答的判断。无论如何,你觉得更令人费解的是,没有暴风雨的反对票或赞成票的存在。不是每个人都会像你一样思考。 :-)
  • 我对你没有意见。我对答案有意见。从某种意义上说,它是可重入的,您可以在工作之前处理其他消息。与您的第一次尝试完全相同。除了这个更糟糕,因为它提供了更大的机会之窗。
猜你喜欢
  • 1970-01-01
  • 2021-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多