Recent

Author Topic: TTaskDialog musings  (Read 1714 times)

Arioch

  • Sr. Member
  • ****
  • Posts: 415
TTaskDialog musings
« on: September 20, 2022, 05:36:13 pm »
Windows Vista introduced new kind of "common dialogs" to ask user about their choices, Task Dialogs.  A good brief article wih examples is at https://specials.rejbrand.se/TTaskDialog/

Being more generic and feature-full iteration of MessageDlg/CreateMessageDialog
https://lazarus-ccr.sourceforge.io/docs/lcl/dialogs/createmessagedialog.html

Especially after Lazarus expanded MessageDlg to include random-amount of developer-captioned button - those dialogs tend to become non-proportional,  ultra-wide while thin.

Windows TaksDialog tends to be veritcally-stacked instead, usually making a more harmonious window shape.

mORMot project developed pre-Vista TaskDialog implementation using stock VCL components, later it was adapted to us LCL components and donated to LCL/Lazarus.

https://wiki.freepascal.org/TTaskDialog

That Lazarus dialog however lacks a lot of Windows features, which is understandable given emphasis on cross-platform and least-common-base reuquiments of LCL.

Simple example: compare https://lazarus-ccr.sourceforge.io/docs/lcl/dialogs/ttaskdialog-5.html with https://docwiki.embarcadero.com/Libraries/Sydney/en/Vcl.Dialogs.TTaskDialog_Events


This became especially apparent in debugger interface thread: https://forum.lazarus.freepascal.org/index.php/topic,60624.0.html

So, i guess, some reflections of how TTaskDialog could be enhanced are to be collected somewhere, rather than pollute debugger's thread.



Microsoft decided that in "Vista age" every user knows what is hyperlink in the internet, and i think they do, They probably also thought, that if explanation is required for common problem - the chance is that number of explanations for different terms are. IOW, one single Help button is not many enough.

This or other reasoning, there is explicitly no HELP buttons in TaskDialog API. More so, eveyr button, even with ModalResult set to zero, would close the dialog.

Martin pointed to a hack making a non-closing button:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.dlgDWARFButtonClicked(Sender: TObject;
  2.   AModalResult: TModalResult; var ACanClose: Boolean);
  3. begin
  4.   ACanClose := AModalResult <> 0; // or any other MR assigned to non-closing buttons
  5. end;  
  6.  

This seems to work indeed, but still has drawbacks:

1. This violates MS intentions, that are materialized in hundred of TaskDialog use cases in dozens of programs. Users would NOT expect that Help button is "harmless".

Even if they did - violating UI guidelines is questionable choice always.

But as a temptorary hack it works.

2. Such a button would have no visual cue it is fundamentally different from other, "final" buttons. It is okay when "horizontal bottom bar of buttons" mode is used, as in traditional dialogs. People got used that Help button there is a non-closing kind. But if the verical arrangement of button is selected (AKA Command buttons mode, it would probably be confusing)


3. The "Command buttons" have two texts in them, Large "caption" and smaller "hint" text. In Delphi.
Such an "extended caption" could explain to suer the Help button is not going to close the dialog. Bad design, but at least user is forewarned.

Since LCL dialog is rooted in pure LCL for "Least common denominator", though, it does not have command button hint at all.


To backport it - 2-captioned buttons have to be added to core LCL.



4. Microsoft made a dubious choice about hyperlinks - the texts in the dialog explicitly support the <A> tag and none of other HTML features.

https://learn.microsoft.com/en-us/windows/win32/api/commctrl/ns-commctrl-taskdialogconfig

This solution seems practical, but also a very ad-hoc-ish. Non-elegant.

This precludes "normal" connecting help systems to the LCL dialog. The dialog needs "on hyperlink clicked" event - whic hcan not be implemented while the dialog does not know what hyperlink is.

One can implement a custom one-tag-HTML parser and x-platform rendred rendered for TTaskDialog, but seems a weird one-use-only solution.

Once can also ascend some basic HTML parser and rendered into LCL code. Examples:

However...

a) this would create the reverse problems, UNIX devs would start using all of implemented HTML features, ruining uniformity of the dialogs, and then suddenly becoming incompatible with Windows (see attached pic below)
b) LCL Core team would probably not be very happy to include a Chrome-grade modern parser into the core and have mantainance burder, not to make LCL dependent on Chromium. There is a commercial library Delphi HTML controls, and it seems working good, but it was their intention to use HTML renderer as their foundation. LCL is not that, though, and hardly wishes to be HTML-rewritten.
c) LCL Core team would probably not be happy to have some very-limited-subset rendered enshrined, and then see repeteative complains "your HTML sucks, obsolete, need to be extended".

