WPF. Simplify your life with Linq extension methods, Canvas and Visual Tree helpers.


This article dwells on .net 3.5 and Visual C# 2008 Express Edition.

Up to current moment I’ve been using the Canvas element very much with the diagramming stuff that I described earlier. As you implement something similar you definitely come across inconvenience using the standard Children collection property. There’s very little changes that you will be using only UIElement objects. For example you have some shapes exposing different interfaces, something like ISelectable, IDraggable or anything else. Additionally you have a variety of visual connections and drawings inheriting Paths, Geometry or Figures.ย  So each time you come across type casting and code longer that it could be ๐Ÿ˜‰

ย 

1. Extending base classes

When you need three or more core functions for manipulating canvas children and don’t have the possibility of inheriting/implementing additional stuff for your own classes there’s a good opportunity of extending the Canvas class itself using a couple of Linq extension methods for that.

using System.Windows;
using System.Windows.Controls;

namespace UsefulExtensions.Extensions
{
  public static class CanvasExtension
  {
    public static void AddChild<T>(this Canvas canvas, T element)
    {
      UIElement uiElement = element as UIElement;
      if (uiElement != null && !canvas.Children.Contains(uiElement))
        canvas.Children.Add(uiElement);
    }

    public static void RemoveChild<T>(this Canvas canvas, T element)
    {
      UIElement uiElement = element as UIElement;
      if (uiElement != null && canvas.Children.Contains(uiElement))
        canvas.Children.Remove(uiElement);
    }

    public static void InsertChild<T>(this Canvas canvas, int index, T element)
    {
      UIElement uiElement = element as UIElement;
      if (uiElement != null && !canvas.Children.Contains(uiElement))
        canvas.Children.Insert(index, uiElement);
    }
  }
}

Of course you should carefully read the documentation towards the extension methods at least at your local VS MSDN library. There’s a good explanation how it works and where and when to use these features.

Similar to "System.Linq" whenever you declare the namespace "UserfulExtensions.Extensions" (or your own namespace name) you get the additional methods for all the Canvas based classes.

image

ย 

The best thing I like using generics is the transparency of T provided. That’s a really great feature having the possibility to omit the T declaration within the generic method. Of course sometimes it may bring to small problems but in most cases helps you a lot.

Having shapes exposing for example IDraggable and ISelectable interfaces you don’t need to explicitly define the type as T when calling generic methods.

The method call will look like following:

myCanvas.AddChild(myShape1);

myCanvas.AddChild(myShape2)

The T will be automatically resolved by the type/interface of the parameter provided. You can actually pass anything you to the method but that’s not the fact the element will be added to the Canvas. As you have seen earlier the extension methods require the element be at least of UIElement type to perform an action, so the next thing will also compile but won’t be executed at runtime.

myCanvas.AddChild(0)

myCanvas.AddChild(new Point(10,10))

As I’ve already said I like the generics feature of resolving the <T> and I think it’s really useful.

ย 

2. Implementing helper classes

2.1. Canvas helper

Helper classes is the thing each developer implement very often. When you use the canvas child maintenance using the methods above you come to conclusion that the standard "Canvas.GetLeft" or "Canvas.GetTop" are not enough flexible for you as they also deal with UIElement objects while you require some generics approach for that. It’s very easy to implement a static helper class to correct that.

Here’s a quick snippet I use often

CanvasHelper.cs

using System;
using System.Windows;
using System.Windows.Controls;

namespace UsefulExtensions.Helpers
{
  public static class CanvasHelper
  {
    public static double GetLeft<T>(T element)
    {
      UIElement uiElement = element as UIElement;
      if (uiElement == null)
        throw new ArgumentNullException("element");
      return (double)uiElement.GetValue(Canvas.LeftProperty);
    }

    public static double GetTop<T>(T element)
    {
      UIElement uiElement = element as UIElement;
      if (uiElement == null)
        throw new ArgumentNullException("element");
      return (double)uiElement.GetValue(Canvas.TopProperty);
    }

