#51 2013-02-04 23:03:33

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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

Offline

#52 2013-02-05 10:59:18

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

I would try to make Task a local variable in ThreadMessage. Or maybe replace TDparams: PTTDexe by a pointer to a TTaskDialog and kick the separate Task variable. This should avoid the need for synchronizing. (But NB: I'm not a threading expert. :-))

Offline

#53 2013-02-08 21:33:26

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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.

Offline

#54 2013-02-09 18:50:34

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Hello bilm,

I just tend to avoid global variables as much as possible. So I would probably expand the TTDexe record to encompass also Title, Inst, Content, Footer etc. and copy those values to a local TTaskDialog variable inside ThreadMessage:

procedure TMainForm.ThreadMessage(var Message: TMessage);
var
  TDparams: PTTDexe; // pointer for Taskdialog parameters record
  Task: TTaskDialog;
begin
  case Message.WParam of
    TH_WCDTD:
    begin  // In my threads the other params never change.
      TDparams := Pointer(Message.LParam);
      Task.Title := TDparams^.Title;
      // ...
      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;

This seems cleaner to me  - the local variable "lives" only while the dialog is shown, whereas a global variable lingers around unused most of the time. And I think you could omit the critical sections.

Best regards,
Uli

Offline

#55 2013-02-09 20:01:52

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,609
Website

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

I also try to avoid global variables, just like hell.

In a multi-thread application, they are much difficult to work with.

And when you are using some GUI process, since it is a stateless process, it is very easy to make your whole program logic confused, e.g. when two processes occur in the same time.

Offline

#56 2013-02-15 18:41:01

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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.

Offline

#57 2013-02-16 21:09:09

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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;

Offline

#58 2013-02-17 05:31:39

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,609
Website

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

As was already wrote above, what makes SynTaskDialog not thread safe is the Delphi VCL itself, which is not thread safe.
So it is a Delphi design limitation, but which is pretty common (even .Net has the same design).

Putting Dispose() in a finally..end block is much safer, as bilm notified.

Thanks for the interest!

Offline

#59 2013-02-19 22:33:06

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

// 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;

Offline

#60 2013-02-20 06:39:00

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,609
Website

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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.

By the way, I edited your posts, to add "code" tags to mark code as such.
It helps the forum indexing and presenting its content.
See http://synopse.info/forum/help.php#bbcode

Offline

#61 2013-02-21 19:16:50

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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;

Offline

#62 2013-02-22 09:59:22

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

At a glance this looks good.

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

Offline

#63 2013-02-23 21:54:07

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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 ?

Offline

#64 2013-02-25 07:14:53

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

bilm wrote:

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 ?

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.

Offline

#65 2013-02-26 21:51:30

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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.

Offline

#66 2013-02-27 07:04:26

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Glad if I could help. :-)

Offline

#67 2013-02-27 22:58:38

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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?

Offline

#68 2013-02-28 07:49:22

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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. And I guess that's the same handle you're posting your message to, so

procedure TForm1.ThreadMessage(var Message: TMessage);
var
  TDparams: ^TTaskDialogEx;    
  // ...
begin
  case Message.WParam of
    TH_SYNTD:
    begin 
      TDparams := Pointer(Message.LParam); 
      try
       TDparams^.Execute(Handle); // <== Self.Handle, i.e. the message receiver
      finally
       Dispose(TDparams); 
      end;         
    end;

    TH_LABEL: // ...

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

would probably suffice.

I don't think handling the message passing generically in the SynTaskDialog unit is feasible. Which value to use for the message that doesn't collide with application defined messages? Where shall the message be posted to? How should the message handler be implemented? I don't think that making TTaskDialog a class would change much in this respect. You have to pass around a pointer - it's irrelevant whether this pointer points to a record or is a reference to a class instance.

@ab: How about

[delphi]...[/delphi]

BBCode tags? :-)

Offline

#69 2013-03-05 21:14:29

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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);

Offline

#70 2013-03-06 10:59:58

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Hi bilm!

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.

