XAML is cool. It lets you do stuff like this to put some formatted text into a button:
<Button> <TextBlock> Click the <Italic>Button</Italic> to<LineBreak />launch the <Bold>Rocket</Bold> </TextBlock> </Button>
But the question I have today is: How do you do that in code?
(And as soon as I start asking questions like that, I hear voices in my head — I think they're the voices of the Avalon developers — saying "Why would you even want to do it in code? Why can't you just do it in XAML and be blissful like the rest of us?" I agree. I'm totally demented. But I plead your indulgence for just a few minutes.)
I know I'm being naive when I consider that it would be nice doing it like this:
Button btn = new Button(); TextBlock text = new TextBlock("Click the <Italic>Button</Italic> " + "to<LineBreak />launch the " + "<Bold>Rocket</Bold>"); // Can't be done! btn.Content = text;
There isn't even a TextBlock constructor that accepts plain old text, let alone a chunk of XAML.. Still, it's not hard to conceive of a TextBlock property that might let you pass a little XAML, because you can actually do something like that in code. Here is some totally workable code:
string str = "<TextBlock xmlns=\"http://schemas.microsoft.com/winfx/avalon/2005\">" + "Click the <Italic>Button</Italic> " + "to<LineBreak />launch the " + "<Bold>Rocket</Bold>" + "</TextBlock>" StringReader strreader = new StringReader(str); XmlTextReader xmlreader = new XmlTextReader(strreader); TextBlock text = (TextBlock)XamlReader.Load(xmlreader);
It's a little convoluted, but basically you're trying to persuade the static XamlReader.Load method to accept and parse a block of XAML and convert it into a TextBlock object. (There's an alternative method involving MemoryStream and StreamWriter but this is somewhat simpler.) Once you have that TextBlock object, the rest is easy:
Button btn = new Button(); btn.Content = text;
Now let's try to do it without any XAML at all. The crucial property of TextBlock is Inlines, which is an object of type InlineCollection, which is a collection of Inline objects. Here's a partial class hierarchy starting from Inline showing the classes we need:
Run defines a Text property and the class is documented as "a uniformatted run of unicode characters." Span defines both a Text property and (like TextBlock) an Inlines property that is another collection of Inline objects. As the documentation states, Span is "used for grouping other Inline elements."
All these classes define parameterless constructors. In addition, Run has a constructor that accepts a string while Span and its derivatives all define constructors that accept another Inline.
The Add method of InlinesCollection accepts text, an Inline object, and even a UIElement object. (In the last case, an InlineUIContainer — another descendent of Inline that for simplicity I omitted from the class hiearchy — is created. But this is way beyond the focus of the current exercise.)
Taking all this into account, the simplest approach I've found to reproduce the button with the formatted text in code is the following:
TextBlock text = new TextBlock(); text.Inlines.Add("Click the "); text.Inlines.Add(new Italic(new Run("Button"))); text.Inlines.Add(" to"); text.Inlines.Add(new LineBreak()); text.Inlines.Add("launch the "); text.Inlines.Add(new Bold(new Run("Rocket"))); Button btn = new Button(); btn.Content = text;
Now this is not too bad, and it's certainly simpler than some earlier code I wrote to do the same thing. In that earlier code, I had stuff like this:
Italic ital = new Italic(); ital.Text = "Button"; text.Inlines.Add(ital);
It wasn't until I realized I could pass a string to the Run constructor and then the Run object to Italic and Bold that the whole thing straightened up into a nice neat line of Add calls.
Still, it'd sure be nice having an additional constructor for Bold and Italic (and etc) that accepted a string. It would then be possible to get rid of all the Run objects in the code shown above, and the result would look something like this:
TextBlock text = new TextBlock(); text.Inlines.Add("Click the "); text.Inlines.Add(new Italic("Button")); text.Inlines.Add(" to"); text.Inlines.Add(new LineBreak()); text.Inlines.Add("launch the "); text.Inlines.Add(new Bold("Rocket"));
Now that would be code I'd be proud to show in the pages of a book.