I have a TextBlock in my WPF window.
Some formatted text.
When it is r
I've added hyperlink and image support to Vincents solution:
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
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 "":
case "":
/* Bold text */
return new Bold(Traverse(content));
case "":
case "":
/* Italic text */
return new Italic(Traverse(content));
case "":
case "":
/* Underlined text */
return new Underline(Traverse(content));
case "
":
case "
":
case " ":
/* Line 1
line 2 */
return new LineBreak();
case "":
case "":
/* {http://www.google.de}Google */
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 "
":
case "":
/* pack://application:,,,/ProjectName;component/directory1/directory2/image.png */
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());
}
///
/// Examines the passed string and find the first token, where it begins and where it ends.
///
/// The string to examine.
/// The found token.
/// Where the token begins.
/// Where the end-token ends.
/// True if a token was found.
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.
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;
}
///
/// Splits the string into sections of tokens and regular text.
///
/// The string to split.
/// An array with the sections.
static string[] SplitIntoSections(string value)
{
List sections = new List();
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));
}
}
Thanks Vincent for the great template, it works like a charm!