182

I've seen several suggestions, that you can add hyperlink to WPF application through Hyperlink control.

Here's how I'm trying to use it in my code:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        mc:Ignorable="d" 
        x:Class="BookmarkWizV2.InfoPanels.Windows.UrlProperties"
        Title="UrlProperties" Height="754" Width="576">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <Grid>
            <ScrollViewer ScrollViewer.VerticalScrollBarVisibility="Auto" Grid.RowSpan="2">
                <StackPanel >
                    <DockPanel LastChildFill="True" Margin="0,5">
                        <TextBlock Text="Url:" Margin="5" 
                            DockPanel.Dock="Left" VerticalAlignment="Center"/>
                        <TextBox Width="Auto">
                            <Hyperlink NavigateUri="http://www.google.co.in">
                                    Click here
                            </Hyperlink>   
                        </TextBox>                      
                    </DockPanel >
                </StackPanel>
            </ScrollViewer>        
        </Grid>
        <StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Margin="0,7,2,7" Grid.Row="1" >
            <Button Margin="0,0,10,0">
                <TextBlock Text="Accept" Margin="15,3" />
            </Button>
            <Button Margin="0,0,10,0">
                <TextBlock Text="Cancel" Margin="15,3" />
            </Button>
        </StackPanel>
    </Grid>
</Window>

I'm getting following error:

Property 'Text' does not support values of type 'Hyperlink'.

What am I doing wrong?

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Arsen Zahray
  • 24,367
  • 48
  • 131
  • 224

11 Answers11

375

If you want your application to open the link in a web browser you need to add a HyperLink with the RequestNavigate event set to a function that programmatically opens a web-browser with the address as a parameter.

<TextBlock>           
    <Hyperlink NavigateUri="http://www.google.com" RequestNavigate="Hyperlink_RequestNavigate">
        Click here
    </Hyperlink>
</TextBlock>

In the code-behind you would need to add something similar to this to handle the RequestNavigate event:

private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
    // for .NET Core you need to add UseShellExecute = true
    // see https://learn.microsoft.com/dotnet/api/system.diagnostics.processstartinfo.useshellexecute#property-value
    Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
    e.Handled = true;
}

In addition you will also need the following imports:

using System.Diagnostics;
using System.Windows.Navigation;

It will look like this in your application:

oO

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
eandersson
  • 25,781
  • 8
  • 89
  • 110
72

In addition to Fuji's response, we can make the handler reusable turning it into an attached property:

public static class HyperlinkExtensions
{
    public static bool GetIsExternal(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsExternalProperty);
    }

    public static void SetIsExternal(DependencyObject obj, bool value)
    {
        obj.SetValue(IsExternalProperty, value);
    }
    public static readonly DependencyProperty IsExternalProperty =
        DependencyProperty.RegisterAttached("IsExternal", typeof(bool), typeof(HyperlinkExtensions), new UIPropertyMetadata(false, OnIsExternalChanged));

    private static void OnIsExternalChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        var hyperlink = sender as Hyperlink;

        if ((bool)args.NewValue)
            hyperlink.RequestNavigate += Hyperlink_RequestNavigate;
        else
            hyperlink.RequestNavigate -= Hyperlink_RequestNavigate;
    }

    private static void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
    {
        Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
        e.Handled = true;
    }
}

And use it like this:

<TextBlock>
    <Hyperlink NavigateUri="https://stackoverflow.com"
               custom:HyperlinkExtensions.IsExternal="true">
        Click here
    </Hyperlink>
</TextBlock>
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
Arthur Nunes
  • 6,718
  • 7
  • 33
  • 46
33

If you want to localize string later, then those answers aren't enough, I would suggest something like:

<TextBlock>
    <Hyperlink NavigateUri="https://speechcentral.net/">
       <Hyperlink.Inlines>
            <Run Text="Click here"/>
       </Hyperlink.Inlines>
   </Hyperlink>
</TextBlock>
Ivan Ičin
  • 9,672
  • 5
  • 36
  • 57
31

Hyperlink is not a control, it is a flow content element, you can only use it in controls which support flow content, like a TextBlock. TextBoxes only have plain text.

H.B.
  • 166,899
  • 29
  • 327
  • 400
27

Note too that Hyperlink does not have to be used for navigation. You can connect it to a command.

For example:

<TextBlock>
  <Hyperlink Command="{Binding ClearCommand}">Clear</Hyperlink>
</TextBlock>
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
24

IMHO the simplest way is to use new class inherited from Hyperlink:

/// <summary>
/// Opens <see cref="Hyperlink.NavigateUri"/> in a default system browser
/// </summary>
public class ExternalBrowserHyperlink : Hyperlink
{
    public ExternalBrowserHyperlink()
    {
        RequestNavigate += OnRequestNavigate;
    }

    private void OnRequestNavigate(object sender, RequestNavigateEventArgs e)
    {
        Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
        e.Handled = true;
    }
}
StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Ilya Serbis
  • 21,149
  • 6
  • 87
  • 74
24

I used the answer in this question and I got an issue with it.

It return exception: {"The system cannot find the file specified."}

After a bit of investigation. It turns out that if your WPF application is CORE, you need to change UseShellExecute to true.

This is mentioned in Microsoft docs:

true if the shell should be used when starting the process; false if the process should be created directly from the executable file. The default is true on .NET Framework apps and false on .NET Core apps.

