2009-05-14 13 views
27

Mam trochę tekstu, który próbuję wyświetlić na liście. Niektóre fragmenty tekstu zawierają hiperłącze. Chciałbym, aby linki były klikalne w tekście. Mogę sobie wyobrazić rozwiązania tego problemu, ale na pewno nie wydają się ładne.WPF - Tworzenie klikalnych hiperłączy

Na przykład, mógłbym oddzielić ciąg, dzieląc go na hiperłącza i nie-hiperlinki. Następnie mogłem dynamicznie budować blok tekstu, dodając elementy zwykłego tekstu i obiekty hiperłączy, stosownie do potrzeb.

Mam nadzieję, że jest lepszy, najlepiej coś deklaratywnego.

Przykład: "Hej, sprawdź ten link: http://mylink.com To naprawdę fajne."

Odpowiedz

43

Trzeba coś, co będzie analizować tekście TextBlock i utworzyć wszystkie obiekty inline przy starcie. W tym celu można utworzyć własny niestandardowy formant oparty na TextBlock lub dołączonej właściwości.

Podczas parsowania można wyszukiwać adresy URL w tekście za pomocą wyrażenia regularnego. Pożyczyłem wyrażenie regularne od A good url regular expression?, ale są jeszcze inne dostępne w Internecie, więc możesz wybrać ten, który najbardziej Ci odpowiada.

W poniższym przykładzie użyłem załączonej właściwości. Aby go użyć, modyfikować swój TextBlock używać NavigateService.Text zamiast właściwości 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> 

kod do załączonego obiektu znajduje się poniżej:

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

Niesamowite, dokładnie to, czego szukałem. Dałem mi zupełnie nowy sposób, aby przyjrzeć się niektórym problemom WPF, z którymi miałem do czynienia. –

+0

Cudownie! Poniżej zamieszczam wersję VB tej odpowiedzi. – Dabblernl

+0

Jednym drobnym problemem jest to, że jeśli protokół nie jest określony (np. Po prostu wpisuję www.google.com), komponent zgłasza wyjątek podczas próby utworzenia Uri (UriFormatException - "Nieprawidłowy identyfikator URI: nie można określić formatu identyfikatora URI"). –

4

Coś takiego?

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

EDYCJA: Jeśli potrzebujesz dynamicznego, wiąż go. W powyższym przykładzie lvTopics (nie pokazano) jest związany z listą obiektów z właściwościami tytułu i adresu URL. Ponadto, nie trafi do url automatycznie, trzeba poradzić z podobnym kodzie:

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

Chciałem tylko pokazać, że można umieścić cokolwiek w TextBlock, w tym hiperłącze, a nic w hiperłącze.

+0

Uh, tak, ale pytanie brzmi: jak włączyć czysty tekst na to, co tu dostarczyłeś. Jest to plik danych, musi się to odbywać dynamicznie. –

9

Oto uproszczona wersja:

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

Jeśli używasz coś jak MVVM światła lub podobną architekturę, możesz wywołać wyzwalacz interakcji w mousedown textblock i zrobić cokolwiek w kodzie viewmodelu.

2

Wersja VB.Net odpowiedzi Bojana. Poprawiłem trochę na nim: Ten kod będzie analizować url jak http://support.mycompany.com?username=x&password=y do inline z http://support.mycompany.com, jednocześnie przechodząc do pełnego adresu URL z nazwą użytkownika i hasłem

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

miła poprawa – GilShalit

Powiązane problemy