wpf xaml binding resources textblock

wpf - ¿Cómo enlazar un TextBlock a un recurso que contiene texto formateado?



wpf templates (7)

Tengo un TextBlock en mi ventana de WPF.

<TextBlock> Some <Bold>formatted</Bold> text. </TextBlock>

Cuando se renderiza se ve así,

Algunos textos con formato.

Mi pregunta es, ¿puedo enlazar este "contenido" en línea a un recurso en mi aplicación?

Llegué hasta

Haciendo una cadena de recursos de aplicación,

myText="Some <Bold>formatted</Bold> text."

y el siguiente xaml (algunos códigos se omiten por brevedad)

<Window xmlns:props="clr-namespace:MyApp.Properties"> <Window.Resources> <props:Resources x:Key="Resources"/> </Window.Resources> <TextBlock x:Name="Try1" Text="{Binding Source={StaticResource Resources} Path=myText}"/> <TextBlock x:Name="Try2"> <Binding Source="{StaticResource Resources}" Path="myText" /> </TextBlock> </Window>

Try1 se procesa con las etiquetas en su lugar y sin afectar el formato.

Algún <Bold> formateado <Bold> texto.

Try2 no compilará ni generará porque el recurso "myText" no es del tipo Inline sino de una cadena.

¿Es esta tarea aparentemente simple posible y si es así, cómo?


¿Qué hay de usar el comportamiento adjunto? El código de abajo solo maneja etiquetas en negrita. Cada palabra que debe estar en negrita debe estar envuelta en etiquetas en negrita. Probablemente quieras hacer que la clase acepte otros formatos también. Además, los espacios deben manejarse mejor, la clase elimina espacios consecutivos y agrega uno extra al final. Por lo tanto, considere la clase a continuación como código de demostración, que necesitará más trabajo para ser útil, pero debería comenzar.

XAML:

<Window x:Class="FormatTest.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:FormatTest="clr-namespace:FormatTest" Title="Window1" Height="300" Width="300"> <TextBlock FormatTest:FormattedTextBehavior.FormattedText="{Binding Path=Text}" /> </Window>

Código detrás:

using System; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; namespace FormatTest { public partial class Window1 : Window { public Window1() { InitializeComponent(); DataContext = this; } public string Text { get { return "Some <Bold>formatted</Bold> text."; } } } public static class FormattedTextBehavior { 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(FormattedTextBehavior), new UIPropertyMetadata("", FormattedTextChanged)); private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { TextBlock textBlock = sender as TextBlock; string value = e.NewValue as string; string[] tokens = value.Split('' ''); foreach (string token in tokens) { if (token.StartsWith("<Bold>") && token.EndsWith("</Bold>")) { textBlock.Inlines.Add(new Bold(new Run(token.Replace("<Bold>", "").Replace("</Bold>", "") + " "))); } else { textBlock.Inlines.Add(new Run(token + " ")); } } } } }


Aquí está mi código modificado para el formato de texto recursivo. Maneja Bold, Italic, Underline y LineBreak, pero puede extenderse fácilmente para admitir más (modificar la instrucción de cambio ).

public static class MyBehavior { 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(MyBehavior), new UIPropertyMetadata("", FormattedTextChanged)); 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>": return new Bold(Traverse(content)); case "<Italic>": return new Italic(Traverse(content)); case "<Underline>": return new Underline(Traverse(content)); case "<LineBreak/>": return new LineBreak(); default: return new Run(section); } } else return new Run(section); } else // Group together { Span 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> 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 temp_startTokenIndex = -1; int temp_endTokenIndex = -1; int pos = 0; do { temp_startTokenIndex = value.IndexOf(token, pos); temp_endTokenIndex = value.IndexOf(endToken, pos); if (temp_startTokenIndex >= 0 && temp_startTokenIndex < temp_endTokenIndex) { nesting++; pos = temp_startTokenIndex + token.Length; } else if (temp_endTokenIndex >= 0 && nesting > 0) { nesting--; pos = temp_endTokenIndex + 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> static string[] SplitIntoSections(string value) { List<string> 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) { string value = e.NewValue as string; TextBlock textBlock = sender as TextBlock; if (textBlock != null) textBlock.Inlines.Add(Traverse(value)); } }