So to make this work you need to added UseShellExecute and set it to true:

Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri){ UseShellExecute = true });
Maytham Fahmi
  • 31,138
  • 14
  • 118
  • 137
  • I had this same issue and came here to see how to fix it, but it still persists with `UseShelExecute = true` any idea why? – High Plains Grifter Dec 12 '19 at 09:31
  • @HighPlainsGrifter so just to understand you made UseShelExecute = true but still have the same issue? if that is the case try to run your visual studio in admin mode (run as administrator) I think this process need to access resources that required admin privilege. And this true thing is only valid for .core projects. let me know if that helps so I can update my answer. – Maytham Fahmi Dec 12 '19 at 09:40
  • yes, I am running as my admin user and have `Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true });` and get the error "System.ComponentModel.Win32Exception: 'The system cannot find the file specified'" when I try to follow the hyperlink – High Plains Grifter Dec 12 '19 at 12:00
  • @HighPlainsGrifter not sure else what it can be, if you have source code I am welling to spend some time debugging it but wont promise any thing. :) – Maytham Fahmi Dec 12 '19 at 22:40
  • Not really shareable code sadly - I have just had to not use a hyperlink for now rather than hold the project up. Thanks Anyway. – High Plains Grifter Dec 17 '19 at 15:03
  • @HighPlainsGrifter no problem any other time ;) – Maytham Fahmi Dec 17 '19 at 15:07
4

I liked Arthur's idea of a reusable handler, but I think there's a simpler way to do it:

private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
    if (sender.GetType() != typeof (Hyperlink))
        return;
    string link = ((Hyperlink) sender).NavigateUri.ToString();
    Process.Start(link);
}

Obviously there could be security risks with starting any kind of process, so be carefull.

Grant
  • 1,608
  • 16
  • 14
4

One of the most beautiful ways in my opinion (since it is now commonly available) is using behaviours.

It requires:

  • nuget dependency: Microsoft.Xaml.Behaviors.Wpf
  • if you already have behaviours built in you might have to follow this guide on Microsofts blog.

xaml code:

xmlns:Interactions="http://schemas.microsoft.com/xaml/behaviors"

AND

<Hyperlink NavigateUri="{Binding Path=Link}">
    <Interactions:Interaction.Behaviors>
        <behaviours:HyperlinkOpenBehaviour ConfirmNavigation="True"/>
    </Interactions:Interaction.Behaviors>
    <Hyperlink.Inlines>
        <Run Text="{Binding Path=Link}"/>
    </Hyperlink.Inlines>
</Hyperlink>

behaviour code:

using System.Windows;
using System.Windows.Documents;
using System.Windows.Navigation;
using Microsoft.Xaml.Behaviors;

namespace YourNameSpace
{
    public class HyperlinkOpenBehaviour : Behavior<Hyperlink>
    {
        public static readonly DependencyProperty ConfirmNavigationProperty = DependencyProperty.Register(
            nameof(ConfirmNavigation), typeof(bool), typeof(HyperlinkOpenBehaviour), new PropertyMetadata(default(bool)));

        public bool ConfirmNavigation
        {
            get { return (bool) GetValue(ConfirmNavigationProperty); }
            set { SetValue(ConfirmNavigationProperty, value); }
        }

        /// <inheritdoc />
        protected override void OnAttached()
        {
            this.AssociatedObject.RequestNavigate += NavigationRequested;
            this.AssociatedObject.Unloaded += AssociatedObjectOnUnloaded;
            base.OnAttached();
        }

        private void AssociatedObjectOnUnloaded(object sender, RoutedEventArgs e)
        {
            this.AssociatedObject.Unloaded -= AssociatedObjectOnUnloaded;
            this.AssociatedObject.RequestNavigate -= NavigationRequested;
        }

        private void NavigationRequested(object sender, RequestNavigateEventArgs e)
        {
            if (!ConfirmNavigation || MessageBox.Show("Are you sure?", "Question", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
            {
                OpenUrl();
            }

            e.Handled = true;
        }

        private void OpenUrl()
        {
//          Process.Start(new ProcessStartInfo(AssociatedObject.NavigateUri.AbsoluteUri));
            MessageBox.Show($"Opening {AssociatedObject.NavigateUri}");
        }

        /// <inheritdoc />
        protected override void OnDetaching()
        {
            this.AssociatedObject.RequestNavigate -= NavigationRequested;
            base.OnDetaching();
        }
    }
}
Dbl
  • 5,634
  • 3
  • 41
  • 66
1

Hope this help someone as well.

using System.Diagnostics;
using System.Windows.Documents;

namespace Helpers.Controls
{
    public class HyperlinkEx : Hyperlink
    {
        protected override void OnClick()
        {
            base.OnClick();

            Process p = new Process()
            {
                StartInfo = new ProcessStartInfo()
                {
                    FileName = this.NavigateUri.AbsoluteUri
                }
            };
            p.Start();
        }
    }
}
jaysonragasa
  • 1,076
  • 1
  • 20
  • 40
1

XMAL binding example is as flowing:

<TextBlock>
  <Hyperlink Command="local:MyCommands.ViewDetails" CommandParameter="{Binding}">
      <TextBlock Text="{Binding Path=Name}"/>
  </Hyperlink>
</TextBlock>
Farb
  • 468
  • 6
  • 11