    public static Point GetPosition<T>(T element)
    {
      UIElement uiElement = element as UIElement;
      if (uiElement == null)
        throw new ArgumentNullException("element");

      return new Point(
        (double)uiElement.GetValue(Canvas.LeftProperty),
        (double)uiElement.GetValue(Canvas.TopProperty));
    }

    public static void SetLeft<T>(T element, double length)
    {
      UIElement uiElement = element as UIElement;
      if (uiElement == null)
        throw new ArgumentNullException("element");
      uiElement.SetValue(Canvas.LeftProperty, length);
    }

    public static void SetTop<T>(T element, double length)
    {
      UIElement uiElement = element as UIElement;
      if (uiElement == null)
        throw new ArgumentNullException("element");
      uiElement.SetValue(Canvas.TopProperty, length);
    }

    public static void SetPosition<T>(T element, Point value)
    {
      UIElement uiElement = element as UIElement;
      if (uiElement == null)
        throw new ArgumentNullException("element");
      uiElement.SetValue(Canvas.LeftProperty, value.X);
      uiElement.SetValue(Canvas.TopProperty, value.Y);
    }
  }
}

I didn’t provide any supplementary information for the sources above because I hope it’s quite self-describing. As static Canvas members "GetLeft" and "GetTop" deal with the attached dependency properties of the UIElement provided there’s quite easy to extend the functionality a bit for supporting the generics approach.

image

Of course this can also be placed as extension to the Canvas class but my intention was to expose the helper classes that can be implemented at any stage of the project life cycle. Also I think these methods deal with attached properties of UIElement the extension method for Canvas won’t be the right place of storing our generics stuff.

ย 

2.2. Visual Tree Helper

Standard VisualTreeHelper class contains a lot of important and useful stuff helping developers a lot. Usually I use if for hit testing and it helped me much with rubberband selection tool implementation.

The only thing it lacks very much is the more complicated Parent processing functionality. According to the MSDN forums a lot of people came across one problem related to the control templates and templating stuff.

When dealing with hit testing it’s rather difficult from the first steps provide hit testing features for the elements totally based on Control Templates because hit testing will work for each element within the template separately. Assuming that you might have some sort of shape containing a text box, an image element and something like text block I clearly understand the troubles you might experience when trying to hit-test it or detect clicks.

The only good solution can be found across forums is navigating with the visual tree to get the parent of the Control Template and so find the element required instead of controls within the template. Simple recursive function is quite enough but as I prefer working with generics I’ve implemented another look of the function.

VisualTreeHelperEx.cs

using System.Windows;
using System.Windows.Media;

namespace UsefulExtensions.Helpers
{
  /// <summary>
  /// Provides additional functionality alongside standard VisualTreeHelper.
  /// </summary>
  public static class VisualTreeHelperEx
  {
    /// <summary>
    /// Returns current object or ony parent object that expose the type provided.
    /// Very useful when accessing the main container for the ControlTemplate or for any elements within the ControlTemplate.
    /// </summary>
    /// <typeparam name="T">The type of the object to be searched.</typeparam>
    /// <param name="obj">Starting object to search for parents or itself.</param>
    /// <returns>Returns current object if it exposes the required type or any parent from the upper levels or null.</returns>
    public static DependencyObject GetParent<T>(DependencyObject obj)
    {
      if (obj is T) return obj;

      DependencyObject parent = VisualTreeHelper.GetParent(obj);
      if (parent == null)
        return null;
      else if (!(parent is T))
        return GetParent<T>(parent);

      return parent;
    }
  }
}

As forย  hit testing itself you will find a lot of information across the MSDN library so I won’t dwell on it. This method helps getting exactly the element you are looking for when performing the test.

Assume you are implementing the rubberband/multi-selection tool some sort of diagramming application and you definitely know that each of your shape expose something like ISelectable interface. Of course you need to hit-test all the ISelectable based elements on the Canvas. But each you shape also contains a very rich content template making each shape look very fancy. In this case you will simply move to your helper methods defining the shapes regardless their content templates:

ย 

private List<ISelectable> hits = new List<ISelectable>();
private List<ISelectable> GetSelectablesHit(Geometry region)
{
  hits.Clear();
  GeometryHitTestParameters parameters = new GeometryHitTestParameters(region);
  HitTestResultCallback callback = new HitTestResultCallback(HitTestCallback);
  VisualTreeHelper.HitTest((Visual)DrawingSurface, null, callback, parameters);
  return hits;
}

private HitTestResultBehavior HitTestCallback(HitTestResult result)
{
  GeometryHitTestResult geometryResult = (GeometryHitTestResult)result;

  // Get the object regardless ControlTemplate content
  ISelectable visual = VisualTreeHelperEx.GetParent<ISelectable>(result.VisualHit) as ISelectable;

  if (visual != null &&
    !hits.Contains(visual) &&
    geometryResult.IntersectionDetail == IntersectionDetail.FullyInside)
    hits.Add(visual);
  return HitTestResultBehavior.Continue;
}

You influence the performance a lot because the function doesn’t navigate the element tree each time.

if (obj is T) return obj;

The first line of the function ensures the actual element is what you are querying and stops the recursion. In the rest cases it will go upper level until it founds the required <T> or return null if nothing was found.

ย 

ย 

3. Extending interfaces.

As I’ve been using IServiceProvider interface for organizing the basic service bus for my diagramming sample application I again got tired of type casting stuff when querying some service host for the services by their interfaces. So I’ve decided to extend it without any additional inheritance and changes to interface. Extension methods features helped me very much with it. Here’s a quick sample:

IServiceProviderExtension.cs

using System;

namespace UsefulExtensions.Extensions
{
  public static class IServiceProviderExtension
  {
    public static T GetService<T>(this IServiceProvider provider)
    {
      return (T)provider.GetService(typeof(T));
    }

    public static bool Contains<T>(this IServiceProvider provider)
    {
      return (provider.GetService(typeof(T)) != null);
    }
  }
}

What does it give you? In all classes exposing IServiceProvider or interfaces inheriting it you get additional methods GetSetvice and Contains

image

This will help you get rid of common type casting and long strings like

provider.GetService(typeof(ISelectionManager)) as ISelectionManager

by simple

provider.GetService<ISelectionManager>()

Additionally it provides an extension helping you determine whether the service exists within the provider collection. Of course the "Contains" method is just a sample of implementing the extension method.

The best thing as I’ve mentioned above is that you get this extensions to each inheritance case

image

ย 

The only thing you should remember is adding the namespace of your extension classes to the source files where you deal with IServiceProvider or interfaces exposing it.

Hope this will help anyone to increase the coding performance.

Source code for the article

Advertisements

WPF Diagramming. Saving you canvas to image, XPS document or raw Xaml.


Guess saving visuals is one of the most spoken topics today. Main formats I wanted to implement within my application were png, xps and pure xaml so this is what I’ll dwell on below.

Exporting canvas to PNG image

public void ExportToPng(Uri path, Canvas surface)
{
  if (path == null) return;

  // Save current canvas transform
  Transform transform = surface.LayoutTransform;
  // reset current transform (in case it is scaled or rotated)
  surface.LayoutTransform = null;

  // Get the size of canvas
  Size size = new Size(surface.Width, surface.Height);
  // Measure and arrange the surface
  // VERY IMPORTANT
  surface.Measure(size);
  surface.Arrange(new Rect(size));

  // Create a render bitmap and push the surface to it
  RenderTargetBitmap renderBitmap = 
    new RenderTargetBitmap(
      (int)size.Width, 
      (int)size.Height, 
      96d, 
      96d, 
      PixelFormats.Pbgra32);
  renderBitmap.Render(surface);

  // Create a file stream for saving image
  using (FileStream outStream = new FileStream(path.LocalPath, FileMode.Create))
  {
    // Use png encoder for our data
    PngBitmapEncoder encoder = new PngBitmapEncoder();
    // push the rendered bitmap to it
    encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
    // save the data to the stream
    encoder.Save(outStream);
  }

  // Restore previously saved layout
  surface.LayoutTransform = transform;
}