Best regards,
Uli

Offline

#71 2013-03-08 20:06:48

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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.

Offline

#72 2013-03-14 13:16:49

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Hi Arnaud,

once more about the TBitBtn/TButton issue from long ago. Please have a look at this picture:
2qoNna4.png
and note the different baselines of the "Abbrechen" buttons. The left one is from an app compiled with the latest SynTaskDialog.pas (http://synopse.info/fossil/artifact/d45 … 47564d4bbb), the right one is with my changes - two lines added:

{$ifdef USETMSPACK}
  /// a TMS PopupMenu
  TSynPopupMenu = TAdvPopupMenu;

  TSynButtonParent = TAdvGlowButton;
  TSynTaskDialogButton = TAdvGlowButton; // <== added
{$else}
  /// a generic VCL popup menu
  TSynPopupMenu = TPopupMenu;

  TSynButtonParent = {$ifdef WITHUXTHEME}TBitBtn{$else}TButton{$endif};
  TSynTaskDialogButton = TButton; // <== added
{$endif USETMSPACK}

and two lines changed:

function AddButton(s: string; ModalResult: integer): TSynTaskDialogButton; // <== changed
var WB: integer;
begin
  s := UnAmp(s);
  WB := Form.Canvas.TextWidth(s)+52;
  dec(XB,WB);
  if XB<X shr 1 then begin
    XB := aWidth-WB;
    inc(Y,32);
  end;
  result := TSynTaskDialogButton.Create(Form); // <== changed

I think the changed version looks much better. It also incurs a tiny bit less runtime overhead - just a plain TButton instead of a TBitBtn. ;-) If you agree feel free to adapt my changes.

Best regards,
Uli

Offline

#73 2013-03-14 13:22:08

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Just another thought: The StripHotkey routine from Menus.pas probably could replace UnAmp.

Offline

#74 2013-03-14 15:22:30

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,609
Website

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Good idea.

I tried to make bottom buttons use better looking TButton component, thanks to your proposals.
See http://synopse.info/fossil/info/83eb0ceafb

Thanks!

Offline

#75 2013-03-15 14:28:52

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Hi bilm,

I just threw together a mini test program - start a new VCL app, drop a button on Form1 and replace the unit source code with this:

unit MainF;

interface

uses
  Windows,
  Messages,
  SysUtils,
  Variants,
  Classes,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  MyThread,
  StdCtrls;

const
  TH_MESSAGE = WM_USER + 5;

type
  TMyThread = class(TThread)
  private
    FHandle: HWND;
  protected
    procedure Execute; override;
  public
    constructor Create(AHandle: HWND);
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    procedure ThreadMessage(var Message: TMessage); message TH_MESSAGE;
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  SynTaskDialog;

{ TMyThread }

constructor TMyThread.Create(AHandle: HWND);
begin
  inherited Create(False);
  FreeOnTerminate := True;
  FHandle := AHandle;
end;

procedure TMyThread.Execute;
var
  pd: ^TTaskDialogEx;
begin
  New(pd);
  try
    pd^ := DefaultTaskDialog;
    pd^.Base.Title := 'Title';
    pd^.Base.Inst := 'Instruction';
    pd^.Base.Buttons := 'AAAAAAAAAA' + sLineBreak + 'BBBBBBBBBBBB';
    pd^.CommonButtons := [cbCancel];
    pd^.ButtonDef := 100;
    pd^.Flags := [tdfUseCommandLinks];
    pd^.DialogIcon := tiQuestion;
    pd^.NonNative := True;
    pd^.EmulateClassicStyle := True;
    PostMessage(FHandle, TH_MESSAGE, 0, LPARAM(pd));
  except
    Dispose(pd);
  end;
end;

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
  t: TMyThread;
begin
  t := TMyThread.Create(Handle);
end;

procedure TForm1.ThreadMessage(var Message: TMessage);
var
  pd: ^TTaskDialogEx;
begin
  pd := Pointer(Message.LParam);
  try
    pd^.Execute(Handle);
  finally
    Dispose(pd);
  end;
end;

end.

Clicking the button starts a thread which shows a task dialog. Does this work for you?

Offline

#76 2013-03-23 20:11:04

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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?

Offline

#77 2013-03-24 17:16:32

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

bilm wrote:

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?

It's just a habit for me to avoid the usage of global variables as far as possible,
and especially so when dealing with threads.

bilm wrote:

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

That's a matter of taste here. The first variant changes only one flag
in the set while the second one initializes the whole set. Which one is "better"
than the other depends on the circumstances.

bilm wrote:

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

AFAIK pd^ is zero-filled after executing New(pd), so

pd^ := DefaultTaskDialog

should do the same as

pd^.DialogIcon := tiInformation;
pd^.FooterIcon:= tfiWarning;

(or similar, depending on your DefaultTaskDialog). You could experiment with this by commenting out one line and see when it breaks.

Edit: Just checked it - New does not zero-initialize, so pd^ contains garbage, e.g. stuff like a negative Width which isn't good. wink You don't have to assign DefaultTaskDialog, but you have to make sure all fields of pd^ have sensible values.

Last edited by uligerhardt (2013-03-25 08:32:42)

Offline

#78 2013-03-30 21:14:37

bilm
Member
Registered: 2013-01-22
Posts: 17

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

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?

Offline

#79 2013-04-02 12:01:21

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

bilm wrote:

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;

I don't think this is gonna work as TTaskDialogX is just a plain Pascal object without a window handle, so it won't receive messages (AFAIK - feel free to try it nevertheless). You could of course use AllocateHWnd but IMHO this isn't worth the effort.

Offline

#80 2013-06-20 12:01:04

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Hi Arnaud,

I've got one more idea - how about adding

    property Title: string read Base.Title write Base.Title;
    property Inst: string read Base.Inst write Base.Inst;
    property Content: string read Base.Content write Base.Content;
    property Buttons: string read Base.Buttons write Base.Buttons;
    property Radios: string read Base.Radios write Base.Radios;
    property Info: string read Base.Info write Base.Info;
    property InfoExpanded: string read Base.InfoExpanded write Base.InfoExpanded;
    property InfoCollapse: string read Base.InfoCollapse write Base.InfoCollapse;
    property Footer: string read Base.Footer write Base.Footer;
    property Verify: string read Base.Verify write Base.Verify;
    property Selection: string read Base.Selection write Base.Selection;
    property Query: string read Base.Query write Base.Query;
    property RadioRes: integer read Base.RadioRes write Base.RadioRes;
    property SelectionRes: integer read Base.SelectionRes write Base.SelectionRes;
    property VerifyChecked: BOOL read Base.VerifyChecked write Base.VerifyChecked;

inside TTaskDialogEx? One could then write

Task.Inst := 'My instruction';

instead of

Task.Base.Inst := 'My instruction';

where Task is a TTaskDialogEx.

Offline

#81 2013-07-01 13:32:41

Arioch
Member
Registered: 2011-11-17
Posts: 28

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

I wonder if this one  was tried as an interfaced object, with RefCount-controlledFluent Style API (chined method calls)

Offline

#82 2015-03-12 10:34:05

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Arioch wrote:

I wonder if this one  was tried as an interfaced object, with RefCount-controlledFluent Style API (chined method calls)

AFAIK Arnaud prefers using records and old-style objects to classes for saving CPU cycles ;-) So I'm quite sure he didn't try using interfaces with their associated overhead.

Offline

#83 2015-03-12 10:37:50

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Hi Arnaud,

what do you think about adding the following little helper method:

procedure TTaskDialog.AddButton(const ACaption: string; const ACommandLinkHint: string = '');
begin
  if Buttons <> '' then
    Buttons := Buttons + sLineBreak;
  Buttons := Buttons + ACaption;
  if ACommandLinkHint <> '' then
    Buttons := Buttons + '\n' + ACommandLinkHint;
end;

It makes dealing with translated and conditionally included buttons easier (and more readable, IMHO).
One could also include similar methods for Radios and Selection.

