WPF - Making hyperlinks clickable

后端 未结 5 907
清歌不尽
清歌不尽 2020-11-28 06:35

I\'ve got a bit of text that I\'m trying to display in a list. Some of those pieces of a text contain a hyperlink. I\'d like to make the links clickable within the text.

相关标签:
5条回答
  • 2020-11-28 06:43

    The VB.Net version of Bojan's answer. I improved a little upon it: This code will parse an url like http://support.mycompany.com?username=x&password=y to an inline of http://support.mycompany.com, while still navigating to the full url with username and password

    Imports System.Text.RegularExpressions
    Imports System.Windows
    Imports System.Windows.Controls
    Imports System.Windows.Documents
    
    Public Class NavigationService
        ' Copied from http://geekswithblogs.net/casualjim/archive/2005/12/01/61722.aspx '
        Private Shared ReadOnly RE_URL = New Regex("(?#Protocol)(?<domainURL>(?:(?:ht|f)tp(?:s?)\:\/\/|~/|/)?(?#Username:Password)(?:\w+:\w+@)?(?#Subdomains)(?:(?:[-\w]+\.)+(?#TopLevel Domains)(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2})))(?#Port)(?::[\d]{1,5})?(?#Directories)(?:(?:(?:/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|/)+|\?|#)?(?#Query)(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)?")
    
        Public Shared ReadOnly TextProperty = DependencyProperty.RegisterAttached( _
            "Text",
            GetType(String),
            GetType(NavigationService),
            New PropertyMetadata(Nothing, AddressOf OnTextChanged)
        )
    
        Public Shared Function GetText(d As DependencyObject) As String
            Return TryCast(d.GetValue(TextProperty), String)
        End Function
    
        Public Shared Sub SetText(d As DependencyObject, value As String)
            d.SetValue(TextProperty, value)
        End Sub
    
        Private Shared Sub OnTextChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
            Dim text_block = TryCast(d, TextBlock)
            If text_block Is Nothing Then Return
    
            text_block.Inlines.Clear()
    
            Dim new_text = CStr(e.NewValue)
            If String.IsNullOrEmpty(new_text) Then Return
    
            ' Find all URLs using a regular expression '
            Dim last_pos As Integer = 0
            For Each match As Match In RE_URL.Matches(new_text)
                'Copy raw string from the last position up to the match '
                If match.Index <> last_pos Then
                    Dim raw_text = new_text.Substring(last_pos, match.Index - last_pos)
                    text_block.Inlines.Add(New Run(raw_text))
                End If
    
                ' Create a hyperlink for the match '
                Dim link = New Hyperlink(New Run(match.Groups("domainURL").Value)) With
                    {
                        .NavigateUri = New Uri(match.Value)
                    }
                AddHandler link.Click, AddressOf OnUrlClick
    
                text_block.Inlines.Add(link)
    
                'Update the last matched position '
                last_pos = match.Index + match.Length
            Next
    
            ' Finally, copy the remainder of the string '
            If last_pos < new_text.Length Then
                text_block.Inlines.Add(New Run(new_text.Substring(last_pos)))
            End If
        End Sub
    
        Private Shared Sub OnUrlClick(sender As Object, e As RoutedEventArgs)
            Try
                Dim link = CType(sender, Hyperlink)
                Process.Start(link.NavigateUri.ToString)
            Catch
            End Try
        End Sub
    End Class
    
    0 讨论(0)
  • 2020-11-28 06:51

    If you're using something like MVVM light or similar architecture, you could have a interaction trigger on textblock mousedown property and do whatever in the viewmodel's code.

    0 讨论(0)
  • 2020-11-28 06:53

    Something like this?

    <TextBlock>
        <TextBlock Text="Hey, check out this link:"/>
        <Hyperlink NavigateUri={Binding ElementName=lvTopics, Path=SelectedValue.Title}
                               Click="Url_Click">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Feed: " FontWeight="Bold"/>
                <TextBlock Text={Binding ElementName=lvTopics, Path=SelectedValue.Url}/>
            </StackPanel>
        </Hyperlink>
    </TextBlock>
    

    EDIT: If you need dynamic, bind it. In the example above, lvTopics(not shown) is bound to a list of objects with Title and Url properties. Also, it will not go to the url automatically, you need to handle it with a similar code:

    private void Url_Click(object sender, RoutedEventArgs e)
    {    browser.Navigate(((Hyperlink)sender).NavigateUri); }
    

    I just wanted to show that you can embed anything into TextBlock, including Hyperlink, and anything into Hyperlink.

    0 讨论(0)
  • 2020-11-28 07:01

    You need something that will parse the Text of the TextBlock and create the all the inline objects at runtime. For this you can either create your own custom control derived from TextBlock or an attached property.

    For the parsing, you can search for URLs in the text with a regular expression. I borrowed a regular expression from A good url regular expression? but there are others available on the web, so you can choose the one which works best for you.

    In the sample below, I used an attached property. To use it, modify your TextBlock to use NavigateService.Text instead of Text property:

    <Window x:Class="DynamicNavigation.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DynamicNavigation"
        Title="Window1" Height="300" Width="300">
        <StackPanel>
            <!-- Type something here to see it displayed in the TextBlock below -->
            <TextBox x:Name="url"/>
    
            <!-- Dynamically updates to display the text typed in the TextBox -->
            <TextBlock local:NavigationService.Text="{Binding Text, ElementName=url}" />
        </StackPanel>
    </Window>
    

    The code for the attached property is given below:

    using System;
    using System.Text.RegularExpressions;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    
    namespace DynamicNavigation
    {
        public static class NavigationService
        {
            // Copied from http://geekswithblogs.net/casualjim/archive/2005/12/01/61722.aspx
            private static readonly Regex RE_URL = new Regex(@"(?#Protocol)(?:(?:ht|f)tp(?:s?)\:\/\/|~/|/)?(?#Username:Password)(?:\w+:\w+@)?(?#Subdomains)(?:(?:[-\w]+\.)+(?#TopLevel Domains)(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2}))(?#Port)(?::[\d]{1,5})?(?#Directories)(?:(?:(?:/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|/)+|\?|#)?(?#Query)(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)?");
    
            public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached(
                "Text",
                typeof(string),
                typeof(NavigationService),
                new PropertyMetadata(null, OnTextChanged)
            );
    
            public static string GetText(DependencyObject d)
            { return d.GetValue(TextProperty) as string; }
    
            public static void SetText(DependencyObject d, string value)
            { d.SetValue(TextProperty, value); }
    
            private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var text_block = d as TextBlock;
                if (text_block == null)
                    return;
    
                text_block.Inlines.Clear();
    
                var new_text = (string)e.NewValue;
                if ( string.IsNullOrEmpty(new_text) )
                    return;
    
                // Find all URLs using a regular expression
                int last_pos = 0;
                foreach (Match match in RE_URL.Matches(new_text))
                {
                    // Copy raw string from the last position up to the match
                    if (match.Index != last_pos)
                    {
                        var raw_text = new_text.Substring(last_pos, match.Index - last_pos);
                        text_block.Inlines.Add(new Run(raw_text));
                    }
    
                    // Create a hyperlink for the match
                    var link = new Hyperlink(new Run(match.Value))
                    {
                        NavigateUri = new Uri(match.Value)
                    };
                    link.Click += OnUrlClick;
    
                    text_block.Inlines.Add(link);
    
                    // Update the last matched position
                    last_pos = match.Index + match.Length;
                }
    
                // Finally, copy the remainder of the string
                if (last_pos < new_text.Length)
                    text_block.Inlines.Add(new Run(new_text.Substring(last_pos)));
            }
    
            private static void OnUrlClick(object sender, RoutedEventArgs e)
            {
                var link = (Hyperlink)sender;
                // Do something with link.NavigateUri like:
                Process.Start(link.NavigateUri.ToString());
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-28 07:04

    Here is the simplified version:

    <TextBlock>
        Hey, check out this link:        
        <Hyperlink NavigateUri="CNN.COM" Click="cnn_Click">Test</Hyperlink>
    </TextBlock>
    
    0 讨论(0)
提交回复
热议问题