且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

WPF - 使超链接可点击

更新时间:2023-01-13 09:24:51

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

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.

对于解析,您可以使用正则表达式搜索文本中的 URL.我从 一个好的 url 正则表达式? 借用了一个正则表达式,但是网络上还有其他可用的,因此您可以选择最适合您的那个.

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.

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

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>

附加属性的代码如下:

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-fd]{2})+)+|/)+|?|#)?(?#Query)(?:(?:?(?:[-w~!$+|.,*:]|%[a-fd{2}])+=(?:[-w~!$+|.,*:=]|%[a-fd]{2})*)(?:&(?:[-w~!$+|.,*:]|%[a-fd{2}])+=(?:[-w~!$+|.,*:=]|%[a-fd]{2})*)*)*(?#Anchor)(?:#(?:[-w~!$+|.,*:=]|%[a-fd]{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());
        }
    }
}