Question

My question is similar to this: WPF Generate TextBlock Inlines but I don't have enough reputation to comment. Here is the attached property class:

public class Attached
{
    public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
        "FormattedText",
        typeof(string),
        typeof(TextBlock),
        new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure));

    public static void SetFormattedText(DependencyObject textBlock, string value)
    {
        textBlock.SetValue(FormattedTextProperty, value);
    }

    public static string GetFormattedText(DependencyObject textBlock)
    {
        return (string)textBlock.GetValue(FormattedTextProperty);
    }

    private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var textBlock = d as TextBlock;
        if (textBlock == null)
        {
            return;
        }

        var formattedText = (string)e.NewValue ?? string.Empty;
        formattedText = string.Format("<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{0}</Span>", formattedText);

        textBlock.Inlines.Clear();
        using (var xmlReader = XmlReader.Create(new StringReader(formattedText)))
        {
            var result = (Span)XamlReader.Load(xmlReader);
            textBlock.Inlines.Add(result);
        }
    }
}

I'm using this attached property class and trying to apply it to a textblock to make the text recognize inline values like bold, underline, etc from a string in my view model class. I have the following XAML in my textblock:

<TextBlock Grid.Row="1" Grid.Column="1" TextWrapping="Wrap" my:Attached.FormattedText="test" />

However I get nothing at all in the textblock when I start the program. I also would like to bind the text to a property on my view model eventually but wanted to get something to show up first...

Sorry this is probably a newbie question but I can't figure out why it's not working. It doesn't give me any error here, just doesn't show up. If I try to bind, it gives me the error:

{"A 'Binding' cannot be set on the 'SetFormattedText' property of type 'TextBlock'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject."}

Was it helpful?

Solution

First, the type of property needs to be a class name, not the type TextBlock:

public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
    "FormattedText",
    typeof(string),
    typeof(TextBlock), <----- Here

Second, the handler is not called, it must be registered here:

new FrameworkPropertyMetadata(string.Empty, 
                              FrameworkPropertyMetadataOptions.AffectsMeasure,
                              YOUR_PropertyChanged_HANDLER)

Thirdly, an example to work, you need to specify the input string like this:

<Bold>My little text</Bold>

Working example is below:

XAML

<Window x:Class="InlineTextBlockHelp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:this="clr-namespace:InlineTextBlockHelp"
        Title="MainWindow" Height="350" Width="525">

    <Grid>
        <TextBlock Name="TestText"
                   this:AttachedPropertyTest.FormattedText="TestString"
                   Width="200"
                   Height="100" 
                   TextWrapping="Wrap" />

        <Button Name="TestButton"
                Width="100"
                Height="30"
                VerticalAlignment="Top"
                Content="TestClick" 
                Click="Button_Click" />
    </Grid>
</Window>

Code-behind

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        string inlineExpression = "<Bold>Once I saw a little bird, go hop, hop, hop.</Bold>";
        AttachedPropertyTest.SetFormattedText(TestText, inlineExpression);
    }
}

public class AttachedPropertyTest
{
    public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
        "FormattedText",
        typeof(string),
        typeof(AttachedPropertyTest),
        new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure, FormattedTextPropertyChanged));

    public static void SetFormattedText(DependencyObject textBlock, string value)
    {
        textBlock.SetValue(FormattedTextProperty, value);
    }

    public static string GetFormattedText(DependencyObject textBlock)
    {
        return (string)textBlock.GetValue(FormattedTextProperty);
    }

    private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var textBlock = d as TextBlock;

        if (textBlock == null)
        {
            return;
        }

        var formattedText = (string)e.NewValue ?? string.Empty;
        formattedText = string.Format("<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{0}</Span>", formattedText);

        textBlock.Inlines.Clear();
        using (var xmlReader = XmlReader.Create(new StringReader(formattedText)))
        {
            var result = (Span)XamlReader.Load(xmlReader);
            textBlock.Inlines.Add(result);
        }
    }
}

Initially be plain text, after clicking on the Button will be assigned to inline text.

Example for MVVM version

To use this example in MVVM style, you need to create the appropriate property in the Model/ViewModel and associate it with the attached dependency property like this:

<TextBlock Name="TestText"
           PropertiesExtension:TextBlockExt.FormattedText="{Binding Path=InlineText, 
                                                                    Mode=TwoWay,
                                                                    UpdateSourceTrigger=PropertyChanged}"
           Width="200"
           Height="100" 
           TextWrapping="Wrap" />

Property in Model/ViewModel must support method NotifyPropertyChanged.

Here is a full sample:

AttachedProperty

public class TextBlockExt
{
    public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
        "FormattedText",
        typeof(string),
        typeof(TextBlockExt),
        new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure, FormattedTextPropertyChanged));

    public static void SetFormattedText(DependencyObject textBlock, string value)
    {
        textBlock.SetValue(FormattedTextProperty, value);
    }

    public static string GetFormattedText(DependencyObject textBlock)
    {
        return (string)textBlock.GetValue(FormattedTextProperty);
    }

    private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var textBlock = d as TextBlock;

        if (textBlock == null)
        {
            return;
        }

        var formattedText = (string)e.NewValue ?? string.Empty;
        formattedText = string.Format("<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{0}</Span>", formattedText);

        textBlock.Inlines.Clear();
        using (var xmlReader = XmlReader.Create(new StringReader(formattedText)))
        {
            var result = (Span)XamlReader.Load(xmlReader);
            textBlock.Inlines.Add(result);
        }
    }
}

MainViewModel

public class MainViewModel : NotificationObject
{
    private string _inlineText = "";

    public string InlineText
    {
        get
        {
            return _inlineText;
        }

        set
        {
            _inlineText = value;
            NotifyPropertyChanged("InlineText");
        }
    }
}

MainWindow.xaml

<Window x:Class="InlineTextBlockHelp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ViewModels="clr-namespace:InlineTextBlockHelp.ViewModels"
        xmlns:PropertiesExtension="clr-namespace:InlineTextBlockHelp.PropertiesExtension"
        Title="MainWindow" Height="350" Width="525"
        ContentRendered="Window_ContentRendered">

    <Window.DataContext>
        <ViewModels:MainViewModel />
    </Window.DataContext>

    <Grid>
        <TextBlock Name="TestText"
                   PropertiesExtension:TextBlockExt.FormattedText="{Binding Path=InlineText, 
                                                                            Mode=TwoWay,
                                                                            UpdateSourceTrigger=PropertyChanged}"
                   Width="200"
                   Height="100" 
                   TextWrapping="Wrap" />
    </Grid>
</Window>

Code-behind (just for test)

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_ContentRendered(object sender, EventArgs e)
    {
        MainViewModel mainViewModel = this.DataContext as MainViewModel;

        mainViewModel.InlineText = "<Bold>Once I saw a little bird, go hop, hop, hop.</Bold>";
    }
}

This example is available at this link.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top