使用超链接动态创建 TextBlock

Create TextBlock dynamically with hyperlinks

创建具有多个超链接的文本的最佳方法是什么,超链接可以出现在文本中的不同位置。

我想在代码隐藏文件中动态构建这样的东西:

<StackPanel Orientation="Horizontal" Width="380">
    <TextBlock Padding="0" Margin="0" Foreground="White" FontSize="20">Some random Text</TextBlock>
    <HyperlinkButton VerticalAlignment="Top" Margin="0" Padding="0" Foreground="White" FontSize="20" Content="Link1" Tapped="RealLink_Tapped" />
    <TextBlock Foreground="White" FontSize="20">Some more random Text</TextBlock>
    <HyperlinkButton VerticalAlignment="Top" Margin="0" Padding="0" Foreground="White" FontSize="20" Content="Link2" Tapped="RealLink_Tapped" />
    <TextBlock Foreground="White" FontSize="20">Some random Text</TextBlock>
    <HyperlinkButton VerticalAlignment="Top" Margin="0" Padding="0" Foreground="White" FontSize="20" Content="Link3" Tapped="RealLink_Tapped" />
</StackPanel>

但这目前还行不通。我怎样才能让超链接与文本块对齐,尽管字体大小、边距和填充是相同的。 而且,如何在堆栈面板中获取换行符?最后它应该看起来像一个普通的 TextBlock(TextWrapping="Wrap")。

编辑: 这是一个 Windows Phone 8.1 项目

编辑#2: 我无法让 WrapPanel 与 WPToolkit 一起工作,相反我找到了一些东西 here

干杯,

克里斯

更新 #2: 这是可能的 WinRT 版本。

第 1 节 - Xaml 代码(用户控件),这里的 WinRtApp 是定义用户控件的项目。如果您想使用其他一些 ContentTemplate 来呈现您的数据(如推文),您应该解析您的文本并添加一个新模型(如 TweetPart),看看我是如何使用 HyperlinkBut​​ton 实现的,添加新的 DataTemplate 并扩展 ContentTemplateSelector。

<UserControl
x:Class="WinRtApp.ComplexTextPresenter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400" x:Name="This">
<Grid>
    <Grid.Resources>
        <DataTemplate x:Key="HyperlinkDataTemplateKey">
            <HyperlinkButton Margin="0" FontSize="12" CommandParameter="{Binding }" Command="{Binding ElementName=This, Path=OnHyperlinkCommand}" Content="{Binding Path=Content}" Foreground="Blue"/>
        </DataTemplate>
        <DataTemplate x:Key="LiteralDataTemplateKey">
            <TextBlock Margin="0" FontSize="12" Text="{Binding Path=Content}"></TextBlock>
        </DataTemplate>
        <MyDataTemplateSelector x:Key="DataTemplateSelectorKey"
                                  LiteralDataTemplate ="{StaticResource LiteralDataTemplateKey}"
                                  HyperlinkDataTemplate="{StaticResource HyperlinkDataTemplateKey}"/>
        <Style TargetType="ListBoxItem">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5,0,0,0"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Grid.Resources>
    <Rectangle Fill="Green" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
    <ListBox ItemsSource="{Binding ElementName=This, Path=InputCollection}" ItemTemplateSelector="{StaticResource DataTemplateSelectorKey}" Margin="5">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ListBox>
</Grid>

第 2 部分 - Xaml 背后的代码

public sealed partial class ComplexTextPresenter : UserControl
{
    public static readonly DependencyProperty InputProperty = DependencyProperty.Register("Input", typeof(string), typeof(MainPage), new PropertyMetadata(default(string), InputPropertyChangedCallback));

    private static void InputPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        var ctrl = dependencyObject as ComplexTextPresenter;
        if(ctrl == null)
            return;
        ctrl.Init();
    }

    public static readonly DependencyProperty InputCollectionProperty = DependencyProperty.Register("InputCollection", typeof(ObservableCollection<object>), typeof(MainPage), new PropertyMetadata(default(ObservableCollection<object>)));
    public static readonly DependencyProperty OnHyperlinkCommandProperty = DependencyProperty.Register("OnHyperlinkCommand", typeof(ICommand), typeof(MainPage), new PropertyMetadata(default(ICommand)));



    private IEnumerable<object> GetParsedInput()
    {
        List<BaseInputPart> inputParts = new List<BaseInputPart>();
        var strings = Input.Split(new[] { " " }, StringSplitOptions.None).ToList();
        strings.ForEach(s =>
        {
            if (s.IsHyperlink())
            {
                inputParts.Add(new HyperLinkPart { Content = s });
            }
            else
            {
                inputParts.Add(new LiteralPart { Content = s });
            }
        });
        return inputParts.OfType<object>().ToList();
    }

    public string Input
    {
        get { return (string)GetValue(InputProperty); }
        set { SetValue(InputProperty, value); }
    }

    public ObservableCollection<object> InputCollection
    {
        get { return (ObservableCollection<object>)GetValue(InputCollectionProperty); }
        private set { SetValue(InputCollectionProperty, value); }
    }

    public ICommand OnHyperlinkCommand
    {
        get { return (ICommand)GetValue(OnHyperlinkCommandProperty); }
        set { SetValue(OnHyperlinkCommandProperty, value); }
    }

    private void Init()
    {
        InputCollection = new ObservableCollection<object>(GetParsedInput());
    }

    public ComplexTextPresenter()
    {
        this.InitializeComponent();
    }
}