Potentially it would be posisble just to add the 3rd imlpementation to TTaskDialog, based entirely on THTMLFrame or partially on TJvHTLabel, and let users who need full functionality include it in their projects. But then TaskDialog would be exposing API that is just ignored on core implementation.



The current code raises some eyesbrows.

Code: Pascal  [Select][+][-]
  1.   if (WidgetSet.GetLCLCapability(lcNativeTaskDialog) = LCL_CAPABILITY_YES) and
  2.  
  3. .....
  4.  
  5. // D:\fpcupdeluxe\lazarus\lcl\include\interfacebase.inc
  6.  
  7. function TWidgetSet.GetLCLCapability(ACapability: TLCLCapability): PtrUInt;
  8. begin
  9.   case ACapability of
  10.     lcCanDrawOutsideOnPaint,
  11.     lcNeedMininimizeAppWithMainForm,
  12.     lcApplicationTitle,
  13.     lcFormIcon,
  14.     lcModalWindow,
  15.     lcReceivesLMClearCutCopyPasteReliably,
  16.     lcSendsUTF8KeyPress,
  17.     lcEmulatedMDI,
  18.     lcNativeTaskDialog,
  19.     lcAccelleratorKeys: Result := LCL_CAPABILITY_YES;
  20.   else
  21.     Result := LCL_CAPABILITY_NO;
  22.   end;
  23. end;
  24.  
  25.     /// if TRUE, the Delphi emulation code will always be used
  26.     NonNative: boolean;
  27.  
  28. ...
  29.  
  30.   // use our native (naive?) Delphi implementation
  31.   Dialog.Emulated := true;
  32.   Dialog.Form := TEmulatedTaskDialog.CreateNew(Application);
  33.  

1. I wonder if some standard  layer could be added to override `GetLCLCapability` in runtime, for testing purposes. I could patch LCL and rcecompile, but this does not feel good, and it won't provide for changing behavior in runtime.
2. The use of term "native" is the comments is perverse here. Everywhere else "Native" means native to the platform. Buttons rendered by Windows GDI or MacOX Cocoa in SWT and VCL, as opposed to custom-rendered by Java Swing and FMX. It adds a cognitive burden... Especially since the nameing of the variables/parameters is conventional.
3. The GetLCLCapability asserts every platform to have this, Vista-only API. Which infers it is demanded for every toolkit but Windows to remove that flag.
Yet, better, function TWin32WidgetSet.GetLCLCapability in lazarus\lcl\interfaces\win32\win32object.inc does not override it. But hey, only Vista+ had API, XP- does not, the dialog was precisely developed for Windows 2000 and XP !!! The TaskDialog itself explicitly queries OS API, if the interface function is present or not. This flag makes no sense in LCL Capabilities!

Code: Pascal  [Select][+][-]
  1. function TQtWidgetSet.GetLCLCapability(ACapability: TLCLCapability): PtrUInt;
  2. begin
  3.   case ACapability of
  4. ....
  5.     lcNativeTaskDialog: Result := {$ifdef MSWINDOWS} LCL_CAPABILITY_NO {$else} LCL_CAPABILITY_YES {$endif};
  6.  

Qt, LOL, it is not correct, you have to check Windows version!!!
And why do you say Windows does not have native API and non-Windows do - when it is exactly the opposite???

And GTK2 and GTK3 and what over toolkits should do it too.
And Cocoa, i am looking at you, why don't you opt-out and pretend having Windows-specific API ???

And then it has
Code: Pascal  [Select][+][-]
  1.       {$IFDEF MSWINDOWS}
  2.       if WidgetSet.GetLCLCapability(lcNativeTaskDialog) = LCL_CAPABILITY_NO then
  3.         ARadioOffset := 1
  4.       else
  5.         ARadioOffset := 0;
  6.       {$ELSE}
  7.       ARadioOffset := 1;
  8.       {$ENDIF}
  9.  

equivalent code:

Code: Pascal  [Select][+][-]
  1.      
  2.    const ARadioOffset = {$IFDEF MSWINDOWS} 0 {$ELSE} 1 {$ENDIF};
  3.  



There is probably a lot more to the TaskDialog. For example Windows/Delphi have some OnNavigate event which is nowhere documented.

Arioch

  • Sr. Member
  • ****
  • Posts: 415
Re: TTaskDialog musings
« Reply #1 on: September 20, 2022, 05:41:04 pm »
LCL implementation puts command buttons above radiobuttons, native Windows implementaiton does the opposite.

Compare attached pic with https://forum.lazarus.freepascal.org/index.php?action=dlattach;topic=60624.0;attach=50866;image

Also, the HELP button has to be default, and it is on Windows (albeit the highlight is thinly faint), but not in the LCL-only dialog
« Last Edit: September 20, 2022, 05:47:47 pm by Arioch »

Arioch

  • Sr. Member
  • ****
  • Posts: 415
Re: TTaskDialog musings
« Reply #2 on: September 20, 2022, 06:26:10 pm »
Wrong implementation of [tfUseCommandLinks,tfUseCommandLinksNoIcon] set of flags.

tfUseCommandLinks says "use big buttons with green arrows"
tfUseCommandLinksNoIcon says "use big buttons but no green arrows"

When both flags are set, Windows still draws arrows, but LCL does not.

wp

  • Hero Member
  • *****
  • Posts: 10300
Re: TTaskDialog musings
« Reply #3 on: September 20, 2022, 07:15:45 pm »
LCL implementation puts command buttons above radiobuttons, native Windows implementaiton does the opposite.
Can't confirm what you are saying about command buttons above radiobuttons:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button12Click(Sender: TObject);
  2. begin
  3.   with TTaskDialog.Create(Self) do
  4.     try
  5.       Caption := 'My Application';
  6.       Title := 'The Title';
  7.       Text := 'This is the text of the TaskDialog.';
  8.       Flags := Flags + [tfUseCommandLinks];
  9.       with TTaskDialogButtonItem(Buttons.Add) do
  10.       begin
  11.         Caption := 'Command Item 1';
  12.         ModalResult := mrYes;
  13.       end;
  14.       with TTaskDialogButtonItem(Buttons.Add) do
  15.       begin
  16.         Caption := 'Command Item 2';
  17.         ModalResult := mrNo;
  18.       end;
  19.       with RadioButtons.Add do
  20.         Caption := 'This is one option';
  21.       with RadioButtons.Add do
  22.         Caption := 'This is another option';
  23.       with RadioButtons.Add do
  24.         Caption := 'This is a third option';
  25.       CommonButtons := [tcbYes, tcbNo];
  26.       MainIcon := tdiQuestion;  //tdiNone; // There is no tdiQuestion  -- wp: this comment is for Delphi only
  27.       if Execute then
  28.         if ModalResult = mrYes then
  29.           beep;
  30.     finally
  31.       Free;
  32.     end;
  33. end;
  34.  
Same as with Delphi.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 8487
  • Debugger - SynEdit - and more
    • wiki
Re: TTaskDialog musings
« Reply #4 on: September 20, 2022, 07:27:37 pm »
Can't confirm what you are saying about command buttons above radiobuttons:
Same as with Delphi.

Depends on native Windows:

Flags := [tfUseHiconMain,tfAllowDialogCancellation,tfUseCommandLinks]

vs

Flags := [tfAllowDialogCancellation,tfUseCommandLinks]


similar tfUseCommandLinksNoIcon

wp

  • Hero Member
  • *****
  • Posts: 10300
Re: TTaskDialog musings
« Reply #5 on: September 20, 2022, 08:12:11 pm »
Depends on native Windows:

Flags := [tfUseHiconMain,tfAllowDialogCancellation,tfUseCommandLinks]

vs

Flags := [tfAllowDialogCancellation,tfUseCommandLinks]


similar tfUseCommandLinksNoIcon
Whatever combination of flags I use in the LCL TaskDialog, the command links never are above the radiobuttons, in the code that I posted above. I don't understand how a screenshot such as in reply #1 could have been achieved. Is it the Windows version? I am on Win 11...

Arioch

  • Sr. Member
  • ****
  • Posts: 415
Re: TTaskDialog musings
« Reply #6 on: September 20, 2022, 10:31:06 pm »
wp, if yo use Windows you have either to use lower level API than TTaskDialog - or patch LCL
or find Win2000/WinXP :-)

The easiest way for you would be to patch

// D:\fpcupdeluxe\lazarus\lcl\include\interfacebase.inc
function TWidgetSet.GetLCLCapability(ACapability: TLCLCapability): PtrUInt;

and make it return xxX_NO  on lcNativeTaskDialog

or to patch a place in TTaskDialog.DoExecute that checks it (that is what i do)

anyway, i alread y made a patch to fix it - https://forum.lazarus.freepascal.org/index.php/topic,60624.msg454399.html#msg454399

wp

  • Hero Member
  • *****
  • Posts: 10300
Re: TTaskDialog musings
« Reply #7 on: September 20, 2022, 11:15:50 pm »
I am completely lost... You say: "LCL implementation puts command buttons above radiobuttons, native Windows implementation does the opposite." My test shows: No - the command buttons are below the radiobuttons which is like native Windows, according to your statement. Why should I have to patch anything then?

Arioch

  • Sr. Member
  • ****
  • Posts: 415
Re: TTaskDialog musings
« Reply #8 on: September 20, 2022, 11:21:13 pm »
I am completely lost... You say: "LCL implementation puts command buttons above radiobuttons, native Windows implementation does the opposite." My test shows: No - the command buttons are below the radiobuttons which is like native Windows, according to your statement. Why should I have to patch anything then?

Beause you run Vista implementation. LCL Native implemetation (emulation) is only called when native Windows API is not available.

Since you use Win11, the API is there and native implementation is executed.

Simple test, change Lazarus IDE to some exotic language, then run Test Dialog in the IDE and see if the standard OK/Cancel butto nwould be translated to overrode Lazarus language or remain in Windows language

dialogs.TTaskDialog it NOT implementation, it is merely API bridge, that choses implementation on the run
« Last Edit: September 20, 2022, 11:23:38 pm by Arioch »

Arioch

  • Sr. Member
  • ****
  • Posts: 415
Re: TTaskDialog musings
« Reply #9 on: September 20, 2022, 11:29:45 pm »
wp, trace into Execute method and see for yourself, you call Windows API

d:\fpcupdeluxe\lazarus\lcl\lcltaskdialog.pas

line 800

Code: Pascal  [Select][+][-]
  1.   if (WidgetSet.GetLCLCapability(lcNativeTaskDialog) = LCL_CAPABILITY_YES) and
  2.     Assigned(TaskDialogIndirect) and not aNonNative and
  3.      not (tdfQuery in aFlags) and (Selection='') then begin
  4.     Dialog.Emulated := False;
  5.     // use Vista/Seven TaskDialog implementation (not tdfQuery nor Selection)
  6. ...
  7.  

that is where you go


also TTaskDialogButtonItem(Buttons.Add) is not needed, the Add already returns typecaseted value

wp

  • Hero Member
  • *****
  • Posts: 10300
Re: TTaskDialog musings
« Reply #10 on: September 20, 2022, 11:53:53 pm »
Ran my test program under Linux, and now I do see the exchanged command and radio buttons. OK - you're correct. I applied your patch and committed it.

I still wonder why you were able to see this on Windows. Your screenshot does not look like you're on XP.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 8487
  • Debugger - SynEdit - and more
    • wiki
Re: TTaskDialog musings
« Reply #11 on: September 21, 2022, 01:06:27 am »
Actually on Windows, the button can be above or below:

Flags := [tfUseHiconMain,tfAllowDialogCancellation,tfUseCommandLinks]
vs
Flags := [tfAllowDialogCancellation,tfUseCommandLinks]

Strangely tfUseHiconMain affects their location and style

PascalDragon

  • Hero Member
  • *****
  • Posts: 4769
  • Compiler Developer
Re: TTaskDialog musings
« Reply #12 on: September 21, 2022, 09:09:18 am »
I still wonder why you were able to see this on Windows. Your screenshot does not look like you're on XP.

Because Arioch forced the LCL to use the emulation code instead of the Vista+ dialog.

wp

  • Hero Member
  • *****
  • Posts: 10300
Re: TTaskDialog musings
« Reply #13 on: September 21, 2022, 09:39:50 am »
Actually on Windows, the button can be above or below:

Flags := [tfUseHiconMain,tfAllowDialogCancellation,tfUseCommandLinks]
vs
Flags := [tfAllowDialogCancellation,tfUseCommandLinks]

Strangely tfUseHiconMain affects their location and style
As I already wrote above I cannot verify this: When I use these Flags settings in my test procedure from reply #3 nothing changes with respect to arrangement of radio and command buttons. But maybe I am missing some point. Can you post some code so that I can reproduce your observation?

wp

  • Hero Member
  • *****
  • Posts: 10300
Re: TTaskDialog musings
« Reply #14 on: September 21, 2022, 09:52:50 am »
I still wonder why you were able to see this on Windows. Your screenshot does not look like you're on XP.

Because Arioch forced the LCL to use the emulation code instead of the Vista+ dialog.
Ah, now I understand what he means...

 

TinyPortal © 2005-2018