Offline

#84 2015-03-13 08:36:06

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,609
Website

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Good idea.

I've added TTaskDialog.AddButton() wrapper method - thanks for the idea and patch!
See http://synopse.info/fossil/info/6cf64f86c2

Offline

#85 2015-03-13 08:44:40

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Thanks for adding it! I'm already using it. :-)

Offline

#86 2016-02-02 14:42:53

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Hello Arnaud,

  1. I just noticed the following block in SynTaskDialog.pas:

          {$ifdef HASINLINE}
          Panel.BevelEdges := [beBottom];
          Panel.BevelKind := bkFlat;
          {$endif}

    and am a bit confused. smile
    HASINLINE refers to the keyword inline, right? So how is it connected to the visual appearance of a dialog?

  2. In our about box I want to link to a SynTaskDialog "project page". Currently I use http://blog.synopse.info/post/2011/03/0 … ista,Seven. But the direct link given there points to an obsolete source code archive from 2011. Could you fix that?

Best regards,
Uli

Offline

#87 2016-02-02 15:07:34

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,609
Website

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

1. HASINLINE matches Delphi 2007, which was the first version supporting those parameters, AFAIR.

2. You may just use http://synopse.info

Offline

#88 2016-02-03 07:01:24

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Thanks for clarifying, Arnaud!

Offline

#89 2016-10-21 07:28:23

Dmitro25
Member
Registered: 2015-02-21
Posts: 19

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Hi.
I'm trying to use TTaskDialog with custom width (auto width is not suitable for me) and aNonNative=False.
Test code is:

  Task.Inst := 'Content';
  Task.Buttons := 'Button1'#13'Button2';
  Task.Execute([],100,[tdfUseCommandLinks],tiWarning,tfiInformation, 0, 480);

When I use it under windows XP it works well, but under Windows 7 message window width is getting too big.
I think the reason is that when you emulate (under Windows XP) the Width parameter indicates the width of the window in pixels, but when using TaskDialogIndirect cxWidth is in Dialog Units (accoding to documentation).

Offline

#90 2016-10-24 05:48:31

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Dmitro25 wrote:

I think the reason is that when you emulate (under Windows XP) the Width parameter indicates the width of the window in pixels, but when using TaskDialogIndirect cxWidth is in Dialog Units (accoding to documentation).

I just tried it (using aNonNative, I don't have XP :-)) and you seem to be right.
Reading http://stackoverflow.com/questions/6870 … nto-pixels, this might be hard to fix. When we want to set cxWidth we don't have an HWND yet. When we have an HWND (e.g. inside TaskDialogCallbackProc) it's probably too late.

Offline

#91 2016-10-25 08:43:56

Dmitro25
Member
Registered: 2015-02-21
Posts: 19

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

I think that in this case, it is sufficient to replace inside TTaskDialog.Execute this line

    Config.cxWidth := aWidth;

with

    Config.cxWidth := round(aWidth / (LOWORD(GetDialogBaseUnits()) / 4));

since it is the system dialog (is uses system font) so it is permissible to use GetDialogBaseUnits in this case.

Offline

#92 2016-10-26 11:37:29

Dmitro25
Member
Registered: 2015-02-21
Posts: 19

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

Аnother suggestion for SynTaskDialog.pas/SynTaskDialog.rc - rename resources 'btnOk' and 'btnArrow' into something a little less popular, such as 'SynTDbtnOk' and 'SynTDbtnArrow'. With present names it is likely to face with a conflict of resource names (I have faced with such situation yet).

Offline

#93 2019-10-31 10:32:06

uligerhardt
Member
Registered: 2011-03-08
Posts: 52

Re: Open Source SynTaskDialog unit for XP,Vista,Seven

FWIW: I added support for TDN_DIALOG_CONSTRUCTED and disabling buttons to my copy of SynTaskDialog. (Besides my previous changes enabling a TComponent wrapper for it.)

Offline

Board footer

Powered by FluxBB