Pages

Monday, September 10, 2012

Silverlight TextBlock: Convert Text to InlineCollection

Silverlight TextBlock: Convert Text to InlineCollection

Consider the following markup declaration:

<TextBlock>  <Run>abcdefghijklmnopqrstuvwxyz</Run>  <LineBreak/>  <Run>0123456789</Run> </TextBlock> 

I want to bind any data to a TextBlock and convert this data to an InlineCollection. It would be very elegant to do this with Data Binding. The other way is to observe my Data Source and use the Inlines-Property from the TextBlock class in Code Behind.

I tried the following but it didn't work:

<TextBlock>  <Binding Path="MyDataSource" Converter="{StaticResource MyTextConverter}"/> </TextBlock> 

What I want to do is achieve auto font-scaling by encapsulating my TextBlock in a ViewBox, but also generating LineBreaks after an arbitrary letter count.

Thanks in advance for any help. Best regards.

Answers & Comments...

Answer: 1

It's too easy to be true ... "\r\n" at the right position does the job.

by : Sascha Hollhttp://stackoverflow.com/users/547231

Answer: 2

I guess you just have to assign yout converter to the TextProperty of your TextBlock. Then your converter add \r\n to get a linebreak when you need it.

by : TerenceJacksonhttp://stackoverflow.com/users/489360

Answer: 3

I think I answered this by sub-classing the TextBlock to make the InlineCollection bindable and writing a Convertor between a String of xaml markup and an InlineCollection(or generic list of Inlines)]

Details here on the Stack

As pointed out by infografnet, the Silverlight version of the TextBlock class is sealed which makes my WPF sub-classing suggestion invalid.

by : Jodrellhttp://stackoverflow.com/users/659190

Answer: 4

I used this answer for a WPF related question to come up with a Silverlight Behavior that can add simple formatting to a TextBlock in Silverlight. Formatting is done through Xml elements that work similar to how a TextBlock can be formatted within the element itself. Bold, Italic, Underline, and LineBreak elements can be used.

First, the behavior is defined as follows:

  public static class FormattedTextBlockBehavior {     public static string GetFormattedText(DependencyObject obj)     {         return (string) obj.GetValue(FormattedTextProperty);     }      public static void SetFormattedText(DependencyObject obj, string value)     {         obj.SetValue(FormattedTextProperty, value);     }      public static readonly DependencyProperty FormattedTextProperty =             DependencyProperty.RegisterAttached("FormattedText",                                                 typeof (string),                                                 typeof (FormattedTextBlockBehavior),                                                 new PropertyMetadata("", FormattedTextChanged));      private static Inline Traverse(string value)     {         // Get the sections/inlines         string[] sections = SplitIntoSections(value);          // Check for grouping         if (sections.Length.Equals(1))         {             string section = sections[0];             string token; // E.g <Bold>             int tokenStart, tokenEnd; // Where the token/section starts and ends.              // Check for token             if (GetTokenInfo(section, out token, out tokenStart, out tokenEnd))             {                 // Get the content to further examination                 string content = token.Length.Equals(tokenEnd - tokenStart)                                  ? null                                  : section.Substring(token.Length, section.Length - 1 - token.Length*2);                  switch (token)                 {                     case "<Bold>":                         var b = new Bold();                         b.Inlines.Add(Traverse(content));                         return b;                     case "<Italic>":                         var i = new Italic();                         i.Inlines.Add(Traverse(content));                         return i;                     case "<Underline>":                         var u = new Underline();                         u.Inlines.Add(Traverse(content));                         return u;                     case "<LineBreak/>":                         return new LineBreak();                     case "<LineBreak />":                         return new LineBreak();                     default:                         return new Run                                    {                                        Text = section                                    };                 }             }             return new Run                        {                            Text = section                        };         }         var span = new Span();          foreach (string section in sections)             span.Inlines.Add(Traverse(section));          return span;     }      /// <summary>     /// Examines the passed string and find the first token, where it begins and where it ends.     /// </summary>     /// <param name="value">The string to examine.</param>     /// <param name="token">The found token.</param>     /// <param name="startIndex">Where the token begins.</param>     /// <param name="endIndex">Where the end-token ends.</param>     /// <returns>True if a token was found.</returns>     private static bool GetTokenInfo(string value, out string token, out int startIndex, out int endIndex)     {         token = null;         endIndex = -1;         startIndex = value.IndexOf("<");         int startTokenEndIndex = value.IndexOf(">");          // No token here         if (startIndex < 0)             return false;          // No token here         if (startTokenEndIndex < 0)             return false;          token = value.Substring(startIndex, startTokenEndIndex - startIndex + 1);          // Check for closed token. E.g. <LineBreak/>         if (token.EndsWith("/>"))         {             endIndex = startIndex + token.Length;             return true;         }          string endToken = token.Insert(1, "/");          // Detect nesting;         int nesting = 0;         int pos = 0;         do         {             int tempStartTokenIndex = value.IndexOf(token, pos);             int tempEndTokenIndex = value.IndexOf(endToken, pos);              if (tempStartTokenIndex >= 0 && tempStartTokenIndex < tempEndTokenIndex)             {                 nesting++;                 pos = tempStartTokenIndex + token.Length;             }             else if (tempEndTokenIndex >= 0 && nesting > 0)             {                 nesting--;                 pos = tempEndTokenIndex + endToken.Length;             }             else // Invalid tokenized string                return false;          } while (nesting > 0);          endIndex = pos;          return true;     }      /// <summary>     /// Splits the string into sections of tokens and regular text.     /// </summary>     /// <param name="value">The string to split.</param>     /// <returns>An array with the sections.</returns>     private static string[] SplitIntoSections(string value)     {         var sections = new List<string>();         while (!string.IsNullOrEmpty(value))         {             string token;             int tokenStartIndex, tokenEndIndex;             // Check if this is a token section             if (GetTokenInfo(value, out token, out tokenStartIndex, out tokenEndIndex))             {                 // Add pretext if the token isn't from the start                 if (tokenStartIndex > 0)                     sections.Add(value.Substring(0, tokenStartIndex));                  sections.Add(value.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex));                 value = value.Substring(tokenEndIndex); // Trim away             }             else             {                 // No tokens, just add the text                 sections.Add(value);                 value = null;             }         }          return sections.ToArray();     }      private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)     {         var value = e.NewValue as string;          var textBlock = sender as TextBlock;          if (textBlock != null)             textBlock.Inlines.Add(Traverse(value));     }  

Then, from the xaml itself, the behavior can be referenced as follows:

  xmlns:Helpers="clr-namespace:Namespace.Helpers"     <TextBlock Grid.Row="0" Margin="10,10,10,10" TextWrapping="Wrap" FontSize="14" Helpers:FormattedTextBlockBehavior.FormattedText="{Binding ViewModel.Text}" /> 
by : Steve Wranovskyhttp://stackoverflow.com/users/3429




1 comment:

Anonymous said...

Great solution! It comes extremely handy when localising rich text data!

Post a Comment

Send us your comment related to the topic mentioned on the blog