Friday, February 19, 2016

Some XAML basics

This is a quick overview of some of the fundamental XAML syntax rules for creating objects and setting properties. It's stuff that's so basic, most WPF guides won't even both to go over it. But if you don't understand these things, life as a WPF developer will be difficult for you.

I had to learn all this through trial and error and inductive reasoning; hopefully spelling it out here will save someone some else the blood, sweat, tears involved.

XAML is fundamentally about describing an object tree that you want the program to construct. You can order an object of a particular type to be created by writing an XML element with a name that matches the class name. For example:

<!-- Creates a default-constructed TextBlock -->
<TextBlock />

This commands the parser to create a TextBlock object using the class's default constructor. This syntax is not limited to WPF classes; you can create anything you like this way:

<!-- Creates a default-constructed String -->
<!-- Need to have 'xmlns:system="clr-namespace:System;assembly=mscorlib"' in document header -->
<system:String />

That's great, but default construction will only get you so far. How can we specify property values for the objects we create? The full, verbose way to do this is by using what is called element syntax. This is how you would set the Text property on a TextBlock using element syntax:

<TextBlock>
   <TextBlock.Text>Hello world</TextBlock.Text>
</TextBlock>

A few things to note here:
  1. Properties are initialized using sub-elements of the class element (property elements)
  2. Property element names must be given as ClassName.PropertyName
  3. The content of the property element gives the property value
This is all well and good, but it's also a lot of typing. That's why attribute syntax was invented. Here is an equivalent way to construct this TextBlock:

<TextBlock Text="Hello world" />

Attribute syntax saves a lot of electronic trees, but it only works when the value of the property in question can be parsed from a string. It can't help you in cases like this:

<ListView>
   <ListView.ItemTemplate>
      <DataTemplate />
   </ListView.ItemTemplate>
</ListView>

Here, the type of the ListView.ItemTemplate property is DataTemplate, and there is no way to construct a DataTemplate from a string.

This raises an interesting question: what types of objects can the XAML parser construct from strings? Strings, obviously, are trivially constructable from strings. Primitive numerics like Int32 and Double aren't much harder. But then you encounter something like this:

<TextBlock Margin="2,4,8,2" />

FrameworkElement.Margin is of type Thickness. Thickness is a struct that exposes four public properties: Left, Top, Right, and Bottom. How can the XAML parser construct this type from the string given? Does it have hard-coded knowledge of Thickness? Or maybe there's a rule that you can assign to properties in order using a comma-delimited list? It's actually neither. If you look at the source code for the Thickness class, you'll see this:

[TypeConverter(typeof (ThicknessConverter))]
public struct Thickness : IEquatable<Thickness>

The XAML parser will notice that TypeConverterAttribute, instantiate the type of converter it indicates (ThicknessConverter), and ask it to try to convert the String "2,4,8,2" into a Thickness. And ThicknessConverter knows how to do it. If you want to be able to instantiate your own types from strings in XAML, all you need to do is write an appropriate TypeConverter and attach a TypeConverterAttribute that points to it.

There's yet another technique for building objects from strings: the markup extension. The XAML for a markup extension looks like this:

<TextBlock Text="{StaticResource ApplicationName}" />

The XAML always begins with '{' and ends with '}'. The first word following the '{' is the name of a class that descends from MarkupExtension (if the class name ends with "Extension", as convention dictates, the word "Extension" can be omitted). After the class name is a space followed by a comma-delimited list of constructor arguments (if any). After the constructor arguments are optional property assignments as a comma-delimited list of "Name=Value" pairs.

When the XAML parser encounters a markup extension, it looks up and instantiates the type using any provided constructor arguments, initializes its properties as indicated, and then calls its virtual MarkupExtension.ProvideValue() method. This method returns an object, which is then assigned to the property on the left-hand side of the expression.

Markup extensions can also be nested inside of other markup extensions. For example:

<Grid IsHitTestVisible="{Binding IsOpen,
   RelativeSource={RelativeSource TemplatedParent},
   Converter={StaticResource InverseBoolConverter}}" />

Let's break down what's happening here:
  1. A Binding markup extension is instantiated, passing the string "IsOpen" to its constructor
  2. A RelativeSource markup extension is instantiated, passing the enum value RelativeSourceMode.TemplatedParent to its constructor
  3. RelativeSource.ProvideValue() is called, and the return value is assigned to the Binding.RelativeSource property
  4. A StaticResourceExtension markup extension is instantiated, passing the string "InverseBoolConverter" to its constructor
  5. StaticResourceExtension.ProvideValue() is called, and the return value is assigned to the Binding.Converter property
  6. Binding.ProvideValue() is called, and the return value is assigned to the Grid.IsHitTestVisible property
A lot going on, but it's all pretty standard and comprehensible once you understand what the syntax means.

Now that you know about markup extensions, I can make something I said earlier less untrue (this is a technique called teaching by diminishing deception). Consider this code again:

<ListView>
   <ListView.ItemTemplate>
      <DataTemplate />
   </ListView.ItemTemplate>
</ListView>

I said before that ListView.ItemTemplate needs to be set using element syntax because there's no way to construct a DataTemplate from a string. You now know this isn't trueyou could use a markup extension. An obvious candidate is StaticResourceExtension:

<UserControl>
   <UserControl.Resources>
      <DataTemplate x:Key="MyDataTemplate" />
   </UserControl.Resources>

   <ListView ItemTemplate="{StaticResource MyDataTemplate}" />
</UserControl>

Now you know another rule of XAMLthere are always about a half dozen ways to achieve any particular goal.

Wednesday, January 27, 2016

Copy an SVG image to the clipboard from C#

Despite the title, this is really an article about putting arbitrary binary data onto the clipboard from .net.

Suppose you have the XML text corresponding to an SVG image stored in a string, and you'd like to put in on the clipboard as an image (not as text). The MIME type is "image/svg+xml", so it seems like the solution is straightforward:

string svg = GetSvg();
Clipboard.SetData("image/svg+xml", svg); // won't work

However, if you do this and then inspect the contents of the clipboard, you'll see that .net added a number of bytes in front of your xml text:


That's no good, and it means any SVG editor is going to reject an attempt to paste the image. Maybe we should try straight binary data instead of a string?

string svg = GetSvg();
byte[] bytes = Encoding.UTF8.GetBytes(svg);
Clipboard.SetData("image/svg+xml", bytes); // still won't work

This gives the same result, but with a slightly different set of extra bytes prepended. The fact that the prefix changed slightly when the datatype changed suggests we might be looking at a serialization header. And indeed, if you dig through the MSDN documentation, you might eventually find this note with the Clipboard class (emphasis mine):
An object must be serializable for it to be put on the Clipboard. If you pass a non-serializable object to a Clipboard method, the method will fail without throwing an exception. See System.Runtime.Serialization for more information on serialization. If your target application requires a very specific data format, the headers added to the data in the serialization process may prevent the application from recognizing your data. To preserve your data format, add your data as a Byte array to a MemoryStream and pass the MemoryStream to the SetData method.
Aha! Take 3:

string svg = GetSvg();
byte[] bytes = Encoding.UTF8.GetBytes(svg);
MemorySteam stream = new MemoryStream(bytes);
Clipboard.SetData("image/svg+xml", stream);

This, finally, will give the result you want. The same technique will work with DataObject, in case you want to copy a bitmap version of the image alongside the SVG.

Might have been nice to have that little note with Clipboard.SetData() too, but such is the life of a software developer. :)