2009-05-14 16 views
27

Tengo un poco de texto que intento 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.WPF - Haciendo que los hipervínculos se puedan hacer clic

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 una mejor, preferiblemente algo declarativa.

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

Respuesta

43

Es necesario algo que va a analizar el texto de la TextBlock y crear los 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. Tomé prestada una expresión regular del A good url regular expression?, pero hay otras disponibles en la web, por lo que puede elegir la que mejor se adapte a sus necesidades.

En el ejemplo siguiente, utilicé una propiedad adjunta. Para usarlo, modificar TextBlock utilizar NavigateService.Text en lugar de propiedad de texto:

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

El código de la propiedad adjunta es la siguiente:

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+:\[email protected])?(?#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()); 
     } 
    } 
} 
+0

Impresionante, exactamente lo que estaba buscando. Me brindó una forma completamente nueva de analizar algunos de los problemas de WPF a los que me he enfrentado también. –

+0

¡Maravilloso! Puse la versión VB de esta respuesta a continuación. – Dabblernl

+0

Un problema menor es que si el protocolo no está especificado (por ejemplo, simplemente escribo en www.google.com), el componente arroja una excepción al intentar crear el Uri (UriFormatException - "URI no válido: no se pudo determinar el formato del URI") –

4

¿Algo como esto?

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

EDIT: Si necesita dinámico, enlazarlo. 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 va a ir a la url de forma automática, es necesario manejarlo con un código similar:

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

Sólo quería demostrar que se puede incrustar cualquier cosa en TextBlock, incluyendo hipervínculo, y nada en hipervínculo.

+0

Uh, sí, pero la pregunta es cómo convertir la texto sin formato EN lo que ha proporcionado aquí. Es una fuente de datos, esto debe suceder dinámicamente. –

9

Aquí es la versión simplificada:

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

Si está usando algo como la luz MVVM o una arquitectura similar, podría tener un activador 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.

2

La versión de VB.Net de la respuesta de Bojan. Creo que he mejorado un poco sobre ella: Este código será analizar un URL como http://support.mycompany.com?username=x&password=y a una línea de http://support.mycompany.com, sin dejar de navegar a la dirección 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)(?<domainURL>(?:(?:ht|f)tp(?:s?)\:\/\/|~/|/)?(?#Username:Password)(?:\w+:\[email protected])?(?#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 
+0

buena mejora – GilShalit

Cuestiones relacionadas