#1 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-03-30 21:14:37

Thanks for the explanations.
I think I will use your implementation.
It makes the most sense to me.

A couple of posts back I asked about the possible advantages of
making TTaskDialog a class. Probably better to add a small class 
using TTaskDialogEx to the SynTaskDialog unit.

The reason is in a class you can implement a procedure
to send messages. Here is a very simplified example to
illustrate the idea.

// interface
TTaskDialogX = class
private 
 FtaskDialogX: TTaskDialogEx;
 procedure SendExecuteTD(Handle: HWND);
 procedure MessageHandler(var Msg: TMessage); message TH_MESSAGE;
protected
public
 constructor Create();
 destructor Destroy; override;
 //property TDialogX: read FTaskDialogX; ??
end;

//implementation
procedure TTaskDialogX.SendExecuteTD(Handle: HWND);
var pd: ^FTaskDialogX;
begin  
  SendMessage(Handle, TH_MESSAGE, 0, LPARAM(pd));
end;

procedure TTaskDialogX.MessageHandler(var Msg: TMessage);
var pd: ^FTaskDialogX;
begin  
  pd := Pointer(Message.LParam);
  try
    pd^.Execute(Handle);
  finally
    Dispose(pd);
  end;  
end;

But if you call SendExecuteTD from a thread using
the main form/thread handle, does the MessageHandler()
recieve it in the main thread/form?
In other words, is it thread safe?

#2 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-03-23 20:11:04

Uli wrote:

I just threw together a mini test program -

It works! No more TTaskDlg record. Cool. Implementing the
thread-safe emulation mode solution is fairly easy now.
Almost everything necessary is already in SyTaskDialog.pas.

After looking over the TMyThread declaration and .Execute,
I saw the only thing I really needed to change was adding
  pd^ := DefaultTaskDialog

I eliminated FHandle in TMyThread and used Form1.Handle
for PostMessage() in TMyThread.Execute. It works just as well.
Unless you think using FHandle is safer for some reason?

I can still use
  Include(pd^.CommonButtons, cbCancel)
but I think
  pd^.CommonButtons := [cbCancel]
is better.

And I put the SyTaskDialog unit down in the Implementation
uses clause where it belongs. Thanks.

But it was adding
  pd^ := DefaultTaskDialog
that did the trick. Except I do not understand, why?

#3 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-03-08 20:06:48

Uli wrote:

It's getting difficult to follow this endless stream of code snippets. :-)
I think the best would be if you build a small (!) project showing the wrong behaviour and post it. AFAICS there is no way to attach it on the forum, so you could use some file hoster.

Yes I agree. I will see what I can do.

I see virtually no difference between TTaskDlg and TTaskDialogEx.
They are identical except one calls the TTaskDialog execute function
directly and the other calls it from a function.
But I have to go with what works for now; one works and the other does not.

#4 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-03-05 21:14:29

Uli wrote:

One simplification you still could make in your code is to make TDparams a pointer to
TTaskDialogEx and drop your own TTaskDlg record.
Then the only additional parameter needed for calling Execute would be the ParentHandle.

It sounds great. No more TTaskDlg record.
But I cannot get it to work in XP emulation mode.

I have a test section for TaskDialog I use in the thread.
I call 2 instances of it speparated by a 4 second delay
using Sleep(4000). The 2nd instance has different
values for the fields and parameters.

When I changed TDparams to ^TTaskDialogEx, the 1st instance
form has no color, no icons, the text is all black and
the width is off the screen. The 2nd instance looks normal
but it is displayed over the 1st instance form which never
goes away.

Here is the code I used. Maybe you can tell
me what I am doing wrong.

procedure thread.execute;
var // ...
    TDparams: ^TTaskDialogEx; // declare a pointer to TTaskDialogEx  
    // ...
begin
  // ...

  New(TDparams);      
  try        
   // to fill in the fields I replaced "Task" with "Base" (TaskDialog object) 
   TDparams^.Base.Title := 'Title';
   TDparams^.Base.Inst := 'General Topic';
   TDparams^.Base.Content := 'Details';
   TDparams^.Base.Footer := 'Additional Info';
   // other fields
   
   // TCommonButtons and TTaskDialogFlags parameters are sets and 
   // need to be handled differently. I used Include() ?   
   Include(TDparams^.CommonButtons, cbOK); // or whatever button(s) you want
   
   // all other parameters are the same as before
   TDparams^.ButtonDef := mrOk;
   TDparams^.DialogIcon := tiInformation;
   TDparams^.FooterIcon := tfiInformation;
   // other parameters

   PostMessage(Form1.Handle, TH_MESSAGE, TH_SYNTD, Integer(TDparams));   
  except
    Dispose(TDparams);
  end;

   // ...

end; // end thread

And I made the change to the thread message handler as you posted:
 

TDparams^.Execute(Handle);

#5 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-02-27 22:58:38

I am thinking the final step would be to
implement it in the SynTaskDialog unit
so users do not have to do it separately.

Maybe add a procedure to the object that uses a SendMessage()
to a message handler or WinProc where Task.Execute() is called?

I know next to nothing about making a class but if making SynTaskDialog
a class would make implementation easier or would open up opportunities
for an even better solution, it might be worthwhile.

What do you think?

#6 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-02-26 21:51:30

Uli wrote:

No problem. Both pointers point to the same record (as there is only one New).
So you need only one Dispose, too. After the call to Dispose both pointers
point to garbage and shouldn't be used anymore, of course.

Understood. It's cool how that works.
I have learned a lot from you and ab. You guys have enabled
this solution to be distilled down to some reasonably easy
code to implement.

#7 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-02-23 21:54:07

Uli wrote:

FWIW: I'd get rid of the version checking (IsWinXP, IsVistaOrHigher) und just always show the task dialog from the main thread.

Yes good idea. Thanks.

procedure thread.execute;
var // ...
    TDparams: PTTaskDiag; // declare a pointer to the TTaskDiag record 
    // ...
begin
  // ...

  New(TDparams);      
  try  
      
   TDparams^.Task.Title := 'Title';
   TDparams^.Task.Inst := 'General Topic';
   TDparams^.Task.Content := 'Details';
   TDparams^.Task.Footer := 'Additional Info';
   // other fields

   TDparams^.CommonButtons := cbOK;
   TDparams^.ButtonDef := mrOk;
   TDparams^.DialogIcon := tiInformation;
   TDparams^.FooterIcon := tfiInformation;
   // other parameters

   PostMessage(Form1.Handle, TH_MESSAGE, TH_SYNTD, Integer(TDparams));     
  
  except
    Dispose(TDparams);
  end;

   // ...

end; // end thread

I am still confused about something.

In the thread, the record pointer (TDparams) is only disposed
if there is an exception. This is good because we do not want
it disposed until the thread message handler uses it.

After thread message handler uses it, the local record pointer (TDparams)
in the message handler is disposed in try/finally.

But what about the local record pointer (TDparams) in the thread ?

#8 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-02-21 19:16:50

ab wrote:

OK for the try...finally.
But why do you use a temporary Task local variable?
You do not need it, I think, since TDParams is already private to your message.

I understand now. In the thread, I use the TTaskDialog inside the
TTaskDiag record to fill in the fields.

TDparams^.Task.Title := 'Title';
// etc.  
TDparams^.Task.Footer := 'Additional Info';
// etc.

The fields and the Task.Execute() parameters are then sent to the
thread message handler.

In the message handler these same fields are received inside the
TTaskDiag record (pointer). All that is necessary is to call the
Task.Execute() within the TTaskDiag record (pointer).
A TTaskDialog variable is not needed.

  
procedure TForm1.ThreadMessage(var Message: TMessage);
var TDparams: PTTaskDiag;    
    // ...
begin
  case Message.WParam of
    TH_SYNTD:
    begin 
      TDparams := Pointer(Message.LParam); 
      try
       TDparams^.Task.Execute([TDparams^.CommonButtons], TDparams^.ButtonDef, [], TDparams^.DialogIcon,
                             TDparams^.FooterIcon, 0, 0, 0, False, False);
      finally
       Dispose(TDparams); 
      end;         
    end;

    TH_LABEL: // ...

    TH_PROGBAR: // ...
   
    // ... others
  end;