Edición: (propuesto por Spook)

Una versión más corta, pero requiere que el texto sea válido para XML:

using System.Xml; // (...) public static class TextBlockHelper { #region FormattedText Attached dependency property 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(TextBlockHelper), new UIPropertyMetadata("", FormattedTextChanged)); private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { string value = e.NewValue as string; TextBlock textBlock = sender as TextBlock; if (textBlock != null) { textBlock.Inlines.Clear(); textBlock.Inlines.Add(Process(value)); } } #endregion static Inline Process(string value) { XmlDocument doc = new XmlDocument(); doc.LoadXml(value); Span span = new Span(); InternalProcess(span, doc.ChildNodes[0]); return span; } private static void InternalProcess(Span span, XmlNode xmlNode) { foreach (XmlNode child in xmlNode) { if (child is XmlText) { span.Inlines.Add(new Run(child.InnerText)); } else if (child is XmlElement) { switch (child.Name.ToUpper()) { case "B": case "BOLD": { Span boldSpan = new Span(); InternalProcess(boldSpan, child); Bold bold = new Bold(boldSpan); span.Inlines.Add(bold); break; } case "I": case "ITALIC": { Span italicSpan = new Span(); InternalProcess(italicSpan, child); Italic italic = new Italic(italicSpan); span.Inlines.Add(italic); break; } case "U": case "UNDERLINE": { Span underlineSpan = new Span(); InternalProcess(underlineSpan, child); Underline underline = new Underline(underlineSpan); span.Inlines.Add(underline); break; } } } } } }

Y un ejemplo de uso:

<RootItem xmlns:u="clr-namespace:MyApp.Helpers"> <TextBlock u:TextBlockHelper.FormattedText="{Binding SomeProperty}" /> </RootItem>


EDITAR:

Esta línea,

<props:Resources x:Key="Resources"/>

es un mal enfoque para acceder al espacio de nombres Project.Properties.Resources. Causa fallas incómodas al recompilar.

Mucho mejor usar x:Static para hacer algo como esto,

Text="{x:Static props:Resources.SomeText}"

en su encuadernación. Gracias a Ben

Bueno, así es como lo hice. No es perfecto pero funciona.

Recuerde, hay un recurso de proyecto llamado FormattedText.

cs:

// TextBlock with a bindable InlineCollection property. // Type is List(Inline) not InlineCollection becuase // InlineCollection makes the IDE xaml parser complain // presumably this is caused by an inherited attribute. public class BindableTextBlock : TextBlock { public static readonly DependencyProperty InlineCollectionProperty = DependencyProperty.Register( "InlineCollection", typeof(List<Inline>), typeof(BindableTextBlock), new UIPropertyMetadata(OnInlineCollectionChanged)); private static void OnInlineCollectionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { BinableTextBlock instance = sender as BindableTextBlock; if (instance != null) { List<Inline> newText = e.NewValue as List<Inline>; if (newText != null) { // Clear the underlying Inlines property instance.Inlines.Clear(); // Add the passed List<Inline> to the real Inlines instance.Inlines.AddRange(newText.ToList()); } } } public List<Inline> InlineCollection { get { return (List<Inline>)GetValue(InlineCollectionProperty); } set { SetValue(InlineCollectionProperty, value); } } } // Convertor between a string of xaml with implied run elements // and a generic list of inlines [ValueConversion(typeof(string), typeof(List<Inline>))] public class StringInlineCollectionConvertor : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string text = value as String; // a surrogate TextBlock to host an InlineCollection TextBlock results = new TextBlock(); if (!String.IsNullOrEmpty(text)) { //Arbritary literal acting as a replace token, //must not exist in the empty xaml definition. const string Replace = "xxx"; // add a dummy run element and replace it with the text results.Inlines.Add(new Run(Replace)); string resultsXaml = XamlWriter.Save(results); string resultsXamlWithText = resultsXaml.Replace(Replace, text); // deserialise the xaml back into our TextBlock results = XamlReader.Parse(resultsXamlWithText) as TextBlock; } return results.Inlines.ToList<Inline>(); } // Not clear when this will be called but included for completeness public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { String results = String.Empty; InlineCollection inlines = value as InlineCollection; if (inlines != null) { //read the xaml as xml and return the "content" var reader = XElement.Parse(XamlWriter.Save(inlines)).CreateReader(); reader.MoveToContent(); results = reader.ReadInnerXml(); } return results; } }

