Skip to main content

...

....

C# WPF Tutorial - Fun With Markup Extensions [Beginner]


Markup extensions! A very important aspect of XAML - but while you are using them all the time, you probably pay very little attention to the fact that they are markup extensions. Every time you are using those curly braces in XAML, you are dealing with a markup extension. Probably the most common one is StaticResource, but Binding is probably up there too.

But so what? So Microsoft came up with a funny syntax for enabling programmers to set complicated values on XML attributes easily. That by itself isn't particularly interesting. What is interesting, however, is that you can write and use your own markup extensions. In fact, if you didn't know you could do this, you should start getting quite excited - because really this is the best tool at your disposal for extending XAML to meet your own specialized needs.

We are going to make a pretty simple markup extension here today, but it should go a long way to showing you what you can do with them. The purpose of our example extension is to resolve an appropriate image path given a size and an image name:
public class ImgPathExtension : MarkupExtension
{
  public double ImageSize { get; set; }

  public string SubPath { get; set; }

  public ImgPathExtension() { }

  public ImgPathExtension(string path)
  {
    SubPath = path;
    ImageSize = 0;
  }

  public ImgPathExtension(string path, double size)
  {
    SubPath = path;
    ImageSize = size;
  }

  public override object ProvideValue(IServiceProvider serviceProvider)
  {
    var target = serviceProvider.GetService(typeof(IProvideValueTarget))
        as IProvideValueTarget;

    var host = target.TargetObject as FrameworkElement;
    if ((ImageSize <= 0 || double.IsNaN(ImageSize)) && host != null)
    { ImageSize = host.Width; }

    string folder = ImageSize > 32 ? "256x256" : "32x32";

    var source = new BitmapImage(new Uri(
      "pack://application:,,,/MarkupExtensionExample;Component/Images/"
      + folder + "/" + SubPath));

    var prop = target.TargetProperty as DependencyProperty;
    //If we aren't sure they want a ImageSource, lets give a
    //full blown image.
    if (prop == null || prop.PropertyType != typeof(ImageSource))
    {
      var img = new Image();
      img.Stretch = Stretch.None;
      img.Source = source;
      return img;
    }

    return source;
  }
}
 
So the first thing to note is that our markup extension is based on the class MarkupExtension. Doing that is what makes this class a markup extension. There is only one thing that you are required to do when deriving from that class - implement the ProvideValue however you see fit.

The ProvideValue function on a markup extension will be called once per use of the extension at run time (and it will be a different instance of the markup extension every time). So this is very much not like a binding where the value can change over time - a markup extension only sets the value of a property once, when an element is being loaded.

So let's walk through the ProvideValue function of our markup extension above. The first thing we do is use the IServiceProvider passed in to get the IProvideValueTarget instance for this use of the markup extension. This interface allows us to get to all sorts of information about who we are providing the value for - the TargetObject and the TargetProperty 
.

On the very next line of code, we use that TargetObject property - we try and pull out a width. This is because we are trying to be smart, and if no desired image size was passed into the markup extension, we are going to use the size of the element we are providing the value for. Next, we take the image size, and resolve it to one of two available image sizes (behind the example application here, we have two sets of the same images - one set 32x32 pixels in size, and the other 256x256).

We then use that size (now a folder name) and the image name to construct a URI that resolves to the image. Since we are specifying the URI in code (instead of in XAML), we have to specify the entire ugly thing (according to the specs of the Pack URIScheme). The first chuck ("pack://application:,,,/MarkupExtensionExample;Component") just specifies that the resources are located in the DLL MarkupExtensionExample (this application), and the rest of the path is the resource location in that DLL.

OK, now we have a BitmapImage that we can return. But say we wanted to be able to use this extension for more than just the source of an Image? That's where the rest of the code comes in. We poke at the TargetProperty, to try and find out the type it wants. If it does want an image source (say, if it is the Source property of an Image) then all is good and we return the BitmapImage we already made. If not, however, we make an Image, set our BitmapImage as the source, and return that.

So we have gone through the code behind this markup extension - let's take a look at how we can use it:
<Window x:Class="MarkupExtensionExample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:m="clr-namespace:MarkupExtensionExample"
    Title="Window1" Height="300" Width="300">
  <ScrollViewer>
    <WrapPanel>
      <Image Source="{m:ImgPath AutumnLeaves.jpg}" Width="32" />
      <Image Source="{m:ImgPath SubPath=Creek.jpg, ImageSize=256}" 
             Stretch="None" />
      <Image Source="{m:ImgPath Dock.jpg, 256}" 
             Stretch="None" />
      <Image Source="{m:ImgPath DesertLandscape.jpg}" 
             Stretch="None" />
      <Image Source="{m:ImgPath DesertLandscape.jpg, 32}" 
             Width="100" />
      <Image Source="{m:ImgPath Creek.jpg, ImageSize=256}" 
             Stretch="None" />
      <Image Source="{m:ImgPath Dock.jpg, 256}" Stretch="None" />
      <Image Source="{m:ImgPath SubPath=AutumnLeaves.jpg}" 
             Stretch="None" />
      <Image Stretch="None">
        <Image.Source>
          <m:ImgPath SubPath="AutumnLeaves.jpg" />
        </Image.Source>
      </Image>
      <m:ImgPathExtension SubPath="Dock.jpg" />
    </WrapPanel>
  </ScrollViewer>
</Window>
 
All these different image instances cover all the different ways you can declare and use this extension. WPF allows us to leave off the "Extension" part of the class name (but you can still put it there if you want to. After saying the type of markup extension to be used, we get the choice of using the constructors or setting properties explicitly.

As you can see in the C# code above, our markup extension has two constructors - one that takes just the image path, and one that takes an image path and a size. If you are going to use the constructor, you just declare the values in a comma separated list, like so:
Source="{m:ImgPath DesertLandscape.jpg, 32}"
 
WPF chooses the constructor to use based on the number of arguments provided. It does not try and do any type matching or checking - so don't have two constructors for a markup extension that have the same number of arguments. The behavior of such a situation is undefined - you will never know which constructor is going to get called.

If you want to set the properties explicitly, you can use the following syntax (a comma separated list of property/value pairs):
Source="{m:ImgPath SubPath=Creek.jpg, ImageSize=256}"
 
Or, if you want, you can mix and match constructor and property setting, like so:
Source="{m:ImgPath Creek.jpg, ImageSize=256}"
 
In addition to the curly brace syntax, you can declare markup extensions like any other element in xaml:
<Image Stretch="None">
  <Image.Source>
    <m:ImgPath SubPath="AutumnLeaves.jpg" />
  </Image.Source>
</Image>
 
You lose the constructor ability here (you have to set properties, just like every other XAML element), but ProvideValue will get called on this instance just like the curly brace use.

Finally, if you look at the last use of the markup extension in the XAML example, you can see where the special case of returning an full Image out of ProvideValue will get hit. The use of the markup extension there just adds another image to the wrap panel. Pretty cool, eh?

Oh, one quick note - using custom markup extensions can sometimes cause weird issues with the Visual Studio designer. Your code will compile and work just fine, but the designer will throw weird errors like "No constructor for type 'foo' has 2 parameters".

Well, that about wraps it up for this intro to markup extensions. You can grab the studio project with all the code below. 


Source Files:

Comments

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >();

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV