Search code examples
c#wpfmvvmtextblock

C# bin new Textblock object to Textblock control in XAML file


I want to show in my C#-WPF application a text containing links. The texts are static and known during compile time.

The following is doing want i want when working directly on the XAML file:

       <TextBlock Name="TextBlockWithHyperlink">
                Some text 
                <Hyperlink 
                    NavigateUri="http://somesite.com"
                    RequestNavigate="Hyperlink_RequestNavigate">
                    some site
                </Hyperlink>
                some more text
      </TextBlock>

Since using MVVM i want to bind the Textblock to a newly constructed Textblock object, through a dependency property. The XAML then looks like this:

        <StackPanel Grid.Row="1" Margin="5 0 0 0">
            <TextBlock Height="16" FontWeight="Bold" Text="Generic Text with link"/>
          
            <TextBlock Text="{Binding Path=TextWithLink, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
        </StackPanel>

In my ViewModel i place

private void someMethod(){
   ...
   TextWithLink = CreateText();
   ...
}

private TextBlock(){
   TextBlock tb = new TextBlock();     
   Run run1 = new Run("Text preceeding the hyperlink.");
   Run run2 = new Run("Text following the hyperlink.");
   Run run3 = new Run("Link Text.");

   Hyperlink hyperl = new Hyperlink(run3);
   hyperl.NavigateUri = new Uri("http://search.msn.com");
   

   tb.Inlines.Add(run1);
   tb.Inlines.Add(hyperl);
   tb.Inlines.Add(run2);
   return tb;
}

private TextBlock _textWithLink;
public TextBlock TextWithLink { 
  get => _textWithLink;
  set{
    _textWithLink = value; 
    OnPropertyChanged();
  }
}

The dependency property setup is working i see a new TextBlock getting assigned to the XAML control, however there is no content shown, just the displayed text reads

System.Windows.Controls.TextBlock

rather than the content. I cannot get my head around what i have to change to show the desired mixed text. Happy for an help.


Solution

  • Instead of using a TextBlock instance in a view model, you should instead use a collection of Inline elements with a UI element that accept it as the source of a Binding.

    Since the Inlines property of a TextBlock is not bindable, you may create a deribed TextBlock with a bindable property like this:

    public class MyTextBlock : TextBlock
    {
        public static readonly DependencyProperty BindableInlinesProperty =
            DependencyProperty.Register(
                nameof(BindableInlines),
                typeof(IEnumerable<Inline>),
                typeof(MyTextBlock),
                new PropertyMetadata(null, BindableInlinesPropertyChanged));
    
        public IEnumerable<Inline> BindableInlines
        {
            get { return (IEnumerable<Inline>)GetValue(BindableInlinesProperty); }
            set { SetValue(BindingGroupProperty, value); }
        }
    
        private static void BindableInlinesPropertyChanged(
            DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var textblock = (MyTextBlock)o;
            var inlines = (IEnumerable<Inline>)e.NewValue;
    
            textblock.Inlines.Clear();
    
            if (inlines != null)
            {
                textblock.Inlines.AddRange(inlines);
            }
        }
    }
    

    Now you may use it like

    <local:MyTextBlock BindableInlines="{Binding SomeInlines}"/>
    

    with a view model property like this:

    public IEnumerable<Inline> SomeInlines { get; set; }
    
    ...
    
    var link = new Hyperlink(new Run("Search"));
    link.NavigateUri = new Uri("http://search.msn.com");
    link.RequestNavigate += (s, e) => Process.Start(e.Uri.ToString());
    
    SomeInlines = new List<Inline>
    {
        new Run("Some text "),
        link,
        new Run(" and more text")
    };