WPF - Делаем гиперссылки кликабельными - PullRequest
27 голосов
/ 14 мая 2009

У меня есть немного текста, который я пытаюсь отобразить в списке. Некоторые из этих фрагментов текста содержат гиперссылку. Я хотел бы сделать ссылки кликабельными внутри текста. Я могу вообразить решения этой проблемы, но они, конечно, не кажутся симпатичными.

Например, я мог бы разорвать строку, разделив ее на гиперссылки и не гиперссылки. Затем я мог бы динамически создавать текстовый блок, добавляя элементы обычного текста и гиперссылки в зависимости от ситуации.

Я надеюсь, что есть лучшее, желательно что-то декларативное.

Пример: «Привет, посмотрите эту ссылку: http://mylink.com Это действительно круто».

Ответы [ 5 ]

44 голосов
/ 15 мая 2009

Вам нужно что-то, что будет анализировать текст TextBlock и создавать все встроенные объекты во время выполнения. Для этого вы можете создать собственный настраиваемый элемент управления, полученный из TextBlock, или присоединенное свойство.

Для анализа вы можете искать URL-адреса в тексте с помощью регулярного выражения. Я позаимствовал регулярное выражение у Хорошее регулярное выражение URL? , но в Интернете есть и другие, так что вы можете выбрать то, которое лучше всего подходит для вас.

В приведенном ниже примере я использовал прикрепленное свойство. Чтобы использовать его, измените ваш 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\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());
        }
    }
}
11 голосов
/ 14 мая 2009

Вот упрощенная версия:

<TextBlock>
    Hey, check out this link:        
    <Hyperlink NavigateUri="CNN.COM" Click="cnn_Click">Test</Hyperlink>
</TextBlock>
4 голосов
/ 14 мая 2009

Как то так?

<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 все, что угодно, в том числе гиперссылку, и что угодно в гиперссылку.

2 голосов
/ 08 января 2012

VB.Net версия ответа Бояна. Я немного улучшил его: этот код будет анализировать URL-адрес, такой как http://support.mycompany.com? Username = x & password = y , для встроенного 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
1 голос
/ 11 мая 2011

Если вы используете что-то вроде MVVM light или аналогичной архитектуры, вы можете иметь триггер взаимодействия со свойством textblock mousedown и делать что угодно в коде модели представления.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...