Task Dialog: Improve hyperlink usage
See original GitHub issue⏬⏬ CLICK HERE TO SCROLL DOWN TO THE FINAL PROPOSAL ⏬⏬
Hi,
as requested by @RussKie in https://github.com/dotnet/winforms/issues/146#issuecomment-502493259, I’ve opened a new issue for this topic. Note: The listed options don’t need to change the currently proposed API in an incompatible way, so this issue can be addressed at a later time.
The native Windows Task Dialog allows to use hyperlinks in certain text elements (text/content, footer, expander) by setting the TDF_ENABLE_HYPERLINKS flag. This allows you to specify a link with the form <a href="mylink">text</a> (similar to HTML). When the user clicks on the link, the TDN_HYPERLINK_CLICKED is sent to the callback with the content of the href attribute as parameter.
This is reflected in the currently proposed API in #146: To use links, you set the TaskDialogPage.HyperlinksEnabled property to true, and then you can set e.g.
page.Text = "This text contains <a href=\"link1\">a link</a>.";
to show a link in the task dialog text:

Then, you have to handle the TaskDialogPage.HyperlinkClicked event to get notified when the user clicks a link. That event will provide you the text of the href attribute, in that case link1.
However, there are some problems with this approach (as is simply a mapping of the native API):
- Users need to know the exact syntax to specify a link (which is similar to HTML), and in source code you need to escape the double quotes.
- There is a risk that when you enable hyperlinks and then show strings from external sources, they might get misinterpreted as hyperlink even if you actually just want it to show as plain text. As noted in the documentation for
TDF_ENABLE_HYPERLINKS, this could cause security vulnerabilities, for example if you did something likeProcess.Start(e.Hyperlink)in theHyperlinkClickedevent. (A safer way is not to use links likehttps://...directly in thehref, but instead use keys likelinkA, and then in the event, compare the key to open the corresponding link.) - In order to prevent strings from external sources to be interpreted as links (or when you actually want to display a string like
<a href="..."></a>as part of the text directly without interpreting as hyperlink), you need to replace"<a"e.g. with"<\u200Ba"(zero width space), so that the task dialog interprets<as literal character. A minor downside of this is that if you then copy the dialog’s text contents with <kbd>Ctrl</kbd>+<kbd>C</kbd>, that invisible character is also copied. (I didn’t find another way for this, as the HTML/XML-like<doesn’t seem to work; and while replacing"<a"with"<<a></a>a"(which I thought previously) would fix the copy problem with the invisible space, it cannot be used as it creates empty hyperlinks that are reachable by pressing <kbd>Tab ↹</kbd>.) - When you enable
HyperlinksEnabledand there is at least one link (<a ...>) present in the text, an&character will be interpreted to specify a mnemonic char, instead of displaying it directly (which is the behavior whenHyperlinksEnabledis not set), although then actually nothing happens when you press <kbd>Alt</kbd> and the character. Therefore, you would generally need to replace&with&&in the strings whenHyperlinksEnabledis set and you have at least one link in the text. (But if you then copy the task dialog’s text contents with <kbd>Ctrl</kbd>+<kbd>C</kbd>, it will copy the escaped form (&&) instead of the displayed form (&).)
Option A
As suggested by @GSPP:
Don’t change the existing API, but add new methods that can be used to retrieve strings with the correct syntax when EnableHyperlinks is set, for example::
public static string GetHyperlinkText(string href, string text)
{
// Don't allow "</a>" in the link text because we cannot escape that.
if (text.Contains("</a>", StringComparison.OrdinalIgnoreCase))
throw new ArgumentException();
if (href.Contains("\"", StringComparison.OrdinalIgnoreCase))
throw new ArgumentException();
return $"<a href=\"{href}\">{text.Replace("&", "&&")}</a>";
}
public static string GetNonHyperlinkText(string text)
{
return text.Replace("&", "&&").Replace("<a", "<\u200Ba").Replace("<A", "<\u200BA");
}
Then, you could set the text like this:
page.Text = TaskDialog.GetHyperlinkText("myLink", "Hello World & others!") +
TaskDialog.GetNonHyperlinkText(" External text: <a href=\"test\">&</a>");

With the helper methods, you wouldn’t need to specify the exact syntax for the link and you wouldn’t need to escape special characters like < by yourself.
Option B
As suggested by @jnm2: Also don’t change the existing API but add a new method
public static string FormatWithHyperlinks(FormattableString str)
on the TaskDialog which you then could call with an interpolated string, that takes the href as argument and the link text as format string (or the other way round):
page.Text = TaskDialog.FormatWithHyperlinks(
$"Some text, then {"https://foo/":a link} & {"https://bar/":another link}; <a href=\"x&y\">no link</a>");
// Or:
page.Text = TaskDialog.FormatWithHyperlinks(
$"Some text, then {"a link":https://foo/} & {"another link":https://bar/}; <a href=\"x&y\">no link</a>");

(However, with this option there will probably need to be another method that allows to escape strings (like GetNonHyperlinkText() in Option A) if you then want to embed other strings which shouldn’t get interpreted as link.)
Thanks!
Issue Analytics
- State:
- Created 4 years ago
- Comments:64 (63 by maintainers)

Top Related StackOverflow Question
We’re proceeding with the following API, which is very much what the original proposal from @kpreisser was: kpreisser@187bdb4.
I acknowledge the API discoverability is a little vague, but having read the docs once on how to use the feature developers shouldn’t have any troubles using it again. We currently won’t be providing any helper methods to construct HREFs.
We can revisit the API and any helper functionality, if/when we detect a significant uptake of this API.
Proposed API
I’m flat out busy with prep’ing the 5.0 release, and I don’t have any capacity for this until we ship. Sorry for the delay.