Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Windows Presentation Foundation 4.5 Cookbook

You're reading from   Windows Presentation Foundation 4.5 Cookbook For C# developers, this book offers a fast route to getting more closely acquainted with the ins and outs of Windows Presentation Foundation. The recipe approach smoothes out the complexities and enhances learning.

Arrow left icon
Product type Paperback
Published in Sep 2012
Publisher Packt
ISBN-13 9781849686228
Length 464 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Pavel Yosifovich Pavel Yosifovich
Author Profile Icon Pavel Yosifovich
Pavel Yosifovich
Arrow right icon
View More author details
Toc

Table of Contents (18) Chapters Close

Windows Presentation Foundation 4.5 Cookbook
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
1. Foundations 2. Resources FREE CHAPTER 3. Layout and Panels 4. Using Standard Controls 5. Application and Windows 6. Data Binding 7. Commands and MVVM 8. Styles, Triggers, and Control Templates 9. Graphics and Animation 10. Custom Elements 11. Threading Index

Creating a custom markup extension


Markup extensions are used to extend the capabilities of XAML, by providing declarative operations that need more than just setting some properties. These can be used to do pretty much anything, so caution is advised – these extensions must preserve the declarative nature of XAML, so that non-declarative operations are avoided; these should be handled by normal C# code.

Getting ready

Make sure Visual Studio is up and running.

How to do it...