end;
Another change

The TTaskDiag record can now be moved from the
interface section to the implementation section.

In the actual code in my app I have renamed the TTaskDiag/PTTaskDiag record
to TTaskDlg/PTaskDlg to avoid confusion.

implementation

{$R *.DFM}

// uses

type  
  PTaskDlg = ^TTaskDlg;  
  TTaskDlg = record   
    Task: TTaskDialog; 
    CommonButtons: TCommonButton;
    ButtonDef: Integer;
    Flags: TTaskDialogFlag;
    DialogIcon: TTaskDialogIcon;
    FooterIcon: TTaskDialogFooterIcon;
    RadioDef,
    Width: Integer;
    ParentHandle: HWND;
    NonNative,
    EmulateClassicStyle: Boolean;
  end;

#9 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-02-19 22:33:06

// revised thread message handler 
// uses TDparams to fill Task: TTaskDialog fields 
// at one time instead of each one separately 
    TH_SYNTD:
    begin
      TDparams := Pointer(Message.LParam);
      Task := TDparams^.Task; // fills in TTaskDialog fields  

      try
       Task.Execute([TDparams^.CommonButtons], TDparams^.ButtonDef, [], TDparams^.DialogIcon,
                     TDparams^.FooterIcon, 0, 0, 0, False, False);
      finally
        Dispose(TDparams);
      end;
    end;

#10 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-02-16 21:09:09

In the message handler it would be best to put it in a try/finally:

    TH_SYNTD:
    begin 
      TDparams := Pointer(Message.LParam);

      try
       Task.Title := TDparams^.Task.Title;
       // ...
       TDparams^.Task.Execute([TDparams^.CommonButtons], TDparams^.ButtonDef, [], TDparams^.DialogIcon,
                             TDparams^.FooterIcon, 0, 0, 0, False, False);
      finally
       Dispose(TDparams); 
      end;
    end; 

and maybe enclose all in a conditional using the custom message identifier:

if Message.Msg = TH_MESSAGE then 
begin
  case Message.WParam of
  // ...
  end;
end;

#11 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-02-15 18:41:01

Uli and ab, you have me convinced about (not) using global vars.
I am going through my app now and trying to eliminate most
or all of them.

Here is an implementation of Uli's idea, no global var and some corrections.

interface

uses
  //..., 
  SynTaskDialog,
  //...;

const 
  // used by thread message handler
  TH_MESSAGE = WM_USER + 5;
  TH_SYNTD = 1;
  TH_LABEL = 2;
  TH_PROGBAR = 3;

type
  
  PTTaskDiag = ^TTaskDiag;  
  TTaskDiag = record   
    Task: TTaskDialog; 
    CommonButtons: TCommonButton;
    ButtonDef: Integer;
    Flags: TTaskDialogFlag;
    DialogIcon: TTaskDialogIcon;
    FooterIcon: TTaskDialogFooterIcon;
    RadioDef,
    Width: Integer;
    ParentHandle: HWND;
    NonNative,
    EmulateClassicStyle: Boolean;
  end;

// ...

type
  TForm1 = class(TForm)

  // ...
  procedure Button1Click(Sender: TObject);

  // ...
  protected
    { Protected declarations }   
  private
    { Private declarations }        
    procedure ThreadMessage(var Message: TMessage); message TH_MESSAGE;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  // ...
 
  // no TTaskDialog global variable is used
  //Task: TTaskDialog; 


implementation

{$R *.DFM}
{$R XP.RES} // used for themes & using SynTaskDialog
                   // there are other ways too


// all routines in the main form/thread where  
// TTaskDialog is used, declare it as a local variable
procedure TForm1.Button1Click(Sender: TObject);
var // ...
   Task: TTaskDialog;
begin
  // ...
  Task.Title := 'Title';
  Task.Inst := 'General Topic';
  Task.Content := 'Details';
  Task.Footer := 'Additional Info';
                             
  Task.Execute([cbOK],mrOk,[],tiInformation, tfiInformation, 0, 0, Handle, False, False);
  // ...
end;


// in a thread
procedure thread.execute;
var // ...
    TDparams: PTTaskDiag; // declare a pointer to the TTaskDiag record 
    // ...
begin
  // ...

  New(TDparams);
      
  try  
       // no critical section needed
   TDparams^.Task.Title := 'Title';
   TDparams^.Task.Inst := 'General Topic';
   TDparams^.Task.Content := 'Details';
   TDparams^.Task.Footer := 'Additional Info';

   if IsWinXP then
   begin
     TDparams^.CommonButtons := cbOK;
     TDparams^.ButtonDef := mrOk;
     TDparams^.DialogIcon := tiInformation;
     TDparams^.FooterIcon := tfiInformation;
     PostMessage(Form1.Handle, TH_MESSAGE, TH_SYNTD, Integer(TDparams));
   end 
   else if IsVistaOrHigher then 
     TDparams^.Task.Execute([cbOK], mrOk, [], tiInformation, tfiInformation, 0, 0, 0, False, False);

  except
    Dispose(TDparams);
  end;

   // ...

end; // end thread


// in main form thread message handler 
procedure TForm1.ThreadMessage(var Message: TMessage);
var TDparams: PTTaskDiag;
    Task: TTaskDialog;
    // ...
begin
  case Message.WParam of
    TH_SYNTD:
    begin 
      TDparams := Pointer(Message.LParam);
        
      Task.Title := TDparams^.Task.Title;
      Task.Inst := TDparams^.Task.Inst;
      Task.Content := TDparams^.Task.Content;
      Task.Buttons := TDparams^.Task.Buttons;
      Task.Radios := TDparams^.Task.Radios;
      Task.Info := TDparams^.Task.Info;
      Task.InfoExpanded := TDparams^.Task.InfoExpanded;
      Task.InfoCollapse := TDparams^.Task.InfoCollapse;
      Task.Footer := TDparams^.Task.Footer;
      Task.Verify := TDparams^.Task.Verify;
      Task.Selection := TDparams^.Task.Selection;
      Task.Query := TDparams^.Task.Query;
      Task.RadioRes := TDparams^.Task.RadioRes;
      Task.SelectionRes := TDparams^.Task.SelectionRes;
      Task.VerifyChecked := TDparams^.Task.VerifyChecked;

      TDparams^.Task.Execute([TDparams^.CommonButtons], TDparams^.ButtonDef, [], TDparams^.DialogIcon,
                     TDparams^.FooterIcon, 0, 0, 0, False, False);

      Dispose(TDparams);         
    end;

    TH_LABEL: // ...

    TH_PROGBAR: // ...
   
    // ... others
  end;
end;

I don't know if this is the best solution to making the Syn TaskDialog
emulation mode thread safe. Whatever the best solution is, it is worthwhile
because there are still lots of XP users out there. And for good reason
especially for home users.

They discover that upgrading to Win7 will cost them more than
the price of the upgrade. In most cases it means also buying a new
computer or laptop with enough resources to run it. In these difficult economic
times to spend 800-1500 USD/598-1122 EUR or more just to upgrade when XP can still do all
the things they need does not make good sense or is not possible with their budget.
Delphi programmers that understand this will continue to support XP for a while.

Some application types require using a thread. Apps involving internet connection
and use, apps that search large amounts of data and in my case write data to DVD/BD discs.
Doing these resource intensive operations using the main thread causes all the visual
controls to intermittently freeze or perform poorly and interupt the operaion with
sometimes negative results. But put it in a thread and everything runs smoothly.

It is really great to have a message dialog that works for both XP and Win7 and
looks much better than MessageDLG() and has many more customizable features.
I think I had about a half dozen different customized message dialogs which I
have now replaced with the Synpose TaskDialog. It is a big advantage
and convenience.

I have found one or two other open source TaskDialogs with an emulation mode
but they don't look very good. And I found some commercial ones. One is reasonably
priced but its emulation mode does not look as good a Synpose's and I don't think
it has as many customizable features. The others are part of a package and are expensive
and do not look better or not as good and they also do not have more features
or at least not any that are of practical use.

