2009-05-14 8 views
27

Tôi có một chút văn bản mà tôi đang cố hiển thị trong danh sách. Một số đoạn văn bản có chứa siêu liên kết. Tôi muốn tạo các liên kết có thể nhấp trong văn bản. Tôi có thể tưởng tượng các giải pháp cho vấn đề này, nhưng họ chắc chắn không có vẻ đẹp. Ví dụ:WPF - Tạo siêu liên kết có thể nhấp

Ví dụ, tôi có thể xé rời chuỗi, tách nó thành siêu liên kết và không siêu liên kết. Sau đó, tôi có thể tự động xây dựng một Textblock, thêm các phần tử văn bản thuần túy và các đối tượng siêu liên kết khi thích hợp.

Tôi hy vọng có một điều tốt hơn, tốt hơn là khai báo.

Ví dụ: "Này, hãy xem liên kết này: http://mylink.com Thật tuyệt vời".

Trả lời

43

Bạn cần cái gì đó sẽ phân tích các văn bản của TextBlock và tạo ra tất cả các đối tượng inline khi chạy. Đối với điều này, bạn có thể tạo điều khiển tùy chỉnh của riêng bạn bắt nguồn từ TextBlock hoặc thuộc tính đính kèm.

Để phân tích cú pháp, bạn có thể tìm kiếm URL trong văn bản bằng cụm từ thông dụng. Tôi đã mượn biểu thức chính quy từ A good url regular expression? nhưng có những người khác có sẵn trên web, do đó bạn có thể chọn biểu tượng phù hợp nhất với mình.

Trong ví dụ bên dưới, tôi đã sử dụng thuộc tính đính kèm. Để sử dụng nó, sửa đổi TextBlock bạn để sử dụng NavigateService.Text thay vì thuộc tính 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> 

Mã cho tài sản gắn liền được đưa ra dưới đây:

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

Tuyệt vời, chính xác những gì tôi đang tìm kiếm. Đã cho tôi một cách hoàn toàn mới để xem xét một số vấn đề của WPF mà tôi cũng phải đối mặt. –

+0

Tuyệt vời! Tôi đặt phiên bản VB của câu trả lời này dưới đây. – Dabblernl

+0

Một vấn đề nhỏ là nếu giao thức không được chỉ định (ví dụ: tôi chỉ cần nhập www.google.com), thành phần ném một ngoại lệ khi cố gắng tạo Uri (UriFormatException - "URI không hợp lệ: Định dạng của URI không thể được xác định.") –

4

Một cái gì đó như thế này?

<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: Nếu bạn cần động, hãy liên kết nó. Trong ví dụ trên, lvTopics (không được hiển thị) bị ràng buộc vào một danh sách các đối tượng có thuộc tính Title và Url. Ngoài ra, nó sẽ không đi đến url tự động, bạn cần phải xử lý nó với một mã số tương tự:

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

Tôi chỉ muốn chứng minh rằng bạn có thể nhúng bất cứ điều gì vào TextBlock, trong đó có siêu liên kết, và bất cứ điều gì vào Hyperlink.

+0

Uh, yeah, nhưng vấn đề là làm thế nào để biến plain text INTO những gì bạn cung cấp ở đây. Đó là một nguồn cấp dữ liệu, điều này cần phải diễn ra tự động. –

9

Đây là phiên bản đơn giản:

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

Nếu bạn đang sử dụng một cái gì đó giống như ánh sáng MVVM hoặc kiến ​​trúc tương tự, bạn có thể có một kích hoạt tương tác trên thuộc tính textblock mousedown và làm bất cứ điều gì trong mã của viewmodel.

2

Phiên bản VB.Net của câu trả lời của Bojan. Tôi được cải thiện một chút khi nó: Mã này sẽ phân tích một url như http://support.mycompany.com?username=x&password=y một inline của http://support.mycompany.com, trong khi vẫn hướng đến toàn bộ url với username và password

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

cải thiện tốt đẹp – GilShalit