xaml

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:props="clr-namespace:Project.Properties" xmlns:local="clr-namespace:Project"> <Window.Resources> <props:Resources x:Key="Resources"/> <local:StringInlineCollectionConvertor x:Key="InlineConvert"/> </Window.Resources> <local:BindableTextBlock InlineCollection=" {Binding Source={StaticResource Resources}, Path=FormattedText, Converter={StaticResource InlineConvert}}"/> </Window>

Hice 2 clases. Un TextBlock subclasificado con un InlineCollection "vinculable" y un IValueConverter para convertir la colección desde y hacia una cadena.

El uso de InlineCollection directamente como el tipo de la propiedad hecha VS2010 se queja, aunque el código todavía funcionó bien. Cambié a una lista genérica de Inlines. Supongo que hay un atributo heredado que indica a VS que InlineCollection no tiene ningún constructor.

Intenté hacer de la propiedad InlineCollection la propiedad de contenido de BindableTextBlock pero tuve problemas y fuera de tiempo. Por favor, siéntase libre de dar el siguiente paso y contármelo.

Pido disculpas por cualquier errata pero este código tuvo que ser transcrito y saneado.

Si hay una mejor manera de hacerlo, seguramente debe haberlo, por favor dígamelo también. ¿No sería bueno si esta funcionalidad estuviera incorporada o, me he perdido algo?


Este trabajo para mí:

XAML:

<phone:PhoneApplicationPage x:Class="MyAPP.Views.Class" xmlns:utils="clr-namespace:MyAPP.Utils">

y tu TextBlock XAML:

<TextBlock utils:TextBlockHelper.FormattedText="{Binding Text}" />

CÓDIGO:

public static class TextBlockHelper { public static string GetFormattedText(DependencyObject textBlock) { return (string)textBlock.GetValue(FormattedTextProperty); } public static void SetFormattedText(DependencyObject textBlock, string value) { textBlock.SetValue(FormattedTextProperty, value); } public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached("FormattedText", typeof(string), typeof(TextBlock), new PropertyMetadata(string.Empty, (sender, e) => { string text = e.NewValue as string; var textB1 = sender as TextBlock; if (textB1 != null) { textB1.Inlines.Clear(); var str = text.Split(new string[] { "<b>", "</b>" }, StringSplitOptions.None); for (int i = 0; i < str.Length; i++) textB1.Inlines.Add(new Run { Text = str[i], FontWeight = i % 2 == 1 ? FontWeights.Bold : FontWeights.Normal }); } })); }

USE en su enlace de cadena:

String Text = Text <b>Bold</b>;


He añadido hipervínculo y soporte de imagen a la solución Vincents :

