You are not logged in.
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
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
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
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
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
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
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
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
// 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
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
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;
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
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
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
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
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
Glad if I could help. :-)
Offline
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
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
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
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
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
Hi Arnaud,
once more about the TBitBtn/TButton issue from long ago. Please have a look at this picture:
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
Just another thought: The StripHotkey routine from Menus.pas probably could replace UnAmp.
Offline
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
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
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
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.
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.
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. 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
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
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
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
I wonder if this one was tried as an interfaced object, with RefCount-controlledFluent Style API (chined method calls)
Offline
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
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
Good idea.
I've added TTaskDialog.AddButton() wrapper method - thanks for the idea and patch!
See http://synopse.info/fossil/info/6cf64f86c2
Offline
Thanks for adding it! I'm already using it. :-)
Offline
Hello Arnaud,
I just noticed the following block in SynTaskDialog.pas:
{$ifdef HASINLINE}
Panel.BevelEdges := [beBottom];
Panel.BevelKind := bkFlat;
{$endif}
and am a bit confused.
HASINLINE refers to the keyword inline, right? So how is it connected to the visual appearance of a dialog?
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
1. HASINLINE matches Delphi 2007, which was the first version supporting those parameters, AFAIR.
2. You may just use http://synopse.info
Offline
Thanks for clarifying, Arnaud!
Offline
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
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
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
А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
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