WPF: hacer que los hipervínculos se puedan hacer clic

Tengo un poco de texto que bash mostrar en una lista. Algunas de esas partes de un texto contienen un hipervínculo. Me gustaría hacer que los enlaces se puedan hacer clic dentro del texto. Puedo imaginar soluciones para este problema, pero seguro que no parecen bonitas.

Por ejemplo, podría separar la cadena, dividiéndola en hipervínculos y no hipervínculos. Entonces podría construir dinámicamente un Textblock, agregando elementos de texto plano y objetos de hipervínculo según corresponda.

Espero que haya algo mejor, preferiblemente algo declarativo.

Ejemplo: “Oye, mira este enlace: http://mylink.com Es realmente genial”.

Necesita algo que analice el texto del bloque de texto y cree todos los objetos en línea en tiempo de ejecución. Para esto, puede crear su propio control personalizado derivado de TextBlock o una propiedad adjunta.

Para el análisis sintáctico, puede buscar las URL en el texto con una expresión regular. Pedí prestada una expresión regular de A good url expression regular? pero hay otros disponibles en la web, por lo que puede elegir el que mejor funcione para usted.

En el ejemplo a continuación, utilicé una propiedad adjunta. Para usarlo, modifique su TextBlock para usar NavigateService.Text en lugar de la propiedad Text:

        

El código para la propiedad adjunta se da a continuación:

 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|[az]{2}))(?#Port)(?::[\d]{1,5})?(?#Directories)(?:(?:(?:/(?:[-\w~!$+|.,=]|%[af\d]{2})+)+|/)+|\?|#)?(?#Query)(?:(?:\?(?:[-\w~!$+|.,*:]|%[af\d{2}])+=(?:[-\w~!$+|.,*:=]|%[af\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[af\d{2}])+=(?:[-\w~!$+|.,*:=]|%[af\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,*:=]|%[af\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()); } } } 

Aquí está la versión simplificada:

  Hey, check out this link: Test  

¿Algo como esto?

          

EDITAR: si necesita dinámica, agréguela. En el ejemplo anterior, lvTopics (no se muestra) está vinculado a una lista de objetos con propiedades de Título y Url. Además, no irá a la url automáticamente, debe manejarlo con un código similar:

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

Solo quería mostrar que puedes insertar cualquier cosa en TextBlock, incluido Hyperlink, y cualquier cosa en Hyperlink.

La versión de VB.Net de la respuesta de Bojan. Mejoré un poco sobre esto: este código analizará una url como http://support.mycompany.com?username=x&password=y a una línea de http://support.mycompany.com , mientras navega hacia la url completa. con nombre de usuario y contraseña

 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)(?(?:(?: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|[az]{2})))(?#Port)(?::[\d]{1,5})?(?#Directories)(?:(?:(?:/(?:[-\w~!$+|.,=]|%[af\d]{2})+)+|/)+|\?|#)?(?#Query)(?:(?:\?(?:[-\w~!$+|.,*:]|%[af\d{2}])+=(?:[-\w~!$+|.,*:=]|%[af\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[af\d{2}])+=(?:[-\w~!$+|.,*:=]|%[af\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,*:=]|%[af\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 

Si está utilizando algo como MVVM light o una architecture similar, podría tener un desencadenador de interacción en la propiedad de mousedown del bloque de texto y hacer lo que sea en el código del modelo de vista.