public abstract class BaseInputPart
{
    public abstract string Content { get; set; }
}

public class HyperLinkPart : BaseInputPart
{
    public override string Content { get; set; }
}

public class LiteralPart : BaseInputPart
{
    public override string Content { get; set; }
}

public static class StringExtension
{
    #region hyperlink regex region

    private static readonly Regex UrlRegex =
        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})*)(?:&amp;(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)?");

    #endregion
    public static bool IsHyperlink(this string word)
    {
        var result = false;
        try
        {
            // First check to make sure the word has at least one of the characters we need to make a hyperlink
            if (word.IndexOfAny(@":.\/".ToCharArray()) != -1)
            {
                if (Uri.IsWellFormedUriString(word, UriKind.Absolute))
                {
                    // The string is an Absolute URI
                    result = true;
                }
                else if (UrlRegex.IsMatch(word))
                {
                    Uri uri = new Uri(word, UriKind.RelativeOrAbsolute);

                    if (!uri.IsAbsoluteUri)
                    {
                        // rebuild it it with http to turn it into an Absolute URI
                        uri = new Uri(@"http://" + word, UriKind.Absolute);
                        result = true;
                    }

                    if (uri.IsAbsoluteUri)
                    {
                        result = true;
                    }
                }
                else
                {
                    Uri wordUri = new Uri(word);

                    // Check to see if URL is a network path
                    if (wordUri.IsUnc || wordUri.IsFile)
                    {
                        result = true;
                    }
                }
            }
        }
        catch (Exception e)
        {
            result = false;
        }

        return result;
    }
}

更新 #4 - 选择器代码(添加为新 class)

public class MyDataTemplateSelector : DataTemplateSelector
{
    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
    {
        if (item is HyperLinkPart)
            return HyperlinkDataTemplate;
        if (item is LiteralPart)
            return LiteralDataTemplate;
        return null;
    }

    public DataTemplate LiteralDataTemplate { get; set; }

    public DataTemplate HyperlinkDataTemplate
    { get; set; }
}

如何使用-主页xaml代码

<Page
x:Class="PutHereTheNameOfYourProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ComplexTextPresenter x:Name="ComplexTextPresenter"/>

How to use - MainPage xaml code behind

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        this.NavigationCacheMode = NavigationCacheMode.Required;
        Init();
    }

    private void Init()
    {
        ComplexTextPresenter.Input =
            @"I Love https://www.google.com site. I Love https://www.google.com site. I Love https://www.google.com site. I Love https://www.google.com site. I Love https://www.google.com site. I Love https://www.google.com site. I Love https://www.google.com site.";
        ComplexTextPresenter.OnHyperlinkCommand = new RelayCommand<object>(Execute);
    }

    private void Execute(object o)
    {
       //put here the code that can open browser 
    }
}

中继命令代码

public class RelayCommand<T> : ICommand
{
    readonly Action<T> _execute;
    readonly Func<T, bool> _canExecute;

    public event EventHandler CanExecuteChanged;

    public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public void RefreshCommand()
    {
        var cec = CanExecuteChanged;
        if (cec != null)
            cec(this, EventArgs.Empty);
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute == null) return true;
        return _canExecute((T)parameter);
    }

    public void Execute(object parameter)
    {
        _execute((T)parameter);
    }
}

更新#3

  1. WinRt 是我的项目名称,您需要使用您的项目名称。
  2. 创建名为 ComplexTextPresenter 的用户控件。
  3. 用第 1 节中定义的代码替换 UserControl 的 xaml。
  4. 用第 2 节中定义的代码替换 UserControl 的代码隐藏。
  5. 将第 3 节中定义的 RelayCommand 代码作为 class 添加到您的项目中。
  6. 阅读this article,关于如何在XAML中添加引用。
  7. 为了在您的项目中获取 Wrappanel,运行 VS 中的下一个 NuGet 命令 Tools/NuGet 包 Manager/Package 管理器控制台:Install-Package WPtoolkit(taken from here).
  8. 下载ReSharper,它将帮助您管理所有程序集导入相关问题。