public static class FormattedTextBlock { 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(FormattedTextBlock), new UIPropertyMetadata("", FormattedTextChanged)); 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.ToUpper()) { case "<B>": case "<BOLD>": /* <b>Bold text</b> */ return new Bold(Traverse(content)); case "<I>": case "<ITALIC>": /* <i>Italic text</i> */ return new Italic(Traverse(content)); case "<U>": case "<UNDERLINE>": /* <u>Underlined text</u> */ return new Underline(Traverse(content)); case "<BR>": case "<BR/>": case "<LINEBREAK/>": /* Line 1<br/>line 2 */ return new LineBreak(); case "<A>": case "<LINK>": /* <a>{http://www.google.de}Google</a> */ var start = content.IndexOf("{"); var end = content.IndexOf("}"); var url = content.Substring(start + 1, end - 1); var text = content.Substring(end + 1); var link = new Hyperlink(); link.NavigateUri = new System.Uri(url); link.RequestNavigate += Hyperlink_RequestNavigate; link.Inlines.Add(text); return link; case "<IMG>": case "<IMAGE>": /* <image>pack://application:,,,/ProjectName;component/directory1/directory2/image.png</image> */ var image = new Image(); var bitmap = new BitmapImage(new Uri(content)); image.Source = bitmap; image.Width = bitmap.Width; image.Height = bitmap.Height; var container = new InlineUIContainer(); container.Child = image; return container; default: return new Run(section); } } else return new Run(section); } else // Group together { Span span = new Span(); foreach(string section in sections) span.Inlines.Add(Traverse(section)); return span; } } static void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) { Process.Start(e.Uri.ToString()); } /// <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> 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 temp_startTokenIndex = -1; int temp_endTokenIndex = -1; int pos = 0; do { temp_startTokenIndex = value.IndexOf(token, pos); temp_endTokenIndex = value.IndexOf(endToken, pos); if(temp_startTokenIndex >= 0 && temp_startTokenIndex < temp_endTokenIndex) { nesting++; pos = temp_startTokenIndex + token.Length; } else if(temp_endTokenIndex >= 0 && nesting > 0) { nesting--; pos = temp_endTokenIndex + 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> static string[] SplitIntoSections(string value) { List<string> 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) { string value = e.NewValue as string; TextBlock textBlock = sender as TextBlock; if(textBlock != null) textBlock.Inlines.Add(Traverse(value)); } }

Gracias por la gran plantilla, funciona como un encanto!


Lo mismo he implementado utilizando Comportamiento. Código dado abajo:

public class FormatTextBlock : Behavior<System.Windows.Controls.TextBlock> { public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.Register( "FormattedText", typeof(string), typeof(FormatTextBlock), new PropertyMetadata(string.Empty, OnFormattedTextChanged)); public string FormattedText { get { return (string)AssociatedObject.GetValue(FormattedTextProperty); } set { AssociatedObject.SetValue(FormattedTextProperty, value); } } private static void OnFormattedTextChanged(DependencyObject textBlock, DependencyPropertyChangedEventArgs eventArgs) { System.Windows.Controls.TextBlock currentTxtBlock = (textBlock as FormatTextBlock).AssociatedObject; string text = eventArgs.NewValue as string; if (currentTxtBlock != null) { currentTxtBlock.Inlines.Clear(); string[] strs = text.Split(new string[] { "<Bold>", "</Bold>" }, StringSplitOptions.None); for (int i = 0; i < strs.Length; i++) { currentTxtBlock.Inlines.Add(new Run { Text = strs[i], FontWeight = i % 2 == 1 ? FontWeights.Bold : FontWeights.Normal }); } } } }

XAML - importar espacio de nombres

<UserControl x:Class="MyClass" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:behav="clr-namespace:myAssembly.myNameSapce;assembly=myAssembly" >

Luego usar el comportamiento como:

<TextBlock TextWrapping="Wrap"> <i:Interaction.Behaviors> <behav:FormatTextBlock FormattedText="{Binding Path=UIMessage}" /> </i:Interaction.Behaviors> </TextBlock>


Terminé necesitando hacer esto en mi aplicación y tuve que admitir muchas de las marcas posibles normalmente en las líneas de TextBlock, así que tomé la respuesta del Programador de Wallstreet (que funciona a la perfección y es mucho menos complicada que la mayoría de las otras respuestas que encontré sobre este tema) y ampliado en él. Me imagino que alguien más podría encontrar esto útil.

