ToolTip with hyperlink in WPF

I created a post about "Capital gains tax percentage" and decided there should be a link to this post from Ledger commander. There is already a ToolTip at the header of the corresponding DataGridTextColumn, so I just wanted an extra link. It turned out the ToolTip is not interactable so it can not be used for hyperlink. 

First, I created a Button outside of the DataGrid because the DataGrid adds extra difficulty. 

<Button x:Name="bttnTarget" Text="test" />

<!--  If StayOpen is False then the button is not clickable  -->
<Popup Placement="Bottom"
        PlacementTarget="{Binding ElementName=bttnTarget}"
        StaysOpen="True">
    <Popup.Style>
        <Style TargetType="Popup">
            <Style.Triggers>

                <!--  Open the Popup when the mouse is over the button  -->
                <DataTrigger Binding="{Binding ElementName=bttnTarget, Path=IsMouseOver}" Value="True">
                    <Setter Property="IsOpen" Value="True" />
                </DataTrigger>

                <!--  Close the Popup when the mouse is not over the button  -->
                <DataTrigger Binding="{Binding ElementName=bttnTarget, Path=IsMouseOver}" Value="False">
                    <Setter Property="IsOpen" Value="False" />
                </DataTrigger>

                <!--  Keep open the Popup when the mouse is over the Popup  -->
                <DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="True">
                    <Setter Property="IsOpen" Value="True" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Popup.Style>
    <Border Padding="10" Background="#E6E6E6"
            BorderBrush="Gray" BorderThickness="1">
        <TextBlock>
            <Hyperlink Command="{Binding MainWindowViewModel.OpenWebPageCommand}" CommandParameter="https://scutumsoft.blogspot.com/">
                <TextBlock Text="{x:Static r:Resource.WebSite}" ToolTip="{x:Static r:Resource.WebSiteToolTip}" />
            </Hyperlink>
        </TextBlock>
    </Border>
</Popup>

The idea is to use a Popup instead of ToolTip. The Popup is placed under the Button and it is hidden in default, the IsOpen property is false. The Popup.Style will show or hide the Popup with DataTrigger. If the mouse is over the Button then the Popup is shown, if the mouse moves away from the Button then the Popup is hidden and if the mouse is over the Popup itself then it is shown again. 
The order of the DataTrigger instances is important because they change the same property. Using  DataTrigger was not easy because most of the examples on the net used code behind to show or hide the Popup.

For the background I tried to find the color of a normal ToolTip. E6E6E6 is close but not an exact match. The Popup is just a TextBlock with a Border. It is important that a Hyperlink is not a standalone tag but it can be inside the TextBlock. Unfortunately, it only format the text but does not actually go to the webpage. To do that a Command is needed. The command will call the method below.

// The WPF hyperlink does not support the opening of a web page but you can use a command to do it.
// The Hyperlink tag must be in a TextBlock tag.
private void OpenWebPage(object obj)
{
    var uri = obj as string;

    // UseShellExecute is important
    var processStartInfo = new ProcessStartInfo() { 
FileName = uri, UseShellExecute = true };
    Process.Start(processStartInfo);
}

Using the Popup in a DataGrid is not easy. The problem is that DataGridTextColumn represents the whole column and it is not suitable as a PlacementTarget. The solution is to create explicitly the column header. 

<DataGridTextColumn.HeaderTemplate>
    <DataTemplate>
        <WrapPanel>
            <TextBlock x:Name="capitalGainsTaxPercentage"
                        Text="{x:Static r:Resource.CapitalGainsTaxPercentage}"
                        TextWrapping="Wrap" />
            <!-- The Popup comes here -->
        </WrapPanel>
    </DataTemplate>
</DataGridTextColumn.HeaderTemplate>

The HeaderTemplate is given with a DataTemplate which contains the TextBlock of the header and the Popup. The Command has to be changed

Command="{Binding DataContext.MainWindowViewModel.OpenWebPageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"

because inside a DataGrid the DataContext is changed so we must grab the original DataContext by going up to the UserControl

We want to reuse the Popup so a custom control is created. 

