【问题标题】:OpenDialog does not show up in Delphi MultiThreaded applicationOpenDialog 不显示在 Delphi 多线程应用程序中
【发布时间】:2011-10-06 21:14:18
【问题描述】:

我尝试在新线程中使用 openDialog 但它的行为很奇怪..

如果我将 if opendialog.execute 然后放在创建构造函数中,如下所示:

constructor TChatMemberThread.Create(Name: string);
begin
  inherited Create(True);
  FName := Name;
  FreeOnTerminate := True;
  Opendialog := TOpenDialog.create(nil);
  if opendialog.execute then
    for 0 to opendialog.filescount do
      somecodeishere
    end;
end;

opendialog 正常打开,但是当我把它放在线程的执行生产者中时,它根本没有打开!!

我是线程的初学者,所以任何人都可以为我解释发生了什么吗?

提前致谢。

[编辑]

unit Unit1;

interface

uses
  Classes,Dialogs,ComCtrls,SysUtils,DCPcrypt2, DCPmd5;

type
  TOpenThread = class(TThread)
  private
    { Private declarations }
    OpenDlG : TOpenDialog;
    LI : TListItem;
    Procedure Openit;
    Function MD5it(Const filename : string ):String;
  protected
    procedure Execute; override;
  Public
    Constructor Create;
    Destructor Destroy;Override;
  end;

implementation
uses Main;

{ TOpenThread }

Constructor TOpenThread.Create;
begin
 inherited Create(True);
 opendlg := Topendialog.Create(nil);
 opendlg.Filter := 'All Files | *.*';
 openDlg.Options := [OfAllowMultiSelect];
 openDlg.InitialDir := GetCurrentDir;
end;

Destructor TOpenThread.Destroy;
begin
  OpenDlg.Free;
  inherited;
end;

Function TOpenThread.MD5it(Const filename : string ):String;
var
hash : TDCP_MD5 ;
Digest: array[0..15] of byte;
Source: TFileStream;
i: integer;
s: string;
begin
  Source:= nil;
    try
      Source:= TFileStream.Create(filename,fmOpenRead);  // open the file specified by Edit1
    except
      MessageDlg('Unable to open file',mtError,[mbOK],0);
    end;
    if Source <> nil then
    begin
      Hash:= TDCP_MD5.Create(nil);         // create the hash
      Hash.Init;                                   // initialize it
      Hash.UpdateStream(Source,Source.Size);       // hash the stream contents
      Hash.Final(Digest);                          // produce the digest
      Source.Free;
      s:= '';
      for i:= 0 to 15 do
        s:= s + IntToHex(Digest[i],2);
    Result := s;
    end;
    Hash.Free;
end;

Procedure TOpenThread.Openit;
var
I: Integer;
begin
 if opendlg.Execute then
 begin
   for I := 0 to openDlg.Files.Count - 1 do begin
      LI := Form1.LV1.Items.Add;
      LI.Caption := ExtractFileName(openDlg.Files[i]);
      LI.SubItems.Add(MD5it(openDlg.Files[i]));
      LI.SubItems.add(openDlg.Files[i]);
   end;
  //SB.Panels[0].Text := ' '+IntToStr(LV1.Items.Count)+' File(s)';
  OpenDlg.Free;
end;end;

procedure TOpenThread.Execute;
begin
  { Place thread code here }
  Synchronize(OpenIt);
end;

end.

【问题讨论】:

  • 我认为您实际上想从主线程中的对话框中获取文件列表,然后启动工作线程进行哈希处理。如果您需要从线程同步更新状态或将其排队到主线程。

标签: multithreading delphi delphi-2009 fileopendialog topendialog


【解决方案1】:

当你在构造函数中调用它时它会起作用,因为构造函数在调用线程(即主线程)的上下文中运行,而 Execute() 在工作线程的上下文中运行。 VCL 不是线程安全的,尤其是 UI 组件很少在主线程之外正常工作。如果您想在线程中显示打开的对话框,请使用您的 TThread.Execute() 方法:

1) 在主线程的上下文中调用TThread.Synchronize() 来访问TOpenDialog

