Delphi:在FormShow事件中重绘窗体

3

我在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 之前没有进行任何绘图。直到过程完成前,窗体总是看起来有问题。除此之外,程序的例行程序都执行得很好。我尝试了所有这些命令的组合或单独使用。

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

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

(Delphi XE7 在 Windows 7 64 位上)


2
你应该使用 class(TThread) 在后台准备数据,而不会打断用户与表单的交互。我已经很多年没有做 Delphi 了,所以我就让你自己去做吧。这里有一些例子:https://dev59.com/3nA75IYBdhLWcg3wJFcL#3456816 - Havenard
1
另一种方法中,删除前三行就是你的解决方案。你所说的“无解决方案”是什么意思? - Sertac Akyuz
@Havenard:MyOpenData()不是线程安全的,但是它相当频繁地访问表格和几个对象。不过,它应该在表单重绘以后运行,以确保外观完整。这就是我的问题所在。 - HJay
3
推迟处理是正确的做法,因为你的表单在 OnShow 中甚至还没有显示出来。在第二段代码中删除前三行,在 OpenMyData 过程中调用 'Update' 作为第一条语句,然后打开你的数据。 - Sertac Akyuz
1
延迟执行将允许窗口被绘制。当然,您仍将阻塞主线程,因此您的窗口将无响应并可能会出现幽灵效果。将长时间运行的代码移动到线程中。不要尝试合成这样的绘画消息。你做不到。只有系统可以。 - David Heffernan
显示剩余16条评论
2个回答

0
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;

1
"PostMessage(Self.Handle, WM_AFTER_SHOW, 0, 0);" 正是我在另一个代码示例中所做的。它并没有解决问题。然而,在 AfterShow 中包含 "Update;" 几乎解决了它。 - HJay
1
你不需要两个,两条消息会依次被检索。 - Sertac Akyuz

-2

在我看来,这里提供的信息不足以做出任何具体的建议。

我猜测MyOpenData()设置了一些数据状态,Form2依赖于它。如果是这样的话,那么你可能想在调用Form2.ShowModal之前调用它。无论如何,在OnShow处理程序内部调用invalidate或refresh都不应该,因为它们都会触发OnShow。

观看我为CodeRage 9制作的视频,标题为“最近您拥抱了软件管道工吗?”(在YouTube上搜索'coderage software plumber'),因为这正是我在这个视频中要解决的问题——初始化表单和对象以及与表单相关的时间问题的整个问题。

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

如果涉及到数据库,您必须将某些内容注入表单中:可以是数据库引用(通常通过全局变量实现);表格(可以是全局变量或表单变量);或当前记录(通常是表单变量)。使用DB-aware控件的好处是初始化始终是隐式的,您不必注入任何内容。但使用DB-aware控件的坏处是,初始化始终是隐式的,您无法明确控制初始化序列。通过显式注入DB依赖项,您可以避开时序问题。这需要一些额外的工作(不多),但您不必处理像这样的问题。
在任何情况下,如果表单需要一个当前记录来初始化其字段,则必须在选择记录后才能显示表单,否则会出现并发问题。虽然可以做到,但会让事情变得非常混乱。

为什么Invalidate会触发OnShow,我本来以为它会触发OnPaint。另外,提问者面临的问题似乎与您的视频无关。这个问题只不过是我们老朋友长时间运行的任务阻塞了GUI线程而已。 - David Heffernan
问题主要不是关于阻塞GUI,而是如何让表单在执行下一个命令之前完全绘制自己。遗憾的是没有命令可以强制表单在那一刻实际绘制自己。这就是问题所在。--当然,如果必要的操作是线程安全的,耗时的例程可以在单独的线程中完成,但在我的情况下它们并不是线程安全的。这就是为什么线程在这里不是解决方案的原因。 - HJay
问题在于...如何让表单在执行下一个命令之前完全绘制。了解表单字段是如何初始化的将非常有帮助。它们是DB-aware组件还是以其他方式填充的普通组件?您面临经典的竞争条件,即在表单尝试呈现它们的同时初始化表单上的字段。OnShow不是放置初始化字段代码的正确位置!在调用OnShow之前,它们需要被初始化,我个人认为,在OnCreate中进行初始化可能是最好的选择。数据来自哪里? - David Schwartz
线程确实是一种解决方案。但稍微重构设计,使事情按预期顺序发生可能更容易些。 - David Schwartz
我曾经在一个编辑器上工作,它在基因和基因组分析领域进行了大量的字符串处理。一些统计函数需要运行几分钟。我们没有在任何地方使用线程,并且UI也没有出现任何问题。它是100%的Delphi。我参与开发的另一个大型应用程序可以进行页面布局和重新排版,需要15-30秒钟的时间。没有使用线程,UI也从未被阻塞。另一个应用程序会在打开表单之前发出后端查询;它们需要10-15秒钟的时间。没有使用数据感知控件。这完全取决于您如何设计解决方案。 - David Schwartz
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接