OpenDialog does not show up in Delphi MultiThreaded application
Asked Answered
L

2

2

i tried to use the openDialog in new thread but it made so strange behavior ..

if i put the if opendialog.execute then in the create constructor like this :

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;

the opendialog open normally but when i put it in the execute producer of the thread it didn't open at all !!

i'm so beginner in threads so can any one explain for me what happened ?

Thanks in advance .

[Edit]

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.
Lunde answered 16/7, 2011 at 19:54 Comment(1)
I think that you actually want to get the list of files from the dialog in the main thread and then start the worker thread to do the hashing. If you need to update status from the thread synchronize or queue it to the main thread.Durer
H
7

It works when you call it in the constructor because the constructor runs in the context of the calling thread (ie the main thread), whereas Execute() runs in the context of the worker thread. The VCL is not thread-safe, and UI components in particular rarely if ever work correctly outside of the main thread. If you want to display an open dialog in a thread, then have your TThread.Execute() method either:

1) call TThread.Synchronize() to access the TOpenDialog within the context of the main thread.

2) call the Win32 API GetOpenFileName() function directly instead. API dialogs can be safely used in threads when used properly.

Hypognathous answered 17/7, 2011 at 1:41 Comment(6)
I'm not convinced that option 2 is a good idea. File dialogs are usually modal. How do you disable the rest of the UI that lives in the main thread?Durer
i did what you said ( as i did understand ) it works but it still hangs the GUI how i can prevent this lagging so i can use the GUI normally while the thread hashing the files which opened by the TOpenDialog i think it's hanging while processing the MD5it function and Thanks for your respond .Lunde
@Tattah - In the edit, with only 'Synchronize' in 'Execute' all your code runs in the main thread. You'd probably show the dialog in the main thread, MD5 the file in a thread, then synchronize the list view.Pleuropneumonia
@Sertac Akyuz : i understand you suggested me to put the md5 hashing function in the execute code then i use the synchronize the listview ?Lunde
@David: Windows synchronizes access to windows (HWNDs) through that window's message pump, so it will see the WM_ENABLE message in the GUI thread even if EnableWindow is called from a background thread. That's how cross-application message passing works. We've been launching all of our common dialogs (open/save/browse/print/color) in background threads for years, and as long as you're careful about VCL access it works fine. For cases like this though, TThread.Synchronize will tend to be be easier and have identical behavior.Oneman
Or maybe it is because of COM :-DSennit
B
0

I just hit a similar case in Delphi XE2 but I suppose it can happen in 2009 too.

Delphi was uplifted to use new Windows Vista open/save dialogs, which are COM-based components instead of old flat C-style API. https://msdn.microsoft.com/library/windows/desktop/bb776913.aspx

I was adding a debug logging function, and it used to call PromptForFileName if the dump file name was not set yet. The function never did a thing.

I traced into Delphi RTL/VCL internals and reached function TCustomFileSaveDialog.CreateFileDialog in Dialogs.pas.

The said function was calling into Microsoft COM API, but then - oops! - just suppressed all the errors that could be returned. I used CPU Window in the Delphi Debugger and saw EAX register having $800401f0 error, which stands for 'COM was not initialized yet' situation. https://msdn.microsoft.com/en-us/library/cc704587.aspx

I knew for sure that the said function worked perfectly in other places of the program, so I supposed it was - unexpectedly for me - executing in a separate thread. That was the case. In your case you DO KNOW already you have multithreading issues, and I think you may try the direct solution, instead of the workaround with 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;
Brunn answered 29/1, 2018 at 10:12 Comment(2)
The main GUI thread already calls Co(Un)Initialize for you. If you invoke the dialog in the context of a worker thread, it must call them manually. It is safe to use COM objects in a worker thread as long as you follow COM's rules regarding that.Hypognathous
I just show the specific reason the author's code failed, rather than vague "VCL was not designed for". It also is not intuitive that TOpenDialog requires COM. BTW, is there somewhere in Delphi docs explicit warranty that COM apartment is always initialized for ANY VCL application, including simplistic Hello World one? I wonder. Using Synchronize workaround definitely has its advantages too, but the more options the better.Sennit

© 2022 - 2024 — McMap. All rights reserved.