WPF: Preserving Whitespace in XAML.

By default an XML parser will eat whitespace, which is very handy when you want to keep a neat indented structure. However there are many instances where whitespace is significant and must be preserved. The xml:space attribute is used to tell the XML parser how to treat white space in an Elements content.

It should be noted that the xml:space attribute will affect not only the element on which it is declared, but also all descendant elements until a new xml:space attribute is encountered. For this reason we try to use this attribute only where it is needed, and as close to the element that requires it.

The XAML parser supports the xml:space attribute and indeed does the right thing in terms of preserving whitespace. However there is a bug in the XAML parser that prevents us from declaring the attribute on the desired XML element in some situations.

This issue was discussed briefly in a thread I started at the MSDN forums and the most elegant solution is discussed here in more detail.

If you are new to XAML terminology or XAML whitespace handling, the following resources will be useful:

A simple example that shows up the problem is trying to set a multi-line string in a TextBox. Since we can not use line breaks in XML attributes, we are forced to use Property Element Syntax, where the property of the object we are creating (Text of the TextBox in this case) is set using a child element rather than a attribute. The result would be something like this:

  1. <TextBox Height="100">
  2. <TextBlock.Text>
  3. Here is my multi-line
  4. text that I want to set in
  5. the TextBox with all
  6. whitespace, including
  7. indentation
  8. to be preserved.
  9. </TextBlock.Text>
  10. </TextBox>

By default the XAML parser will not preserve whitespace, so all the text appears as a single line that wraps in the TextBox. To enable white space preservation we set the attribute xml:space="preserve" as follow:

  1. <TextBox Height="100">
  2. <TextBlock.Text xml:space="preserve">
  3. Here is my multi-line
  4. text that I want to set in
  5. the TextBox with all
  6. whitespace, including
  7. indentation
  8. to be preserved.
  9. </TextBlock.Text>
  10. </TextBox>

This should work. The result, however, is a nasty exception:

    XamlParseException: "Cannot set properties on property elements."

One simple solution is to put the xml:space attribute on the TextBox element, and that works well in this situation:

  1. <TextBox Height="100" xml:space="preserve">
  2. <TextBlock.Text>
  3. Here is my multi-line
  4. text that I want to set in
  5. the TextBox with all
  6. whitespace, including
  7. indentation
  8. to be preserved.
  9. </TextBlock.Text>
  10. </TextBox>

If, however, we were talking about some object other than the TextBox that had child elements or other properties set using Element Property Syntax, the elevated scope of the xml:space attribute may cause problems as its effect is not localised to the element that requires whitespace preservation. It seems that this bug in the XAML parser may make it impossible to localise the effect of the xml:space attribute. Luckily there is a simple trick we can use to workaround this bug!

The bug only affects Property Element Syntax. Object Element Syntax works as expected. So the trick is to set the Text property using Object Element Syntax. To do this we have to set the property using a constructed object rather than the element content - it turns out that this is indeed very simple - we just create a System.String:

  1. <TextBox Height="100" xml:space="preserve">
  2. <TextBlock.Text>
  3. <system:String xml:space="preserve">
  4. Here is my multi-line
  5. text that I want to set in
  6. the TextBox with all
  7. whitespace, including
  8. indentation
  9. to be preserved.
  10. </system:String>
  11. </TextBlock.Text>
  12. </TextBox>

Note that xmlns:system maps to the System clr namespace (ie, put xmlns:system="clr-namespace:System;assembly=mscorlib" somewhere appropriate in your document)

There's just one finishing touch to make life easier; the use of a CDATA section to avoid escaping things that look like XML markup:

  1. <TextBox Height="100" xml:space="preserve">
  2. <TextBlock.Text>
  3. <system:String xml:space="preserve">
  4. <![CDATA[
  5. ... your multi-line text here ...
  6. ]]>
  7. </system:String>
  8. </TextBlock.Text>
  9. </TextBox>

This workaround has made my life using XAML much easier and I hope you find it useful. It has been used heavily in my work to embed Python in XAML (especially loose xaml), and as we all know, Python is very picky about whitespace! I will write about embedding Python in XAML is later posts.

Feel free to contact me regarding this post by email - daniel.paull at thinkbottomup.com.au - or leave comments here after registering with this site.

Comments

That's a very cool post. Thanks for sharing. I've added it to my project's website, and I hope to reorganize the website better in the future. Thanks for contacting me about your post.

Actually, hmm, your last point about IronPython intrigues me... what are you using it for?

Hello John,

Thanks for your comments - here is a link back to John's site, "Cowbell". A great resource for people thinking outside of the WPF box:

http://code.google.com/p/cowbell/

Lately I've been working on a 3D visualisation system for a client, visualising various Exploration and Mining datasets. The aim of the project is to build a set of reusable, flexible components that can be wired up in various ways to build point solutions and proof of concept applications. The use of loose XAML and embedded DLR scripts (I choose to use Python as the DLR language) allows us to quickly wire things up at run-time. It also gives us flexible deployment options as XAML files can be retrived via any URI, allowing for serving them up, say, from a web server.

From a developers point of view, the use of Python means that I don't need to write a class simply to write a simple Value Converter for a Binding.

Cheers,

Dan

I don't think my site is that great, but a couple of people have told me they enjoy it. Positive feedback makes it worthwhile, even though it is a stream-of-conciousness.

Consider modifying the metaphor of "thinking outside the WPF box". Instead, I prefer the saying, "Find the box". WPF is large, and as a result developers I talk to have a difficult time understanding just how big the box is. Other than the fact I enjoy Christopher Walken's comedy, that is truly the idea behind Cowbell: "Explore the space!" That is the only way to find the box.

I thought about using DLR scripts at one point, but instead preferred to constrain myself with transactional data binding and the state machine metaphor. The issue with using DLR scripts is they are hard to statically analyze and verify. By placing constraints on my architecture, they force me to reconsider problems in new ways. Having too many degrees of freedom, is like reading too many metaphors in a John Updike nvoel: eventually your eyes glaze over from staring at the visual complexity.

As a result, I try to avoid Turing complete transformational ability and think of my program as self-describing UI data structures.

Most developers probably do not have the creative freedom to do this, though. It took me four months to come up with the right set of constraints for my architecture.

Send me an email some time about what you are doing with data viz.