WPF - 使超链接可点击

Posted

技术标签:

【中文标题】WPF - 使超链接可点击【英文标题】:WPF - Making hyperlinks clickable 【发布时间】:2010-10-26 01:46:34 【问题描述】:

我想在列表中显示一些文本。其中一些文本包含超链接。我想让链接在文本中可点击。我可以想出解决这个问题的方法,但它们看起来确实不漂亮。

例如,我可以撕开字符串,将其分成超链接和非超链接。然后,我可以动态构建一个 Textblock,根据需要添加纯文本元素和超链接对象。

我希望有更好的,最好是声明性的。

示例:“嘿,看看这个链接:http://mylink.com 真的很酷。”

【问题讨论】:

【参考方案1】:

您需要能够解析 TextBlock 的文本并在运行时创建所有内联对象的东西。为此,您可以创建自己的从 TextBlock 或附加属性派生的自定义控件。

对于解析,您可以使用正则表达式在文本中搜索 URL。我从A good url regular expression? 借用了一个正则表达式,但网上还有其他可用的,所以你可以选择最适合你的一个。

在下面的示例中,我使用了附加属性。要使用它,请将您的 TextBlock 修改为使用 NavigateService.Text 而不是 Text 属性:

<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>

附加属性的代码如下:

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\d2])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]2)*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d2])+=(?:[-\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());
        
    

【讨论】:

太棒了,正是我想要的。为我提供了一种全新的方式来看待我一直面临的一些 WPF 问题。 太棒了!我把这个答案的VB版本放在下面。 一个小问题是,如果未指定协议(例如,我只输入 www.google.com),则组件在尝试创建 Uri 时会引发异常 (UriFormatException - "Invalid URI : 无法确定 URI 的格式。")【参考方案2】:

这是简化版:

<TextBlock>
    Hey, check out this link:        
    <Hyperlink NavigateUri="CNN.COM" Click="cnn_Click">Test</Hyperlink>
</TextBlock>

【讨论】:

【参考方案3】:

这样的?

<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>

编辑:如果您需要动态,请绑定它。在上面的示例中,lvTopics(未显示)绑定到具有 Title 和 Url 属性的对象列表。 另外,它不会自动转到url,你需要用类似的代码来处理它:

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

我只是想表明你可以将任何东西嵌入到 TextBlock 中,包括超链接,以及任何东西到超链接中。

【讨论】:

嗯,是的,但问题是如何将纯文本转换为您在此处提供的内容。这是一个数据馈送,需要动态进行。【参考方案4】:

Bojan 答案的 VB.Net 版本。我对其进行了一些改进:此代码会将http://support.mycompany.com?username=x&password=y 之类的 url 解析为 http://support.mycompany.com 的内联,同时仍使用用户名和密码导航到完整的 url

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\d2])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]2)*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d2])+=(?:[-\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

【讨论】:

【参考方案5】:

如果您使用类似 MVVM light 或类似架构的东西,您可以在 textblock mousedown 属性上有一个交互触发器,并在视图模型的代码中执行任何操作。

【讨论】:

以上是关于WPF - 使超链接可点击的主要内容,如果未能解决你的问题,请参考以下文章

如何使超链接在 RichTextBox 中工作?

如何根据读取的 cookie 值使超链接指向不同的 url?

WPF流文档超链接

wpf webBrowser 禁用超链接

Wpf Hyperlink超链接控件使用

如何在 qtextbrowser 中添加超链接和用户可点击操作