【问题标题】:Create TextBlock dynamically with hyperlinks使用超链接动态创建 TextBlock
【发布时间】:2016-04-12 22:43:31
【问题描述】:

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

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

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

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

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

编辑#2: 我无法让 WrapPanel 与 WPToolkit 一起使用,而是找到了一些东西 here

干杯,

克里斯

【问题讨论】:

  • 如何使用只读的 RichTextBox 来识别和显示可点击的超链接?
  • 我觉得这个主意不错,连这个话题都好像很复杂……我去看看告诉你
  • 好的。我知道如何用 RichTextBox 解决这个问题。让我知道你有兴趣。
  • @Ilan 我现在对 RichEditBox 有了一个粗略的了解(希望你是这个意思)。我可以用代码隐藏文件中的内容填充它。但是,问题是标记应该标记为超链接的部分。接下来的事情是,不仅仅是超链接,而是一个小小的 Twitter 内容框,所以还有 Hashtags 和 @-Twitterhandles 也不是超链接,所以我必须自己进行链接。我不知道 RhichEditBox 是否可以处理这个问题。你对 RichTextbox 有什么想法?

标签: wpf xaml hyperlink winrt-xaml stackpanel


【解决方案1】:

更新 #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 - 选择器代码(添加为新类)

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

如何使用 - MainPage 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"/>

如何使用 - MainPage xaml 代码背后

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. 将 UserControl 的 xaml 替换为第 1 节中定义的代码。
  4. 将 UserControl 的代码隐藏替换为第 2 节中定义的代码。
  5. 将第 3 节中定义的 RelayCommand 代码作为一个类添加到您的项目中。
  6. 阅读this article,关于如何在XAML中添加引用。
  7. 为了在您的项目中获取 Wrappanel,请在 VS 工具/NuGet 包管理器/包管理器控制台中运行下一个 NuGet 命令:Install-Package WPtoolkit(taken from here)。
  8. 下载ReSharper,它将帮助您管理所有与程序集导入相关的问题。

【讨论】:

  • 感谢您的努力。伟大的。但我不确定是否完全理解它:这个命名空间clr-namespace:DataTemplateProjecytSOHelpAttempt 是什么?我无法将它添加到项目中,我没有找到任何关于它的信息。如果我做对了,我不需要实现INPC-baseOberservable Object?这是什么行为?如何将此添加到项目: Behavior&lt;RichTextBox&gt;
  • 只是为了清楚。您真的是指 RichTextBox,因为 WindowsPhone 没有这样的元素?只有 RichTextBlock...
  • @Ulpin 您应该在问题中提及使用特定平台的事实。
  • @Ulpin 让我检查一下。
  • @Ulpin 请参阅更新 #4。有数据模板选择器代码。
最近更新 更多