I’ve again tried to make the code self-explaining. You can try to use different encoders for saving the data so it’s up to you to look through what ones are available. The main trouble many guys come across is blank images as the canvas output or saving canvas programmatically without visualizing it. The key is measuring and arranging the surface before pushing it to render bitmap. As render bitmap does nothing with measuring and arranging elements in this case, this should be regarded mandatory for you. And of course you should remember the zooming/scaling and rotation matrices ๐Ÿ˜‰ the layout transformations will also be saved to the image so you should take care of that manually. In my case I cache the LatoutTransform and reset the original value right before measuring canvas.

Paying more attention you can see that bitmap encoder accepts a Visual so canvas is not the only element that can be passed ๐Ÿ˜‰ Going down the inheritance tree it is convenient to use the FrameworkElement or pure Visual. FrameworkElement contains Width and Height properties used by canvas.

Default set references provided by WPF application template is quite enough to implement this method. Of course you’ll have to resolve "using" section ๐Ÿ˜‰

Exporting canvas to XPS document

Actually this was the first I’ve started implementing for my application. I like XPS format very much because it is easy to maintain and integrate. Here’s the quick snippet of exporting your canvas to XPS

public void Export(Uri path, Canvas surface)
{
  if (path == null) return;

  // Save current canvas transorm
  Transform transform = surface.LayoutTransform;
  // Temporarily reset the layout transform before saving
  surface.LayoutTransform = null;

  // Get the size of the canvas
  Size size = new Size(surface.Width, surface.Height);
  // Measure and arrange elements
  surface.Measure(size);
  surface.Arrange(new Rect(size));

  // Open new package
  Package package = Package.Open(path.LocalPath, FileMode.Create);
  // Create new xps document based on the package opened
  XpsDocument doc = new XpsDocument(package);
  // Create an instance of XpsDocumentWriter for the document
  XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(doc);
  // Write the canvas (as Visual) to the document
  writer.Write(surface);
  // Close document
  doc.Close();
  // Close package
  package.Close();

  // Restore previously saved layout
  surface.LayoutTransform = transform;
}

You will need two additional assemblies to be referenced from the GAC: "ReachFramework.dll" and "System.Printing.dll". Again I’d advice you to backup the original layout transformation regardless it’s state before processing canvas. You should definitely read more details towards System.IO.Packaging and XPS documents. For those who doesn’t know yet the XPS document is presented by zip container for some xml, fonts and binary data like images etc. You can freely rename XPS to ZIP, unpack it and look inside for better understanding when you’ll be looking though MSDN articles towards it.

I was really pleased to find out that 10 or 20 of my duplicated elements having the same image with content template loaded from the separate assembly wasn’t cloned. I had only one image in the XPS for each distinct type of element so the archive size was extremely small for my diagram.

Exporting canvas to the XAML

I’ll give you the most stupid sample that can be found everywhere

public void Export(Uri path, Canvas surface)
    {
      if (path == null) return;
      if (surface == null) return;

      string xaml = XamlWriter.Save(surface);
      File.WriteAllText(path.LocalPath, xaml);
    }

You get the xaml presentation of your canvas that can be later loaded with XamlReader. Be prepared to cuss MS guys a lot because it is rather difficult to adopt any application load anything except simple xamls into your canvas ๐Ÿ™‚ The worst thing is that it doesn’t support bindings/multibindings so you’ll have to resolve them somehow on document loading. The same is for events and there’s a list of things not supported that can be easily found at MSDN shipped with Visual Studio. I’ve looked through the Internet to find any good solution but there’s nothing I liked. Guess if it becomes a hot topic (and I’m sure it will become soon) a lot of good overrides will appear. I’m doing my own implementation but it’s in the early stage and cannot be brought to public.

Anyway there’s a lot of cases when the pure xaml will be quite enough for you.

Have a nice coding and investigations.