如何使用Delphi从线程更新GUI

8
我正在使用Delphi匿名线程来执行代码。 在线程中间,需要进行一些GUI更新,例如改变标签等。
如果我在线程内部执行这些操作,则更改会生效。但是一旦线程停止,它们就会消失,然后应用程序会给出旧的窗口处理程序错误...(这是预料之中的)
我尝试使用Synchronize(updateui);方法来执行更改(将它们移至单独的函数中),但是同步会出现错误E2066 Missing operator or semicolon,这对我来说毫无意义...
我已经搜索了一页又一页的内容,它们似乎都是这样调用的,但是当我这样做时,我得到了上面的错误...
我的调用方式有误吗?
代码:
TThread.CreateAnonymousThread(
procedure
 begin
 main.Enabled:=false;
 Loading.show;
 label52.caption:=getfieldvalue(datalive.users,'users','credit_amount','user_id',user_id );
 CoInitialize(nil);
   if (length(maskedit1.Text)=maskedit1.MaxLength) and (pingip(serverip)=true) then
    begin
       if (strtofloat(label52.caption)>0) then
        begin
           ....do some work....

           Synchronize(updateui);
        end
       else Showmessage('Insufficient Funds. Please add funds to continue.');
    end
   else if (length(maskedit1.Text)<>maskedit1.MaxLength) then
    begin
     Showmessage('ID Number not long enough.');
    end
   else
    begin
     Showmessage('Could not connect to the server. Please check your internet connection and try again.');
    end;
 CoUnInitialize;
 loading.close;
 main.Enabled:=true;
end).start;

更新用户界面:

procedure TMain.updateui;
var
birthdate,deathdate:TDate;
begin
Panel3.Show;

Label57.Caption := 'Change 1';
Label59.Caption := 'Change 2';
Label58.Caption := 'Change 3';
Label60.Caption := 'Change 4';
Label62.Caption := 'Change 5';
Label70.Caption := 'Change 6';


ScrollBox1.Color := clwhite;
end;

3
你绝不能在任何情况下从不同的线程访问控件,无论是写入还是读取! - Sir Rufo
@SirRufo 我必须......数据库操作之间有很多变化。每个数据库操作都需要一些时间,如果我不在线程中执行它,主GUI看起来就像是挂起的。所以我必须全部或者不进行线程处理... - Marcel
我并没有说“不要使用线程”。我只是说“不要从与主线程不同的任何线程访问(读/写)控件”。 - Sir Rufo
1个回答

11

使用 TThread.Synchronize 并传递另一个匿名函数。然后您可以在匿名函数中调用 updateui

使用TThread.Synchronize并传递另一个匿名函数,然后在该匿名函数中调用updateui

TThread.CreateAnonymousThread(
  procedure
  begin
    // do whatever you want

    TThread.Synchronize(nil,
      procedure
      begin
        updateui();
      end);

   // do something more if you want
  end
).Start();

同步通常很昂贵(涉及性能)。只有在确实需要时才执行。如果您将updateui方法扩展到减少绘制操作,可以提高性能。

这可以通过调用SendMessage以及使用WM_SETREDRAW来实现:

procedure StopDrawing(const Handle: HWND);
const
  cnStopDrawing = 0;
begin
  SendMessage(Handle, WM_SETREDRAW, cnStopDrawing, 0);
end;

procedure ContinueDrawing(const Handle: HWND);
const
  cnStartDrawing = 1;
begin
  SendMessage(Handle, WM_SETREDRAW, cnStartDrawing, 0);

  // manually trigger the first draw of the window
  RedrawWindow(Handle, nil, 0,
    RDW_ERASE or RDW_FRAME or RDW_INVALIDATE or RDW_ALLCHILDREN);
end;

updateui()函数顶部加入StopDrawing()的调用,然后在updateui()函数末尾加入ContinueDrawing()的调用。ContinueDrawing()的调用应该在finally块中。这将确保即使在执行updateui()过程中发生异常,窗口也会被绘制。

例如:

procedure TMain.updateui;
begin
  try
    StopDrawing(Handle);

    Panel3.Show;

    Label57.Caption := 'Change 1';
    Label59.Caption := 'Change 2';

    // ...
  finally
    // Code under finally gets executed even if there was an error
    ContinueDrawing(Handle);
  end;
end;

太棒了,你获得了金星 :),非常感谢,它运行得很好。 - Marcel
Marcel,如果你读了之前问题的评论,这个答案正是我们告诉你的内容... - whosrdaddy
1
我认为 cnStartDrawing = 0; 应该改为 cnStartDrawing = 1;。 - Jason Bemis
多么昂贵啊!匿名线程真的很昂贵,我知道从匿名线程更新我的备忘录表单和同步中会损失多少性能! - peiman F.

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