No lo he probado completamente con TODAS las etiquetas todavía, pero todas las que he probado han funcionado a la perfección. También sospecho que no es el código más rápido del mundo, pero mis propias pruebas con varios miles de mensajes formateados en un ListView parecían sorprendentemente rápidos. YMMV. El código está abajo:

XAML:

<Window x:Class="FormatTest.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:FormatTest="clr-namespace:FormatTest" Title="Window1" Height="300" Width="300"> <TextBlock FormatTest:FormattedTextBehavior.FormattedText="{Binding Path=Text}" /> </Window>

DO#

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media; namespace FormatTest { public static class FormattedTextBehavior { public class TextPart { public String mType = String.Empty; public Inline mInline = null; public InlineCollection mChildren = null; public TextPart() {} public TextPart(String t, Inline inline, InlineCollection col) { mType = t; mInline = inline; mChildren = col; } } private static Regex mRegex = new Regex(@"<(?<Span>/?[^>]*)>", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static Regex mSpanRegex = new Regex("(?<Key>[^//s=]+)=/"(?<Val>[^//s/"]*)/"", RegexOptions.Compiled | RegexOptions.IgnoreCase); 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(FormattedTextBehavior), new UIPropertyMetadata("", FormattedTextChanged)); private static void FormattedTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { TextBlock textBlock = sender as TextBlock; FormatText(e.NewValue as string, new TextPart("TextBlock", null, textBlock.Inlines)); } public static void FormatText(String s, TextPart root) { int len = s.Length; int lastIdx = 0; List<TextPart> parts = new List<TextPart>(); parts.Add(root); Match m = mRegex.Match(s); while (m.Success) { String tag = m.Result("${Span}"); if (tag.StartsWith("/")) { String prevStr = s.Substring(lastIdx, m.Index - lastIdx); TextPart part = parts.Last(); if (!String.IsNullOrEmpty(prevStr)) { if (part.mChildren != null) { part.mChildren.Add(new Run(prevStr)); } else if (part.mInline is Run) { (part.mInline as Run).Text = prevStr; } } if (!tag.Substring(1).Equals(part.mType, StringComparison.InvariantCultureIgnoreCase)) { Logger.LogD("Mismatched End Tag ''" + tag.Substring(1) + "'' (expected </" + part.mType + ">) at position " + m.Index.ToString() + " in String ''" + s + "''"); } if (parts.Count > 1) { parts.RemoveAt(parts.Count - 1); TextPart parentPart = parts.Last(); if (parentPart.mChildren != null) { parentPart.mChildren.Add(part.mInline); } } } else { TextPart prevPart = parts.Last(); String prevStr = s.Substring(lastIdx, m.Index - lastIdx); if (!String.IsNullOrEmpty(prevStr)) { if (prevPart.mChildren != null) { prevPart.mChildren.Add(new Run(prevStr)); } else if (prevPart.mInline is Run) { (prevPart.mInline as Run).Text = prevStr; } } bool hasAttributes = false; TextPart part = new TextPart(); if (tag.StartsWith("bold", StringComparison.InvariantCultureIgnoreCase)) { part.mType = "BOLD"; part.mInline = new Bold(); part.mChildren = (part.mInline as Bold).Inlines; } else if (tag.StartsWith("underline", StringComparison.InvariantCultureIgnoreCase)) { part.mType = "UNDERLINE"; part.mInline = new Underline(); part.mChildren = (part.mInline as Underline).Inlines; } else if (tag.StartsWith("italic", StringComparison.InvariantCultureIgnoreCase)) { part.mType = "ITALIC"; part.mInline = new Italic(); part.mChildren = (part.mInline as Italic).Inlines; } else if (tag.StartsWith("linebreak", StringComparison.InvariantCultureIgnoreCase)) { part.mType = "LINEBREAK"; part.mInline = new LineBreak(); } else if (tag.StartsWith("span", StringComparison.InvariantCultureIgnoreCase)) { hasAttributes = true; part.mType = "SPAN"; part.mInline = new Span(); part.mChildren = (part.mInline as Span).Inlines; } else if (tag.StartsWith("run", StringComparison.InvariantCultureIgnoreCase)) { hasAttributes = true; part.mType = "RUN"; part.mInline = new Run(); } else if (tag.StartsWith("hyperlink", StringComparison.InvariantCultureIgnoreCase)) { hasAttributes = true; part.mType = "HYPERLINK"; part.mInline = new Hyperlink(); part.mChildren = (part.mInline as Hyperlink).Inlines; } if (hasAttributes && part.mInline != null) { Match m2 = mSpanRegex.Match(tag); while (m2.Success) { String key = m2.Result("${Key}"); String val = m2.Result("${Val}"); if (key.Equals("FontWeight", StringComparison.InvariantCultureIgnoreCase)) { FontWeight fw = FontWeights.Normal; try { fw = (FontWeight)new FontWeightConverter().ConvertFromString(val); } catch (Exception) { fw = FontWeights.Normal; } part.mInline.FontWeight = fw; } else if (key.Equals("FontSize", StringComparison.InvariantCultureIgnoreCase)) { double fs = part.mInline.FontSize; if (Double.TryParse(val, out fs)) { part.mInline.FontSize = fs; } } else if (key.Equals("FontStretch", StringComparison.InvariantCultureIgnoreCase)) { FontStretch fs = FontStretches.Normal; try { fs = (FontStretch)new FontStretchConverter().ConvertFromString(val); } catch (Exception) { fs = FontStretches.Normal; } part.mInline.FontStretch = fs; } else if (key.Equals("FontStyle", StringComparison.InvariantCultureIgnoreCase)) { FontStyle fs = FontStyles.Normal; try { fs = (FontStyle)new FontStyleConverter().ConvertFromString(val); } catch (Exception) { fs = FontStyles.Normal; } part.mInline.FontStyle = fs; } else if (key.Equals("FontFamily", StringComparison.InvariantCultureIgnoreCase)) { if (!String.IsNullOrEmpty(val)) { FontFamily ff = new FontFamily(val); if (Fonts.SystemFontFamilies.Contains(ff)) { part.mInline.FontFamily = ff; } } } else if (key.Equals("Background", StringComparison.InvariantCultureIgnoreCase)) { Brush b = part.mInline.Background; try { b = (Brush)new BrushConverter().ConvertFromString(val); } catch (Exception) { b = part.mInline.Background; } part.mInline.Background = b; } else if (key.Equals("Foreground", StringComparison.InvariantCultureIgnoreCase)) { Brush b = part.mInline.Foreground; try { b = (Brush)new BrushConverter().ConvertFromString(val); } catch (Exception) { b = part.mInline.Foreground; } part.mInline.Foreground = b; } else if (key.Equals("ToolTip", StringComparison.InvariantCultureIgnoreCase)) { part.mInline.ToolTip = val; } else if (key.Equals("Text", StringComparison.InvariantCultureIgnoreCase) && part.mInline is Run) { (part.mInline as Run).Text = val; } else if (key.Equals("NavigateUri", StringComparison.InvariantCultureIgnoreCase) && part.mInline is Hyperlink) { (part.mInline as Hyperlink).NavigateUri = new Uri(val); } m2 = m2.NextMatch(); } } if (part.mInline != null) { if (tag.TrimEnd().EndsWith("/")) { if (prevPart.mChildren != null) { prevPart.mChildren.Add(part.mInline); } } else { parts.Add(part); } } } lastIdx = m.Index + m.Length; m = m.NextMatch(); } if (lastIdx < (len - 1)) { root.mChildren.Add(new Run(s.Substring(lastIdx))); } } } }