-3

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.

theDrifter
  • 1,631
  • 6
  • 24
  • 40
  • 1
    `set => OnPropertyChanged();` does obviously not set the property's backing field. It should be `set { _textWithLink = value; OnPropertyChanged(); }`. Besides that, you would not use a TextBlock instance in a view model. – Clemens Oct 14 '20 at 11:19
  • As said the setup of the DepProp was not the issue, this was rather forgotten while posting the question. Why wouldn't i use a TextBlock obj in my Vm? – theDrifter Oct 14 '20 at 11:23
  • Because it is a UI element, i.e. a view class. Search StackOverflow for how to set the Inlines property of a TextBlock in XAML to an InlinesCollection. While Inlines is not a dependency property and hence not directly bindable, there are workarounds, e.g. with an attached property or a derived TextBlock class. – Clemens Oct 14 '20 at 11:26
  • 1
    E.g. this: https://stackoverflow.com/a/636741/1136211 – Clemens Oct 14 '20 at 11:32
  • FYI, chosen binding parameters Mode=OneWay and UpdateSourceTrigger=PropertyChanged are mutually exclusive. "Since using MVVM i want to bind the Textblock to a newly constructed Textblock object, through a dependency property." - do you really understand concepts of MVVM, bindings and DP? "The following is doing want i want when working directly on the XAML file" - there is no any sane reason to change it. – ASh Oct 14 '20 at 11:38
  • @ASh maybe i am mixing things up here. However there is a sane reason for wanting to do that – theDrifter Oct 14 '20 at 11:44

1 Answers1

0

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")
};
Clemens
  • 123,504
  • 12
  • 155
  • 268