I still do not know exactly what causes the emulation mode to be thread unsafe.
I wish it was possible to fix it. But I will use the above solution until a
better one comes along.

The soapbox is getting wobbly so I’m getting off before I fall off.

#12 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-02-08 21:33:26

uligerhardt,
I need the global TTaskDialog var for the many procedures where I use it in the main form/thread besides in the thread message handler. In the thread I already had several global vars whose values are changed and needed a critical section so a few cases of copy/paste of the enter/leave code was easy.

I cannot see how it is possible to pass the Task.execute() parameters to the message handler using a pointer to TTaskDialog. I am no expert either as you can tell from my posts. I would be happy to find a way to eliminate the TDparams record but you would have to explain to me your idea in more detail or an example.

#13 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-02-04 23:03:33

Usually Task: TTaskDialog; is declared as a global var
in the main (form) thread in which case in secondary threads the
fields need to be protected by a critical section.

// in thread
EnterCriticalSection(CriticalSection);
Task.Title := 'Title';
Task.Inst := 'General Topic';
Task.Content := 'Details';
Task.Footer := 'Additional Info';
LeaveCriticalSection(CriticalSection);

if IsWinXP then
begin   
  New(TDparams);
  try 
   // etc. as above

#14 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-02-02 19:09:20

-------- revised code ------
ab, thanks for your advice & instruction

I no longer declare TDparams: PTTDexe as a global var in mainform/interface.

// it is declared in the thread ... 
procedure thread.execute;
var TDparams: PTTDexe;
    // ...
begin
  // ...
  
  Task.Title := 'Title';
  Task.Inst := 'General Topic';
  Task.Content := 'Details';
  Task.Footer := 'Additional Info';

  if IsWinXP then
  begin   
    New(TDparams);
    try 
      TDparams^.CommonButtons := cbOK;
      TDparams^.ButtonDef := mrOk;
      TDparams^.DialogIcon := tiInformation;
      TDparams^.FooterIcon := tfiInformation;
      PostMessage(Form1.Handle, WCDMESSAGE, TH_WCDTD, Integer(TDparams));
    except
      Dispose(TDparams);
    end;
  end // end emulation mode for XP
  else if IsVistaOrHigher then
    Task.Execute([cbOK], mrOk, [], tiInformation, tfiInformation, 0, 0, 0, False, False);

  // ...

end; // end thread

// ... and is declared in the message handler (mainform)
procedure TForm1.ThreadMessage(var Message: TMessage);
var TDparams: PTTDexe;
begin
  case Message.WParam of
    TH_WCDTD:    
    begin 
      if Message.LParam > 0 then
      begin 
        TDparams := PTTDexe(Message.LParam);
        Task.Execute([TDparams^.CommonButtons], TDparams^.ButtonDef, [], TDparams^.DialogIcon,      
                     TDparams^.FooterIcon, 0, 0, 0, False, False);
        Dispose(TDparams);
      end;    
    end; 

    // ... more WParams  
  
  end; // end case
end; // end message handler
------------------------------------

I don't understand what a "shared record" is. Could you give an example or
reference a synpose project unit where it occurs?

#15 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-01-31 20:18:30

uligerhardt and Administrator thanks for your replies.

This is some sample code of the implementation I made and tried
to describe (not very well) in my last post.

interface

uses ..., SynTaskDialog;
 
const 
  TH_MESSAGE = WM_USER + 10; 
  // for wparams in thread message handler
  TH_WCDTD = 1;
  TH_PROGBAR = 2;
  TH_LABEL1 = 3;
  TH_LABEL2 = 4;
  // etc.
  
type
  PTTDexe = ^TTDexe;
  TTDexe = record
    CommonButtons: TCommonButton;
    ButtonDef: Integer;
    Flags: TTaskDialogFlag;
    DialogIcon: TTaskDialogIcon;
    FooterIcon: TTaskDialogFooterIcon;
    RadioDef,
    Width: Integer;
    ParentHandle: HWND;
    NonNative,
    EmulateClassicStyle: Boolean;
  end;

