如何在不同步的情况下使用 Delphi (Pre Delphi 2010) 版本更新 GUI 控件
Posted
技术标签:
【中文标题】如何在不同步的情况下使用 Delphi (Pre Delphi 2010) 版本更新 GUI 控件【英文标题】:How to update GUI controls with versions of Delphi (Pre Delphi 2010) without Synchronize 【发布时间】:2013-11-21 16:00:55 【问题描述】:我有一些使用 Delphi 2010 和 XE4 构建的应用程序,它们在线程中使用同步。我认为 Synchronize 是在 Delphi 2010 中引入 Delphi 的。我的线程运行良好,所以这不是问题。
我的问题是:有什么方法可以与 Delphi 2010 之前的 Delphi 版本“同步”,或者以不同的方式询问,如何在没有同步的情况下更新这些早期版本的 Delphi 中的 GUI 控件?
下面显示的代码是实际代码的子集,以减少本文的长度。
type
A TThread descendent for loading Images from a folder
TFolderLoadingThread = class(TThread)
private
Private declarations
AspectRatio: double;
protected
Protected declarations
procedure Execute; override;
public
Public declarations
constructor Create(CreateSuspended: Boolean);
end;
procedure TFolderLoadingThread.Execute;
Load images ImageEnView in the thread.
begin
inherited;
Free the thread onTerminate
FreeOnTerminate := True;
if not Terminated then
begin
Set the Progressbar.Max Value
**Synchronize**(
procedure
begin
if iFileCount > 0 then
Form1.Gauge1.MaxValue := iFileCount - 1;
end);
end;
【问题讨论】:
Synchronize 适用于所有版本的 Delphi(因为 TThread 已经存在)。TThread.Synchronize
一直都在。您是在问匿名方法吗?
一个很好的同步总结:***.com/a/1806947/327083
【参考方案1】:
Synchronize 是一个非常古老的例程,但在 D2009 之前的 Delphi 版本中没有匿名过程。在这些版本中,Synchronize 旨在调用不带参数的方法。
procedure TFolderLoadingThread.UpdateProgress;
begin
if iFileCount > 0 then
Form1.Gauge1.MaxValue := iFileCount - 1;
end;
在执行中:
do thead work...
Synchronize(UpdateProgress);
附:您不必在 Execute 正文中调用 Terminate
【讨论】:
换句话说:将同步代码移动到该线程类的单独方法中,并通过 Synchronize 调用此方法。 @Uwe- 你能显示一些代码还是我应该显示线程类...它很短。procedure TFolderLoadingThread.UpdateGUI; begin Form1.Gauge1.MaxValue := iFileCount - 1; end;
并由Synchronize(UpdateGUI);
调用
@Bill 与其要求我们提供代码,不如自己尝试一下吗?
@David - 是的,我想我明白了,所以我可以自己尝试一下。【参考方案2】:
同步几乎永远存在。我在 Delphi 5 中使用过。但上面已经说过了。
1234563 .例如,如果有一个很长的计算,并且您想要一个计量器“完成 5678 个中的 1234 个” - 那么停止计算过程就没有什么意义了。让计数器不准确,加减一打。为什么要关心?现在,如何实现它...对于简单的情况,您可以使用直接 Windows 访问。
例如,如果我们有一个 windowed 文本标签 - 继承自 TWinControl
然后我们可以绕过 VCL 及其所有优点并使用标准 Windows API 更改标题
http://msdn.microsoft.com/en-us/library/windows/desktop/ms632644.aspx http://msdn.microsoft.com/en-us/library/windows/desktop/ms644944.aspx您还必须正确计算要显示的总数和消息。然而,这与 GUI 和 VCL 无关。但希望您了解同步原语,例如
http://www.freepascal.org/docs-html/fcl/syncobjs/tcriticalsection.html作为。是的,有 AtomicIncrement 给你,我只是详细展示了框架。
下面我假设 TMyThread 是工作人员池之一,处理一些常见的工作项。它可能是唯一的工作线程或许多工作线程之一,它只是不知道,也不关心。
TThreadsCoordinator = class
...
private
CounterCS; TCriticalSection;
public
property CompleteItemsCount: integer
read FCompleteItemsCount;
function IncCompleteItemsCount(const Delta: integer): integer;
end;
...
function TThreadsCoordinator.IncCompleteItemsCount;
begin
CounterCS.Acquire;
try
Inc(FCompleteItemsCount, Delta);
finally
CounterCS.Release;
end;
Result := FCompleteItemsCount;
end;
var GlobalCaptionBuffer: array[0..127] of char;
// should be filled through with #0 before threads are started
procedure TMyThread.WorkItemComplete;
var s: string;
begin
...
s := Format('Done: %d of %d',
[ Self.Coordinator.IncCompleteItemsCount( +1 ),
Self.Coordinator.TotalItemsCount ] );
StrCopy( @GlobalCaptionBuffer[0], PChar(s));
if MyProgressForm1.Visible then // avoid ReCreateWnd inside .ShowModal
PostMessage( MyProgressForm1.StaticText1.Handle,
WM_SETTEXT, 0, Integer(@GlobalCaptionBuffer[0]));
...
end;
这个实现——虽然是直截了当的——可能会遇到同步问题。 * 你需要一些文本的全局缓冲区,在 WM_SETTEXT 实际接收和执行之前不会被删除。 * 您需要考虑会发生什么,当该缓冲区中的文本正在更新时,标签会同时读取它 * 下面的 David Heffernan 声称,在某些情况下,Windows 控件可能正在销毁和重新创建过程中(阅读:RecreateWnd 方法),因此可能会发生意想不到的后果。就个人而言 - 只要 ProgressForm 只能由 WinGDI 控制,就意味着避免所有 VCL 好东西(例如在旅途中更改组合框样式,这确实会触发 RecreateWnd,但本机 WinGDI API 不可能) - 我不明白为什么可以发生。但大卫声称它可以。自己选择,如果适合你。
但是对于更复杂的任务(或使用无窗口文本标签),您可以求助于“消息方法”——VCL 的基础。
http://www.freepascal.org/docs-html/ref/refsu31.html http://docwiki.embarcadero.com/RADStudio/XE4/en/Methods这种实现更灵活(将数据生成和数据可视化分开)并且可以说更可靠(请参阅 cmets 中的 RecreateWnd 注释)。但它需要更多样板代码。
const WM_IncCounter = WM_USER + 10;
type TMyProgressForm = class(TForm)
private
FCompleteItemsCount: integer;
procedure IncCounter(var Msg: TMessage ); message WM_IncCounter;
...
end;
procedure TMyProgressForm.IncCounter(var Msg: TMessage );
var s: string;
begin
Inc(FCompleteItemsCount, Msg.WParam);
s := Format('Done: %d of %d',
[ FCompleteItemsCount, TotalItemsCount ] );
Label1.Caption := s;
ProgressBar1.Position := FCompleteItemsCount;
end;
procedure TMyThread.WorkItemComplete;
begin
Inc(Self.UncommittedTicks);
if MyProgressForm1.Visible then // avoid ReCreateWnd inside .ShowModal
begin
PostMessage( MyProgressForm1.Handle, WM_IncCounter, Self.UncommittedTicks, 0);
Self.UncommittedTicks := 0;
end;
end;
【讨论】:
带有WM_SETTEXT
的PostMessage 是错误的。重新创建窗口打破了这一点。您最终可以在工作线程上创建窗口句柄。请记住,Handle
可以导致调用 CreateWnd
。
@DavidHeffernan 我假设在线程运行时已经显示进度窗口。因此,所有组件都已经有了句柄——它们被渲染在屏幕上。好吧,我可以重写它以使用 docwiki.embarcadero.com/Libraries/XE3/en/… 代替(使用 >0 保护)
@Arioch'The 是否已经显示没关系。可能会重新创建窗口。 WindowHandle
也不好。没有同步,所以你有一个数据竞争。当然,您会发现很难显示错误,但这并不意味着代码是正确的。您应该将您的消息发布到分配的窗口,例如,AllocateHWnd
。
@DavidHeffernan 为什么可以重新创建位于 TForm 上的 TStaticText ?这不像我会更改组合框样式。如果比赛发生,那么一两条消息将被发布到无效句柄并被丢弃。大不了。
@Arioch'The 因为它只被创建一次。看看在ShowModal
中对RecreateWnd
的调用。通常很难证明永远不会重新创建窗口句柄。这就是为什么我不费心尝试这样做的原因。另一方面,AllocateHWnd
很好理解。以上是关于如何在不同步的情况下使用 Delphi (Pre Delphi 2010) 版本更新 GUI 控件的主要内容,如果未能解决你的问题,请参考以下文章
我如何在不使用Delphi约束的情况下确定泛型类型为nil?
如何在不写入磁盘的情况下将 XML 从 Delphi 传递到 C#?
如何在不冻结 gui 的情况下运行 QProcess 的同步链?
OkHttp 如何在不使用线程的情况下通过看似同步的 HTTP 连接执行并行 HTTP 请求?