我有一小段文本,想要在列表中展示。其中一些文本含有超链接。我想让这些链接在文本中可点击。我可以想象解决此问题的方法,但它们似乎并不美观。
例如,我可以将字符串拆分为超链接和非超链接,然后动态构建 Textblock,根据需要添加纯文本元素和超链接对象。
我希望有更好的、最好是声明性的解决方案。
示例:"嘿,看看这个链接:http://mylink.com它真的很酷。"
我有一小段文本,想要在列表中展示。其中一些文本含有超链接。我想让这些链接在文本中可点击。我可以想象解决此问题的方法,但它们似乎并不美观。
例如,我可以将字符串拆分为超链接和非超链接,然后动态构建 Textblock,根据需要添加纯文本元素和超链接对象。
我希望有更好的、最好是声明性的解决方案。
示例:"嘿,看看这个链接:http://mylink.com它真的很酷。"
<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\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());
}
}
}
这是简化版:
<TextBlock>
Hey, check out this link:
<Hyperlink NavigateUri="CNN.COM" Click="cnn_Click">Test</Hyperlink>
</TextBlock>
<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>
编辑:如果需要动态绑定,可以使用bind方法。在上面的例子中,lvTopics(未显示)已经绑定到一个包含Title和Url属性的对象列表上。 此外,它不会自动跳转到URL,你需要使用类似的代码处理:
private void Url_Click(object sender, RoutedEventArgs e)
{ browser.Navigate(((Hyperlink)sender).NavigateUri); }
我只是想表明您可以将任何内容嵌入到TextBlock中,包括超链接,并将任何内容嵌入到超链接中。
以下是 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\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