web-dev-qa-db-ja.com

WPF-ハイパーリンクをクリック可能にする

リストに表示しようとしているテキストが少しあります。これらのテキストの一部には、ハイパーリンクが含まれています。テキスト内のリンクをクリックできるようにしたいのですが。私はこの問題の解決策を想像することができますが、それらは確かにきれいに見えません。

たとえば、文字列を分解して、ハイパーリンクと非ハイパーリンクに分割することができます。次に、必要に応じてプレーンテキスト要素とハイパーリンクオブジェクトを追加して、Textblockを動的に構築できます。

より良い、できれば宣言的なものがあることを願っています。

例:「ねえ、このリンクをチェックしてください: http://mylink.com 本当にかっこいいです。」

27
Ben

TextBlockのテキストを解析し、実行時にすべてのインラインオブジェクトを作成するものが必要です。このために、TextBlockまたは添付プロパティから派生した独自のカスタムコントロールを作成できます。

解析では、正規表現を使用してテキスト内のURLを検索できます。 良いURL正規表現? から正規表現を借りましたが、他にもWebで入手できるので、自分に最適なものを選択できます。

以下のサンプルでは、​​添付プロパティを使用しました。これを使用するには、Textプロパティの代わりにNavigateService.Textを使用するようにTextBlockを変更します。

<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());
        }
    }
}
44
Bojan Resnik

簡略化されたバージョンは次のとおりです。

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

このようなもの?

<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には何でも、ハイパーリンクには何でも埋め込むことができることを示したかっただけです。

4

Bojanの答えのVB.Netバージョン。私はそれを少し改善しました:このコードは http://support.mycompany.com?username=x&password=y のようなURLを 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
2
Dabblernl

MVVMライトまたは同様のアーキテクチャのようなものを使用している場合は、textblockのmousedownプロパティでインタラクショントリガーを設定し、ビューモデルのコードで何でも実行できます。

1
WhoIsNinja