2) 直接调用 Win32 API GetOpenFileName() 函数。如果使用得当,API 对话框可以安全地在线程中使用。

【讨论】:

  • 我不相信选项 2 是个好主意。文件对话框通常是模态的。如何禁用主线程中的其余 UI?
  • 我按照你说的做了(正如我所理解的那样)它可以工作,但它仍然挂起 GUI 我如何防止这种滞后,所以我可以在线程散列由打开的文件时正常使用 GUI TOpenDialog 我认为它在处理 MD5it 函数时挂起,感谢您的回复。
  • @Tattah - 在编辑中,“执行”中只有“同步”,所有代码都在主线程中运行。您可能会在主线程中显示对话框,在线程中显示 MD5 文件,然后同步列表视图。
  • @Sertac Akyuz :我知道你建议我将 md5 散列函数放在执行代码中,然后我使用同步列表视图?
  • @David:Windows 通过该窗口的消息泵同步对窗口 (HWND) 的访问,因此即使从后台线程调用 EnableWindow,它也会在 GUI 线程中看到 WM_ENABLE 消息。这就是跨应用程序消息传递的工作原理。多年来,我们一直在后台线程中启动所有常用对话框(打开/保存/浏览/打印/颜色),只要您注意 VCL 访问,它就可以正常工作。但是对于这样的情况,TThread.Synchronize 会更容易并且具有相同的行为。
【解决方案2】:

我刚刚在 Delphi XE2 中遇到过类似的情况,但我想它也可能在 2009 年发生。

Delphi 被提升为使用新的 Windows Vista 打开/保存对话框,这是基于 COM 的组件,而不是旧的平面 C 样式 API。 https://msdn.microsoft.com/library/windows/desktop/bb776913.aspx

我正在添加一个调试日志记录功能,如果尚未设置转储文件名,它会调用PromptForFileName。该函数从未做过任何事情。

我追踪到 Delphi RTL/VCL 内部,并在Dialogs.pas 中找到了function TCustomFileSaveDialog.CreateFileDialog

上述函数正在调用 Microsoft COM API,但随后 - 糟糕! - 只是抑制了所有可能返回的错误。我在 Delphi 调试器中使用 CPU Window 并看到 EAX 寄存器出现 $800401f0 错误,代表“COM 尚未初始化”的情况。 https://msdn.microsoft.com/en-us/library/cc704587.aspx

我确信上述函数在程序的其他地方工作得很好,所以我想它——对我来说出乎意料——在一个单独的线程中执行。情况就是这样。在您的情况下,您已经知道您有多线程问题,我认为您可以尝试直接解决方案,而不是使用 Synchronize 的解决方法

uses ActiveX, Windows;
constructor TChatMemberThread.Create(Name: string);
var COM_Init_Here: Boolean;
begin
  inherited Create(True);
  FName := Name;
  FreeOnTerminate := True;
  COM_Init_Here := S_OK = CoInitialize(nil); // ***
  try                                        // ***        
    Opendialog := TOpenDialog.create(nil);
    if opendialog.execute then
      for 0 to opendialog.filescount do
        somecodeishere
      end;
  finally                                    // ***
    if COM_Init_Here then CoUnInitialize();  // ***
  end;                                       // ***
end;

【讨论】:

  • GUI 主线程已经为你调用了Co(Un)Initialize。如果您在工作线程的上下文中调用对话框,它必须手动调用它们。只要遵守 COM 的相关规则,在工作线程中使用 COM 对象是安全的。
  • 我只是展示了作者的代码失败的具体原因,而不是模糊的“VCL 不是为设计而设计的”。 TOpenDialog 需要 COM 也是不直观的。顺便说一句,Delphi 文档中是否有明确保证 COM 单元始终为任何 VCL 应用程序初始化,包括简单的 Hello World 应用程序?我想知道。使用Synchronize 解决方法肯定也有其优势,但选择越多越好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-07
  • 1970-01-01
  • 2018-04-18
  • 2023-03-27
  • 1970-01-01
相关资源
最近更新 更多