You are not logged in.
Read the comments on http://blog.barrkel.com/2010/01/one-lin … elphi.html. So much for that. :-)
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.)
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.
No problem here, too! :-)
I implemented the ideas from my last post. Are you interested in it?
What has been the "Buttons" member variable is now a read/write property "ButtonsString" tied to an array of TTaskDialogButton named "Buttons". I could rename the two to "Buttons" (as before) and "ButtonItems" or similar if you want it to to be completely backward compatible. (Although I like the current names much more. )
FWIW: The changes also made it quite trivial to write a wrapper component for visual design (similar to the VCL TTaskDialog), so I did that.
I tried it with VCL's TTaskDialog before my first post and it worked as I wanted.
Looking through SynTaskDialog.pas, I see that it would be a bit tough to add it in it's current state - the contents of TTaskDialog.Buttons would have to evolve into a mini-DSL :-)
I had the idea of internally storing an array of TTASKDIALOG_BUTTON instead of a string (for both emulated and native mode) and then make TTaskDialog.Buttons a read/write property. What do you think?
Is there a way to manually assign button IDs instead of the automatic 100, 101, 102, ...? To be more concrete: I'd like to have a command link with IDCANCEL so that it acts as Cancel button - especially reacting to the Escape key.
Thanks for clarifying, Arnaud!
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
Thanks for adding it! I'm already using it. :-)
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.
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.
Allow me one more question: what about initializing the TTaskDialog to an empty object? Doesn't it have to be done? What if there is some garbage data from previous calls?
I guess you're right there. I always use TTaskDialogEx like this:
procedure Test;
var
Task: TTaskDialogEx;
begin
Task := DefaultTaskDialog;
//...
end;
, so never had problems from this. You could probably also use:
procedure Test;
var
Task: TTaskDialog;
begin
Task := DefaultTaskDialog.Base;
//...
end;
Hello Arnauld and reddwarf!
FWIW: I just gave this version of the unit a quick test in my VCL app and had one compiler error, probably because I always compile with TYPEDADDRESS ON. The call to TaskDialogIndirect in line 930 yields an error "E2010 Inkompatible Typen: 'PBOOL' und 'Pointer'". I fixed this by introducing a local variable "bVerifyChecked: BOOL" and call TaskDialogIndirect like this:
bVerifyChecked := VerifyChecked;
if TaskDialogIndirect(@Config,@result,@RadioRes,@bVerifyChecked)=S_OK then
begin
VerifyChecked := bVerifyChecked;
exit; // error (mostly invalid argument) -> execute the VCL emulation
end;
After that, I didn't notice any errors.
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.
Cool. I'm gonna make use of that as soon as time permits. :-)
Thank you!
One minor nitpick: AFAICS you could make "s" a const parameter now. :-)
Hello again!
Some time ago the problem occurred that array of resourcestrings can't be used in units inside packages. You solved that by using arrays of strings instead, e.g.
const
TD_BTNS: array[TCommonButton] of string = (
SMsgDlgOK, SMsgDlgYes, SMsgDlgNo, SMsgDlgCancel, SMsgDlgRetry,
SCloseButton);
instead of
const
TD_BTNS: array[TCommonButton] of pointer = (
@SMsgDlgOK, @SMsgDlgYes, @SMsgDlgNo, @SMsgDlgCancel, @SMsgDlgRetry,
@SCloseButton);
Unfortunately we have to assign the resourcestrings at runtime (with HookResourceString or similar), so this doesn't work. An alternative solution considered back then was using functions like
function TD_BTNS(cb: TCommonButton): Pointer;
begin
case cb of
cbOK: Result := @SMsgDlgOK;
cbYes: Result := @SMsgDlgYes;
cbNo: Result := @SMsgDlgNo;
cbCancel: Result := @SMsgDlgCancel;
cbRetry: Result := @SMsgDlgRetry;
cbClose: Result := @SCloseButton;
else Result := nil;
end;
end;
which seem to work under these circumstances. What do you think about changing this?
Cheers,
Uli
The easiest solution from my perspective would be to just delete the call to StripHotkey in AddButton - or call it controlled by a flag - and have the translators avoid clashing shortcuts. They have to consider them anyway because NonNative might be false and the system dialog shows the CommonButton shortcuts.
Hi ab,
I recently noticed that emulated SynTaskDialogs suppress the shortcuts for CommonButtons, i.e. the user can't press y to "click" he Yes button.
System provided dialog:
Emulated dialog:
For reference please see Windows Explorer's deletion confirmation which has shortcuts:
I checked the SynTaskDialog source and noticed the explicit call to StripHotkey at the beginninig of AddButton and wondered why it is there?
Best regards,
Uli.
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.
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.
These articles helped me with a similar task: http://itinerantdeveloper.blogspot.de/s … ansparency. I came up with this:
procedure NormalizeRect(var r: TRect);
var
t: Integer;
begin
if r.Left > r.Right then
begin
t := r.Right;
r.Right := r.Left;
r.Left := t;
end;
if r.Top > r.Bottom then
begin
t := r.Bottom;
r.Bottom := r.Top;
r.Top := t;
end;
end;
// AlphaBlendRect: draws an alphablended rectangle:
procedure AlphaBlendRect(DC: HDC; const ARect: TRect; AColor: TColor; AIntensity: Byte);
var
Bitmap: TBitmap;
BlendParams: TBlendFunction;
rClip, rBlend: TRect;
function GetBlendColor: TRGBQuad;
function PreMult(b: Byte): Byte;
begin
Result := (b * AIntensity) div $FF;
end;
var
cr: TColorRef;
begin
cr := ColorToRGB(AColor);
Result.rgbBlue := PreMult(GetBValue(cr));
Result.rgbGreen := PreMult(GetGValue(cr));
Result.rgbRed := PreMult(GetRValue(cr));
Result.rgbReserved := AIntensity;
end;
begin
GetClipBox(DC, rClip);
NormalizeRect(rClip);
rBlend := ARect;
NormalizeRect(rBlend);
if not IntersectRect(rBlend, rClip, rBlend) then
Exit;
Bitmap := TBitmap.Create;
try
Bitmap.PixelFormat := pf32bit;
Bitmap.SetSize(1, 1);
PRGBQuad(Bitmap.ScanLine[0])^ := GetBlendColor;
BlendParams.BlendOp := AC_SRC_OVER;
BlendParams.BlendFlags := 0;
BlendParams.SourceConstantAlpha := $FF;
BlendParams.AlphaFormat := AC_SRC_ALPHA;
Windows.AlphaBlend(
DC, rBlend.Left, rBlend.Top, rBlend.Right - rBlend.Left, rBlend.Bottom - rBlend.Top,
Bitmap.Canvas.Handle, 0, 0, 1, 1,
BlendParams);
finally
Bitmap.Free;
end;
end;
// AlphaBlendPolygon: draws an alphablended polygon:
procedure AlphaBlendPolygon(DC: HDC; const APoints: array of TPoint; AColor: TColor; AIntensity: Byte);
procedure SetClip(APoints: array of TPoint); // pass APoints by value
var
rgn: HRGN;
begin
LPtoDP(DC, APoints[0], Length(APoints));
rgn := CreatePolygonRgn(APoints[0], Length(APoints), ALTERNATE);
try
ExtSelectClipRgn(DC, rgn, RGN_AND);
finally
DeleteObject(rgn);
end;
end;
var
SaveIndex: Integer;
rClip: TRect;
begin
SaveIndex := SaveDC(DC);
try
SetClip(APoints);
GetClipBox(DC, rClip);
AlphaBlendRect(DC, rClip, AColor, AIntensity);
finally
RestoreDC(DC, SaveIndex);
end;
end;
which works quite well for me.
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?
Just another thought: The StripHotkey routine from Menus.pas probably could replace UnAmp.
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
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
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? :-)
Glad if I could help. :-)
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.
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.
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
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. :-))
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.
I'm no experts in threads, too, but this sounds good to me.
>> 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?
IIUC you show the task dialog in the context of the main thread, so the parent window probably should be from the main thread too.
Isn't there some kind of "active" window - maybe the one the user used to start the thread?
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."
I guess that's when you show an emulated task dialog? E.g on a XP system?
That probably won't work because the emulation is a VCL dialog and the VCL is not threadsafe.
The handle parameter should be the thread handle, right?
No. Have a look at the declaration - its type is HWND. It specifies the parent window of the task dialog.
As far as I tested it, my commit did work as expected under Windows 7 and XP.
Some comments:
SynTaskDialog doesn't compile for me - in line 791 EmulateClassicStyle is used instead of aEmulateClassicStyle.
Line 726 (the first "Panel.Color := clWhite;") should be deleted, otherwise EmulateClassicStyle also shows a white background.
At the moment, with EmulateClassicStyle = True there is a line below the white panel where there should be none IMHO.
With EmulateClassicStyle = False there are even two lines next to each other. Something like
Panel := TPanel.Create(Form);
Panel.Parent := Form;
Panel.Align := alTop;
Panel.BorderStyle := bsNone;
Panel.BevelOuter := bvNone;
if not aEmulateClassicStyle then begin
Panel.BevelEdges := [beBottom];
Panel.BevelKind := bkFlat;
Panel.Color := clWhite;
{$ifdef WITHUXTHEME}
Panel.ParentBackground := false; // clWhite not used otherwise
{$endif}
end;
Par := Panel;
might be better.
Regarding the OK/Cancel/... buttons: Compare this (current version)
and this, where I changed AddButton to create and return a plain TButton:
. Notice the ugly text placement in the OK and Abbrechen (= Cancel) buttons of the first image?
(NB: Just replacing "TSynButton" with "TButton" in AddButton wouldn't be enough because ButtonClick specifically checks for TSynButton.)
Somewhat related: I guess
Sender is TSynButton
is more idiomatic in Delphi than
Sender.InheritsFrom(TSynButton)
Thanks for taking care of my ideas. I threaten to bring some more ideas if I've got some time again.
Enjoy your holidays!
Thanks, I did up to now. Even watched some French movies although I know next to no French. Luckily there are subtitles :-)
About RecreateAsPopup, as far as I understood the VCL implementation of this method (from a quick overview), it is similar to CreateParented...
Perhaps you can confirm it.But it may definitively be the reason of RecreateAsPopup.
As you stated in this ticket (I did not notice its existence!), we may safely use a plain Create instead. Is it right?
AFAIK it works as expected, and fix the issue.
RecreateAsPopup just does not make any difference with a plain Create + ShowModal.
I can't test right now (I'm on vacation for Hof film festival :-)) but AFAIR CreateParented showed the problematic behaviour under Windows 7 + D2007 and XP + D2006 whereas plain Create (or CreateNew) worked in both cases. RecreateAsPopup probably only makes a difference if the owner/parent form is non-VCL.
FYI the RecreateAsPopup method is not available in Delphi 5-6-7 (at least).
So I would rather use the previous implementation, using the CreateParented() constructor.
I see. But CreateParented seems to exist for embedding controls in other controls and not for establishing a hierarchy between top-level windows/forms. This is probably the cause for http://synopse.info/fossil/tktview?name=01395e5932. Can you reproduce that?
- What may be the specifications for "EmulateClassicStyle" new parameter?
I'd like a task dialog under Windows XP without themes to look similar to a task dialog under Vista/7 without themes. EmulateClassicStyle tries to achieve that by making the emulation look more like Windows Classic.
For example, this is a task dialog under Windows 7 with Contrast #1 theme:
These are emulated dialogs with EmulateClassicStyle = True:
and EmulateClassicStyle = False:
There are quite a few differences: fonts, background color, border sizes etc. And there are still a lot of details missing.
- What is the purpose of those TSynCommandLinkParent and such types (sounds like redundant code)?
I introduced TSynTaskDialogButton because the OK, Cancel and similar buttons shouldn't be TBitBtns. They don't ever show bitmaps (on a task dialog) and look slightly wrong if one uses TBitBtns (the text is one or two pixels to low, which stands out if the button is only 22 pixels tall).
At the moment TSynCommandLinkParent and TSynCommand are just relicts of my "not so smart move" and could be replaced by TSynButton. One could probably use them to integrate a button class that looks more like a "real" command link.
- May I receive the refreshed "arrow" icon?
Done.
- Why did you overwrite the TForm constructor?
In the original code you created a TForm instance and casted it to TTaskDialogForm which it wasn't. In the modified code a real TTaskDialogForm is created and no cast is needed.
If you decide to go with the RecreateAsPopup way and skip the CreateParented, you could also directly call TTaskDialogForm.CreateNew and remove the overridden constructor.
Using ActiveControl instead of my tricky Form.Tag use is well done.
Thanks. I wasn't sure if it's compatible with D<2006 and TMS.
uligerhardt wrote:That move wasn't too smart, as all of these are used elsewhere in your framework. I've reinstated them and will send you an update later.
I would not have deleted those lines either.
I guessed so. :-)
I'm waiting for your update.
I sent it this morning to your bouchez-info address. Slow electrons today...
3. BitmapOK (and its accompanying res entry) weren't used, so I removed them.
5. I split TSynButton in two: TSynCommandLink for the command links and TSynButton for the other buttons. ...
6. The DropDown stuff in TSynButton wasn't used, so I commented it out.
That move wasn't too smart, as all of these are used elsewhere in your framework. I've reinstated them and will send you an update later.
Hello Arnaud,
I have made some modifications to SynTaskDialog.pas and will send them to you per mail as I can't attach it here. You probably won't like all of the changes but feel free to take what you like.
I wrote a little wrapper TTaskDialogEx to make the usage of TTaskDialog.Execute a bit easier if you want to change just one or two of the default arguments. There is a global variable DefaultTaskDialogEx: TTaskDialogEx which is initialized to the default values from TTaskDialog.Execute.
You could use it like this:
Customize DefaultTaskDialogEx in some initialization section, FormCreate or similar, maybe
DefaultTaskDialogEx.EmulateClassicStyle := True;
or
DefaultTaskDialogEx.AlwaysEmulate := True;
Use it like
procedure TForm1.Button3Click(Sender: TObject);
const
Icons: array[TMsgDlgType] of SynTaskDialog.TTaskDialogIcon = (
tiWarning, tiError, tiInformation, tiQuestion, tiNotUsed);
var
Task: SynTaskDialog.TTaskDialogEx;
Res: Integer;
begin
Task := DefaultTaskDialogEx;
// Base is a TTaskDialog
Task.Base.Title := 'My Test App';
Task.Base.Inst := 'Saving application settings';
// more Base settings...
Task.CommonButtons := [cbOK, cbCancel];
Task.ButtonDef := 101;
Task.Flags := [tdfUseCommandLinks];
// Notice: no mentioning of DialogIcon, FooterIcon, RadioDef, Width, ...
Res := Task.Execute(Handle);
end;
All other changes only apply if emulation is active.
New parameter EmulateClassicStyle in TTaskDialog.Execute. If True then the emulation is drawn more like the original Microsoft dialog under Windows Classic. (This is not finished yet - the verification checkbox and footer text aren't handled correctly yet.)
If EmulateClassicStyle = False I've reverted the icon border to your taste (24 instead of 10, see post http://synopse.info/forum/viewtopic.php?pid=2777#p2777).
BitmapOK (and its accompanying res entry) weren't used, so I removed them.
Removed the shadow in the arrow bitmap (see http://synopse.info/forum/viewtopic.php?pid=2785#p2785).
I split TSynButton in two: TSynCommandLink for the command links and TSynButton for the other buttons. The latter ones are now never TBitBtns because this looks ugly for OK buttons etc (wrong text base line) and the Glyph isn't used anyway.
The DropDown stuff in TSynButton wasn't used, so I commented it out.
Fixed the E2201 error by using an array of strings (see http://synopse.info/forum/viewtopic.php?pid=2850#p2850).
By overloading TTaskDialogForm.Create (calling CreateNew) some ugly casting could be removed.
Used ActiveControl instead of the Tag/FormShow trick.
Fixed the TabOrder issue (see http://synopse.info/forum/viewtopic.php?pid=2778#p2778).
Uli
Hello Arnaud,
if you have an app that uses SynTaskDialog and has runtime packages enabled you get compiler errors
E2201: Need imported data reference ($G) to access 'SMsgDlgOK' from unit 'SynTaskDialog'
for TD_BTNS and TD_ICONS_IDENT. I don't know where this comes from - probably vcl.dpk is compiled with the "wrong {$IMPORTEDDATA ...} directive. There is a (German) post that seems to deal with this problem: http://www.delphipraxis.net/72509-resou … ckage.html and this post: https://forums.embarcadero.com/message. … ageID=6844.
I've found two workarounds: Either
replace the pointer arrays with string arrays like
TD_ICONS_IDENT: array[TTaskDialogIcon] of string =(
'', SMsgDlgWarning, SMsgDlgConfirm, SMsgDlgError, SMsgDlgInformation,
'', SMsgDlgInformation);
and remove some LoadResString calls or
replace the pointer arrays with functions like
GetIconIdent(TTaskDialogIcon): Pointer
.
Best regards,
Uli.
Once more a PITA. :-)
Another thing: I'd suggest you remove the shadow from the arrow bitmap because it looks ugly when you have configured your system to a non-gray clBtnFace. (PNG probably isn't an option.)
Do see what I mean compare these pictures:
Original Windows dialog:
Emulation, arrow with shadow:
Emulation, arrow without shadow:
The last screenshot was produced with this BTNARROW (can't append the real bmp, unfortunately):
Looks better, doesn't it?
Best regards,
Uli.
Thank you, Arnaud,
this almost looks like the original!
Maybe you could insert
Panel.BevelOuter := bvNone;
Panel.BevelEdges := [beBottom];
Panel.BevelKind := bkFlat;
after the
Panel.BorderStyle := bsNone;
line to make the border around the white panel more authentic.
Two more things: If I show a task dialog like this:
var
Task: TTaskDialog;
Res: Integer;
begin
Task.Inst := 'Saving application settings';
Task.Content := 'This is the content';
Task.Buttons := 'Registry\nStore settings in registry' + sLineBreak + 'XML\nStore settings in XML file';
Res := Task.Execute([cbOK, cbCancel], 101, [tdfUseCommandLinks], tiQuestion, tfiInformation, 0, 0, Handle, CheckBoxUseEmulation.Checked);
end;
the "real" dialog has a close button in the upper right corner, while the emulated one hasn't. Is there a way to have the button in the emulation, too?
With the same code, the tab order of the OK and Cancel buttons is reversed. This could fixed like this:
Declare
CurrTabOrder: TTabOrder;
next to Par and Panel.
Put the line
CurrTabOrder := Panel.TabOrder;
at the beginning of the
if (byte(aCommonButtons) <> 0)...
branch.
Set
result.TabOrder := CurrTabOrder;
in AddButton.
BTW: I hate to be a PITA, but I like my GUI to be "right". :-)
Regarding the border width of the emulation: Just compare the border around the icon of an emulated dialog
with a Windows task dialog, either themed
or unthemed
(Replacing the 24's after WIN_ICONS[aDialogIcon] with 10's (and 48 with 20) should work. )
The look depends on the Windows theming - using plain Windows controls has its pros and cons.
I wanted the unit to stay "plain" VCL ready, and the resulting layout is expected to use Windows theming.
The fact that your unit uses standard VCL controls and respects theming is one of the things I like about it.
What I mean is that the emulated task dialogs look like the MS dialogs from a standard Windows Vista/7 Aero. Because of this they look a bit out of place on on XP with Luna and on every classic/unthemed Windows (even on unthemed Vista/7). If the emulation looked more like a Windows Classic task dialog it would blend in better on those platforms.
The fact that there are some many arguments in Execute() is, as far as I remember, that... there is no obvious reason unless that they will provide default parameters.
We may pass all arguments as TTaskDialog fields, but since it is an object/record, there is no default values to them (when allocated on the stack, it is not filled with zero).
OK, I see.
You can use the fossil tickets, but for issues.
For suggestions and enhancements, you can better use this forum, and we will discuss here.
OK.
Thanks for your interest.
Thanks for your component. :-)
If I have more suggestions, would http://synopse.info/fossil/rptview?rn=1 / http://synopse.info/fossil/tktnew be the place to report them?
One other question: Is there a reason that there are so many arguments to TTaskDialog.Execute? I think SynTaskDialog would be easier to use if most of these parameters were fields of the TTaskDialog record instead.
And it might be good to replace TTaskDialogForm.FormShow with an override of DoShow.
Hello ab!
Some more nitpicking about the look of your emulation. :-)
I'd like to look the emulated dialogs more like the real Windows 7 task dialog under Windows Classic (i.e. unthemed), that is similar to this:
.
no white panel
different fonts
narrower borders
etc.
I have played a bit with SynTaskDialog.pas and got a look that I like better (even if I'm not done yet). Would you be interested in integrating this in the official unit as an compile time or runtime option?
Another thing: I'd suggest you remove the shadow from the arrow bitmap because it looks ugly when you have configured your system to a non-gray clBtnFace. (PNG probably isn't an option.)
Best regards,
Uli.