TMainForm = class(TForm)
// ...
private
  procedure ThreadMessage(var Message: TMessage); message TH_MESSAGE;

var
  Form1: TForm1;
  // more vars ...
  TDparams: PTTDexe; // pointer for Taskdialog parameters record 

implementation

procedure TForm1.ThreadMessage(var Message: TMessage);
begin
  case Message.WParam of
    TH_WCDTD:    
    begin  // In my threads the other params never change.
      Task.Execute([TDparams^.CommonButtons], TDparams^.ButtonDef, [], TDparams^.DialogIcon, TDparams^.FooterIcon, 0, 0, 0, False, False);
      Dispose(TDparams); // release allocated memory
    end; 
    // other WParam constants for progressbar.stepit/.update, label.update, etc.
    // instead of using Synchronize(). 
  end;
end;

//=== within a thread ===//
// I do the following for each instance of SynTaskDialog:
// initialize fields
 Task.Title := 'Title';
 Task.Inst := 'General Topic';
 Task.Content := 'Details';
 Task.Footer := 'Additional Info';

// initialize taskdialog.execute() params
// allocate memory, reference TDparams & assign values
 New(TDparams); 
 TDparams^.CommonButtons := cbOK; 
 TDparams^.ButtonDef := mrOk; 
 TDparams^.DialogIcon := tiWarning;
 TDparams^.FooterIcon := tfiWarning;

// call taskdialog.execute() based on OS version
if IsWinXP then
   PostMessage(Form1.Handle, TH_MESSAGE, TH_WCDTD, Integer(TDparams)) 
else if IsVistaOrHigher then
   Task.Execute([cbOK], mrOk, [], tiWarning, tfiWarning, 0, 0, 0, False, False);
//=======================//

I don't like threads. I only use them when absolutely necessary.
I use several secondary threads but only one runs at a time.

Re: "invisible window"
Sorry about using that term to describe what I meant.
What I mean is adding a handle of type HWND to the thread declaration
then assigning it using AllocateHWnd() in thread execute. I was wondering
if the the HWND could then be used as the handle parameter in task.execute().

#16 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-01-29 22:43:24

Yes I should have said. The error only occurs in Win XP emulation mode.
I understand now emulation mode is not thread safe. Thanks.

I've implemented a thread message handler. I also made a record with a
pointer for all the params of the Task.Execute function and then a
var for the record pointer.

For each occurance in the thread I fill in the Task fields and also the
Task.Execute params using the record pointer var which I send via the PostMessage()
Lparam and dereference in the message handler for the Task.Execute call.

It all works great but I'm just an amateur hobbyist, maybe intermediate level at best.
If you or another more expert programmer has a better way to do this I'd appreciate you sharing it.

>> It specifies the parent window of the task dialog.
I'm still curious about the handle thing. Could you assign
it the handle of the invisible window of a thread?

bilm

#17 Re: Other components » Open Source SynTaskDialog unit for XP,Vista,Seven » 2013-01-24 21:58:58

The Synopse TaskDialog works great in the main thread but
when I call it in a secondary thread I get an invalid operation error:
  "Canvas does not allow drawing."
The Consts unit shows this as "SNoCanvasHandle".

I declare
  Task: TTaskDialog;
as a var in the interface of the main thread/main form then
in the secondary thread I have:
  Task.Title := 'My title';
  Task.Inst := 'Main Subject';
  Task.Content := 'Detailed information.';
  Task.Execute([cbOK], mrOk, [], tiInformation, tfiInformation, 0, 0, Handle, False, False);

The handle parameter should be the thread handle, right?

I have also tried declaring ThTask: TTaskDialog; as a var in the
secondary thread with but with the same result.

Strangley it works fine when executed inside the Delph IDE (D7).

Do I need to make a thread message handler and use Send/Postmessage?
How would I use their params to call it that way?

I hope there is a solution to this because I want to use
SynTaskDialog compared to the others available but
I need one that can run from a thread.

Thanks for any help.

bilm

Board footer

Powered by FluxBB