Ulpin
Ulpin

Reputation: 179

Create TextBlock dynamically with hyperlinks

what is the best way to create texts with several hyperlinks whereby the hyperlinks can appear at different positions within in the text.

I want to build something like this dynamically in the code-behind file:

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

But this doesnt work so far. How can I get the Hyperlinks aligned with the TextBlocks, though the fontsize, margin and paddings are the same. And also, how do I get the line break withn in the stackpanel? At the end it should look like a normal TextBlock (with TextWrapping="Wrap").

EDIT: It's a Windows Phone 8.1 Project

EDIT#2: I couldn't get the WrapPanel to work with the WPToolkit, instead I've found something here.

Cheers,

Chris

Upvotes: 1

Views: 663

Answers (1)

Ilan
Ilan

Reputation: 2782

Updates #2: Here is the possible WinRT version.

Section 1 - Xaml code(user control), here the WinRtApp is the project where the user control is defined. If you want to use some another ContentTemplate to present your data(like tweets), you should parse your text and add a new model(like a TweetPart) see how I did it with the HyperlinkButton, add new DataTemplate and extend the 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>

Section 2 - Xaml's code behind

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

Updates #4 - Selector code (add as a new 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; }
}

How to use - MainPage xaml code

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

Relay command code

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

Updates #3

  1. The WinRt is the name of my project, you need to use the name of your project instead.
  2. Create UserControl named ComplexTextPresenter.
  3. Replace the UserControl's xaml with the code defined in Section 1.
  4. Replace the UserControl's code-behind with the code defined in Section 2.
  5. Add the RelayCommand code defined in Section 3 as a class to your project.
  6. Read this article, it is about to how to add a references in XAML.
  7. In order to get the Wrappanel in your project, run the next NuGet command in your VS Tools/NuGet Package Manager/Package Manager Console: Install-Package WPtoolkit(taken from here).
  8. Download ReSharper, it will help you to manage all assembly import related issues.

Upvotes: 2

Related Questions