We'll create a new markup extension that would provide random numbers and use it within a simple application:

  1. First, we'll create a class library with the markup extension implementation and then test it in a normal WPF application. Create a new Class Library project named CH01.CustomMarkupExtension. Make sure the checkbox Create directory for solution is checked, and click on OK:

  2. The base MarkupExtension class resides in the System.Xaml assembly. Add a reference to that assembly by right-clicking the References node in the Solution Explorer, and selecting Add Reference…. Scroll down to System.Xaml and select it.

  3. Delete the file Class1.cs that was created by the wizard.

  4. Right-click the project node, and select Add Class…. Name the class RandomExtension and click on Add. This markup extension will generate a random number in a given range.

  5. Mark the class as public and inherit from MarkupExtension.

  6. Add a using statement to System.Windows.Markup or place the caret somewhere over MarkupExtension, click on the smart tag (or press Ctrl + . (dot), and allow the smart tag to add the using statement for you. This is how the class should look right now:

       public class RandomExtension : MarkupExtension {}
  7. We need to implement the ProvideValue method. The easiest way to get the basic prototype is to place the caret over MarkupExtension and use the smart tag again, this time selecting Implement abstract class. This is the result:

    public class RandomExtension : MarkupExtension {
       public override object ProvideValue(IServiceProvider sp) {
          throw new NotImplementedException();
       }
    }
  8. Before we create the actual implementation, let's add some fields and constructors:

    readonly int _from, _to; 
    public RandomExtension(int from, int to) {
       _from = from; _to = to;
    } 
    public RandomExtension(int to)
       : this(0, to) {
    }
  9. Now we must implement ProvideValue. This should be the return value of the markup extension – a random number in the range provided by the constructors. Let's create a simple implementation:

    static readonly Random _rnd = new Random();
    public override object ProvideValue(IServiceProvider sp) {
       return (double)_rnd.Next(_from, _to);
    }
  10. Let's test this. Right-click on the solution node in Solution Explorer and select Add and then New Project….

  11. Create a WPF Application project named CH01.TestRandom.

  12. Add a reference to the class library just created.

  13. Open MainWindow.xaml. We need to map an XML namespace to the namespace and assembly our RandomExtension resides in:

    xmlns:mext="clr-namespace:CH01.CustomMarkupExtension;
    assembly=CH01.CustomMarkupExtension"
  14. Replace the Grid with a StackPanel and a couple of TextBlocks as follows:

    <StackPanel>
       <TextBlock FontSize="{mext:Random 10, 100}" Text="Hello"
                  x:Name="text1"/>
       <TextBlock Text="{Binding FontSize, ElementName=text1}" />
    </StackPanel>
  15. he result is a TextBlock that uses a random font size between 10 and 100. The second TextBlock shows the generated random value.

How it works...

A markup extension is a class inheriting from MarkupExtension, providing some service that cannot be done with a simple property setter. Such a class needs to implement one method: ProvideValue. Whatever is returned provides the value for the property. ProvideValue accepts an IServiceProvider interface that allows getting some "context" around the markup extension execution. In our simple example, it wasn't used.

Any required arguments are passed via constructor(s). Any optional arguments can be passed by using public properties (as the next section demonstrates).

Let's try using our markup extension on a different property:

 <TextBlock Text="{mext:Random 1000}" />

We hit an exception. The reason is that our ProvideValue returns a double, but the Text property expects a string. We need to make it a bit more flexible. We can query for the expected type and act accordingly. This is one such service provided through IServiceProvider:

public override object ProvideValue(IServiceProvider sp) {
   int value = _rnd.Next(_from, _to);
   Type targetType = null;
   if(sp != null) {
      var target = sp.GetService(typeof(IProvideValueTarget)) 
         as IProvideValueTarget;
      if(target != null) {
         var clrProp = target.TargetProperty as PropertyInfo;
         if(clrProp != null)
            targetType = clrProp.PropertyType;
         if(targetType == null) {
            var dp = target.TargetProperty 
               as DependencyProperty;
            if(dp != null)
               targetType = dp.PropertyType;
         }
      }
   }
   return targetType != null ?
      Convert.ChangeType(value, targetType) :
      value.ToString();
}

You'll need to add a reference for the WindowsBase assembly (where DependencyProperty is defined). IServiceProvider is a standard .NET interface that is a kind of "gateway" to other interfaces. Here we're using IProvideValueTarget, which enables discovering what property type is expected, with the TargetProperty property. This is either a PropertyInfo (for a regular CLR property) or a DependencyProperty, so appropriate checks must be made before the final target type is ascertained. Once we know the type, we'll try to convert to it automatically using the Convert class, or return it as a string if that's not possible.

For more information on other interfaces that can be obtained from this IServiceProvider, check this page on the MSDN documentation: http://msdn.microsoft.com/en-us/library/B4DAD00F-03DA-4579-A4E9-D8D72D2CCBCE(v=vs.100,d=loband).aspx.

There's more...

Constructors are one way to get parameters for a markup extension. Properties are another, allowing optional values to be used if necessary. For example, let's extend our random extension, so that it is able to provide fractional values and not just integral ones. This option would be set using a simple public property:

   public bool UseFractions { get; set; }

The implementation of ProvideValue should change slightly; specifically, calculation of the value variable:

   double value = UseFractions ? 
      _rnd.NextDouble() * (_to - _from) + _from : 
      (double)_rnd.Next(_from, _to);

To use it, we set the property after the mandatory arguments to the constructor:

 <TextBlock Text="{mext:Random 1000, UseFractions=true}" />

Don't go overboard

Markup extensions are powerful. They allow arbitrary code to run in the midst of XAML processing. We just need to remember that XAML is, and should remain, declarative. It's pretty easy to go overboard, crossing that fine line. Here's an example: let's extend our RandomExtension to allow modifying the property value at a regular interval. First, a property to expose the capability:

   public TimeSpan UpdateInterval { get; set; }

Now, some modifications to the ProvideValue implementation:

if(UpdateInterval != TimeSpan.Zero) {
   // setup timer...
   var timer = new DispatcherTimer();
   timer.Interval = UpdateInterval;
   timer.Tick += (sender, e) => {
      value = UseFractions ?
         _rnd.NextDouble() * (_to - _from) + _from :
         (double)_rnd.Next(_from, _to);
      finalValue = targetType != null ?
         Convert.ChangeType(value, targetType) :
         value.ToString();
      if(dp != null)
         ((DependencyObject)targetObject).SetValue(
            dp, finalValue);
      else if(pi != null)
         pi.SetValue(targetObject, value, null);
   };
   timer.Start();
}

targetObject is obtained by calling IProvideValueTarget.TargetObject. This is the actual object on which the property is to be set.

And the markup:

<TextBlock Text="This is funny" 
FontSize="{mext:Random 10, 50, UpdateInterval=0:0:1}" />

This is certainly possible (and maybe fun), but it's probably crossing the line.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime