Book Image

Windows Presentation Foundation 4.5 Cookbook

By : Pavel Yosifovich
Book Image

Windows Presentation Foundation 4.5 Cookbook

By: Pavel Yosifovich

Overview of this book

Windows Presentation Foundation (WPF) provides developers with a unified programming model for building rich Windows smart client user experiences that incorporate UI, media, and documents.WPF has become the leading technology for developing rich client applications on the Windows platform, packed with features and capabilities. However, WPF is big; in fact, it's huge, causing a steep learning curve for the beginner and even for those already using some WPF features.Windows Presentation Foundation 4.5 Cookbook provides clear recipes for common WPF tasks. It includes detailed explanations and code examples for customizing and enhancing the basic scenarios, while gaining a deep understanding of WPF mechanics and capabilities.WPF is different and requires a different mind-set and approach. This book provides recipes and insights not only in its design but also its practical implementation details.Starting from the foundations of WPF, such as dependency properties and XAML, the book touches on all major WPF aspects, such as controls and layout, resources, and digs deep into its unprecedented data binding capabilities.The book shows data and control templates in action, which allow full customizations of displayed data and controls in a declarative way. Supported by styles and resources makes data binding all the more powerful. The Model View View-Model pattern is presented as an effective way of maximizing decoupling of components, while providing an elegant way of expanding applications while maintaining a tight grip on complexity.The later parts discuss custom elements and controls ñ the ultimate customization mechanism, and looks at multithreading issues, and how .NET 4.5 task parallelism features can enhance application performance.
Table of Contents (18 chapters)
Windows Presentation Foundation 4.5 Cookbook
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
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.