<!--  A ToolTip is not interactable so a Popup is used  -->
<UserControl x:Class="HecklHelper.CustomControl.ToolTipWithHyperlink"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:r="clr-namespace:HecklHelper.Resources"
             x:Name="toolTipWithHyperlinkControl">
    <Popup x:Name="popup" Placement="Bottom"
           PlacementTarget="{Binding PlacementTarget, ElementName=toolTipWithHyperlinkControl}"
           StaysOpen="True">
        <Popup.Style>
            <Style TargetType="Popup">
                <Style.Triggers>

                    <!--  Open the Popup when the mouse is over the PlacementTarget  -->
                    <DataTrigger Binding="{Binding ElementName=toolTipWithHyperlinkControl, Path=PlacementTarget.IsMouseOver}" Value="True">
                        <Setter Property="IsOpen" Value="True" />
                    </DataTrigger>

                    <!--  Close the Popup when the mouse is not over the PlacementTarget  -->
                    <DataTrigger Binding="{Binding ElementName=toolTipWithHyperlinkControl, Path=PlacementTarget.IsMouseOver}" Value="False">
                        <Setter Property="IsOpen" Value="False" />
                    </DataTrigger>

                    <!--  Keep open the Popup when the mouse is over the Popup itself  -->
                    <DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="True">
                        <Setter Property="IsOpen" Value="True" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Popup.Style>

        <Border Padding="10" Background="#E6E6E6" BorderBrush="Gray"
                BorderThickness="1">
            <TextBlock>
                <Hyperlink Command="{Binding Command, ElementName=toolTipWithHyperlinkControl}" CommandParameter="{Binding HyperlinkUrl, ElementName=toolTipWithHyperlinkControl}">
                    <TextBlock Text="{x:Static r:Resource.Help}" />
                </Hyperlink>
            </TextBlock>
        </Border>
    </Popup>
</UserControl>

This is very similar to the directly used Popup but we are inside a new UserControl and we use 3 custom properties. These properties are created in the code behind.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace HecklHelper.CustomControl;

public partial class ToolTipWithHyperlink : UserControl
{
    public ToolTipWithHyperlink()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty PlacementTargetProperty =
        DependencyProperty.Register("PlacementTarget", typeof(UIElement), 
            typeof(ToolTipWithHyperlink), new PropertyMetadata(null));

    public UIElement PlacementTarget
    {
        get => (UIElement)GetValue(PlacementTargetProperty);
        set => SetValue(PlacementTargetProperty, value);
    }

    public static readonly DependencyProperty HyperlinkUrlProperty =
        DependencyProperty.Register("HyperlinkUrl", typeof(string), 
            typeof(ToolTipWithHyperlink), new PropertyMetadata(string.Empty));

    public string HyperlinkUrl
    {
        get => (string)GetValue(HyperlinkUrlProperty);
        set => SetValue(HyperlinkUrlProperty, value);
    }

    public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), 
                typeof(ToolTipWithHyperlink), new PropertyMetadata(null));

    public ICommand Command
    {
        get => (ICommand)GetValue(CommandProperty);
        set => SetValue(CommandProperty, value);
    }
}

Now it is possible to use the new component.

<DataGridTextColumn.HeaderTemplate>
    <DataTemplate>
        <WrapPanel>
            <TextBlock x:Name="capitalGainsTaxPercentage"
                        Text="{x:Static r:Resource.CapitalGainsTaxPercentage}"
                        TextWrapping="Wrap" />
            <hhc:ToolTipWithHyperlink HyperlinkUrl="https://scutumsoft.blogspot.com/2024/10/tax-for-unrealized-capital-gains.html" PlacementTarget="{Binding ElementName=capitalGainsTaxPercentage}"
                                        Command="{Binding DataContext.MainWindowViewModel.OpenWebPageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
        </WrapPanel>
    </DataTemplate>
</DataGridTextColumn.HeaderTemplate>

To make our life easier we can create yet another component which contains the TextBlock also.

<DataGridTextColumn.HeaderTemplate>
    <DataTemplate>
        <hhc:TextBlockWithToolTipWithHyperlink Command = "{Binding DataContext.MainWindowViewModel.OpenWebPageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
          HyperlinkUrl = "https://scutumsoft.blogspot.com/2024/10/tax-for-unrealized-capital-gains.html"
          Text = "{x:Static r:Resource.CapitalGainsTaxPercentage}" />
    </DataTemplate>
</DataGridTextColumn.HeaderTemplate>

Adding a hyperlink to a webpage is one minute but the ToolTip is not interactable in WPF so a new method had to be implemented by scratch. This is the humble result.







Previous Post Next Post

Contact Form