Archive

Archive for the ‘MVVM’ Category

Ultimate Coder – The pursuit of Bacon

March 19, 2013 1 comment

This is a copy of the post I made on the Intel site here. For the duration of the contest, I am posting a weekly blog digest of my progress with using the Perceptual Computing items. This weeks post shows how Huda has evolved from the application that was created at the end of the fourth week. My fellow competitors are planning to build some amazing applications, so I’d suggest that you head on over to the main Ultimate Coder site and follow along with their progress as well. I’d like to take this opportunity to wish them good luck.

Bring me the Bacon.

Rather than doing a day by day update this week, I’ve waited until near the end of the week to cover the progress I’ve been making this week. It couldn’t be more timely, given Steve’s update this week as I’d reached a lot of the conclusions he did regarding gesture performance and also layout of the interface (a point he raised elsewhere). Hopefully, having a bit of video footage should showcase where we are with Huda right now.

The first part of this week revolved around finalising the core saving and loading of photographs. As I revealed last week, the filters aren’t applied directly to the image; rather, they are associated with the image in a separate file. The code I showed last week demonstrated that each filter would be responsible for serialising itself, so the actual serialisation graph code should be relatively trivial.

using AForge.Imaging.Filters;
using GalaSoft.MvvmLight.Messaging;
using Huda.Messages;
using Huda.Transformations;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
namespace Huda.Model
{
public sealed class PhotoManager
{
private Dictionary<string, List<IImageTransform>> savedImageList = new Dictionary<string, List<IImageTransform>>();
private readonly object SyncLock = new object();
private string savedirectory;
private string path;
public PhotoManager()
{
}
public List<IImageTransform> GetFilters(string originalImage)
{
if (!savedImageList.ContainsKey(originalImage))
{
lock (SyncLock)
{
if (!savedImageList.ContainsKey(originalImage))
{
savedImageList.Add(originalImage, new List<IImageTransform>());
Save();
}
}
}
return savedImageList[originalImage];
}
private void AddFilter(string key, IImageTransform filter)
{
var filters = GetFilters(key);
lock (SyncLock)
{
filters.Add(filter);
Save();
}
}
private void Save()
{
if (savedImageList == null || savedImageList.Count == 0) return;
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fs, savedImageList);
}
}
public void Load()
{
savedirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"Huda");
if (!Directory.Exists(savedirectory))
{
Directory.CreateDirectory(savedirectory);
}
path = Path.Combine(savedirectory, @"Huda.hda");
Messenger.Default.Register<FilterUpdatedMessage>(this, FilterUpdated);
if (!File.Exists(path)) return;
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
BinaryFormatter formatter = new BinaryFormatter();
savedImageList = (Dictionary<string, List<IImageTransform>>)formatter.Deserialize(fs);
}
}
private void FilterUpdated(FilterUpdatedMessage filter)
{
if (filter == null) throw new ArgumentNullException("filter");
AddFilter(filter.Key, filter.Filter);
}
}
}

While I could have created a custom format to wrap the Dictionary there, it would have been overkill for what I was trying to achieve. The reason for me using a Dictionary in this class is that it’s a great way to see if an item is already present – helping to satisfy our requirement to load the filters that we previously applied to the photo.

This class subscribes to all the messages that Huda will raise with relation to filters, which means that we can easily save the state of the filters as and when they are updated. A key tenet of MVVM is that we shouldn’t be introducing knowledge of one ViewModel in another ViewModel, so we use messaging as an abstraction to pass information around. The big advantage of this is that we are able to hook into the same messages here in the model, so we don’t have to worry about which ViewModel is going to update the model. In MVVM, it’s okay to have the link between the ViewModel and the Model, but as we already have this messaging mechanism in place, it’s straightforward for us to just use it here.

Saving and loading the actual photo and filter information is accomplished using a simple BinaryFormatter. As the filters are explicitly serializing themselves, the performance should be acceptable as the formatter doesn’t have to rely on reflection to work out what it needs to save. That’s Pete’s big serializer hint for you this week – it may be easy to let the formatter work out what to serialize, but when your file gets bigger, the load takes longer.

You may wonder why I’m only guarding the Save routine through a lock (note, the lock is actually in the AddFilter method). The Load routine is only called once – at application startup, and it has to finish before the application can continue, so we have this in a thread in the Startup routine – and the splash screen doesn’t disappear until all the information is loaded.

Okay, that’s the core of the photo management in place. Now it’s just a matter of maintaining the filter lists, and that’s accomplished through applying the filters.

You may recall from last weeks code that some of the filters had to convert from BitmapSource to Bitmap (needed for AForge support), but what wasn’t apparent was the fact that we need to convert to a standard image format. Knowing this, I decided to simplify my life by introducing the following helper code:

public static Bitmap To24BppBitmap(this BitmapSource source)
{
return ToBitmap(source, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
}
public static Bitmap ToBitmap(BitmapSource source, System.Drawing.Imaging.PixelFormat format)
{
using (Bitmap bmp = ToBitmap(source))
{
return AForge.Imaging.Image.Clone(bmp, format);
}
}

This enabled me to simplify my filter calls quite considerably. So, the Sepia transformation code, for instance, became this:

public System.Windows.Media.Imaging.BitmapSource Transform(System.Windows.Media.Imaging.BitmapSource source)
{
using (Bitmap bitmap = source.To24BppBitmap())
{
using (Bitmap bmp = new Sepia().Apply(bitmap))
{
return ImageSourceHelpers.FromBitmap(bmp);
}
}
}
[/csharp]

Another thing that should be apparent in that code is that I'm wrapping Bitmap allocations inside using statements. This is because Huda was leaking memory all over the place. Basically, Bitmap objects were being created and not released - this needed taking care of. Fortunately, that's a very simple fix - all it requires was for me to wrap the Bitmap allocations in using statements.

I realised after I posted last weeks article that you might actually like to see the code I'm using to get the gesture to work with the tree. It's incredibly simple and very similar to the POC sample I showed last week. So, if you want to hook the gesture code in, all you need to do is:

1
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Input;
using System.Windows.Controls;
using System.Diagnostics;
using System.Timers;
using System.Windows.Media;
using System.Windows.Threading;
using Goldlight.Perceptual.Sdk;
namespace Huda.Behaviors
{
public class TreeviewGestureItemBehavior : Behavior<TreeView>
{
private bool inBounds;
private Timer selectedTimer = new Timer();
private TreeViewItem selectedTreeview;
private TreeViewItem previousTreeView;
protected override void OnAttached()
{
PipelineManager.Instance.Gesture.HandMoved += Gesture_HandMoved;
selectedTimer.Interval = 2000;
selectedTimer.AutoReset = true;
selectedTimer.Elapsed += selectedTimer_Elapsed;
base.OnAttached();
}
protected override void OnDetaching()
{
PipelineManager.Instance.Gesture.HandMoved -= Gesture_HandMoved;
selectedTimer.Stop();
base.OnDetaching();
}
void selectedTimer_Elapsed(object sender, ElapsedEventArgs e)
{
selectedTimer.Stop();
Dispatcher.Invoke(DispatcherPriority.Normal,
(Action)delegate()
{
if (previousTreeView != null)
{
previousTreeView.IsSelected = false;
}
previousTreeView = selectedTreeview;
selectedTreeview.IsSelected = true;
});
}
void Gesture_HandMoved(object sender, Goldlight.Perceptual.Sdk.Events.GesturePositionEventArgs e)
{
Dispatcher.Invoke((Action)delegate()
{
Point pt = new Point(e.X - ((MainWindow)App.Current.MainWindow).GetWidth() / 2, e.Y - ((MainWindow)App.Current.MainWindow).GetHeight() / 2);
Rect rect = new Rect();
rect = AssociatedObject.RenderTransform.TransformBounds(
new Rect(0, 0, AssociatedObject.ActualWidth, AssociatedObject.ActualHeight));
if (rect.Contains(pt))
{
if (!inBounds)
{
Debug.WriteLine("Here I am");
inBounds = true;
}
// Now, let's test to see if we are interested in this coordinate.
if (selectedRectangle == null || !selectedRectangle.Contains(pt))
{
GetElement(pt);
}
}
else
{
if (inBounds)
{
selectedTimer.Stop();
inBounds = false;
}
}
}, DispatcherPriority.Normal);
}
private Rect selectedRectangle;
private void GetElement(Point pt)
{
IInputElement element = AssociatedObject.InputHitTest(pt);
if (element == null) return;
TreeViewItem t = FindUpVisualTree<TreeViewItem>((DependencyObject)element);
if (t != null)
{
// Get the bounds of t.
if (selectedTreeview != t)
{
selectedTimer.Stop();
// This is a different item.
selectedTreeview = t;
selectedRectangle = selectedTreeview.RenderTransform.TransformBounds(
new Rect(0, 0, t.ActualWidth, t.ActualHeight));
selectedTimer.Start();
}
}
else
{
// Stop processing and drop this out.
selectedTimer.Stop();
selectedTreeview = null;
}
}
private T FindUpVisualTree<T>(DependencyObject initial) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null && current.GetType() != typeof(T))
{
current = VisualTreeHelper.GetParent(current);
}
return current as T;
}
}
}

Of course, this is only useful if you have the Gesture handling code that I use as well. Being a nice guy, here it is:

using Goldlight.Perceptual.Sdk.Events;
using Goldlight.Windows8.Mvvm;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Goldlight.Perceptual.Sdk
{
public class GesturePipeline : AsyncPipelineBase
{
private WeakEvent<GesturePositionEventArgs> gestureRaised = new WeakEvent<GesturePositionEventArgs>();
private WeakEvent<MultipleGestureEventArgs> multipleGesturesRaised = new WeakEvent<MultipleGestureEventArgs>();
private WeakEvent<GestureRecognizedEventArgs> gestureRecognised = new WeakEvent<GestureRecognizedEventArgs>();
public event EventHandler<GesturePositionEventArgs> HandMoved
{
add { gestureRaised.Add(value); }
remove { gestureRaised.Remove(value); }
}
public event EventHandler<MultipleGestureEventArgs> FingersMoved
{
add { multipleGesturesRaised.Add(value); }
remove { multipleGesturesRaised.Remove(value); }
}
public event EventHandler<GestureRecognizedEventArgs> GestureRecognized
{
add { gestureRecognised.Add(value); }
remove { gestureRecognised.Remove(value); }
}
public GesturePipeline()
{
EnableGesture();
searchPattern = GetSearchPattern();
}
private int ScreenWidth = 1024;
private int ScreenHeight = 960;
public void SetBounds(int width, int height)
{
this.ScreenWidth = width;
this.ScreenHeight = height;
}
public override void OnGestureSetup(ref PXCMGesture.ProfileInfo pinfo)
{
// Limit how close we have to get.
pinfo.activationDistance = 70;
base.OnGestureSetup(ref pinfo);
}
public override void OnGesture(ref PXCMGesture.Gesture gesture)
{
var handler = gestureRecognised;
if (handler != null)
{
handler.Invoke(new GestureRecognizedEventArgs(gesture.label.ToString()));
}
base.OnGesture(ref gesture);
}
public override bool OnNewFrame()
{
// We only query the gesture if we are connected. If not, we shouldn't
// attempt to query the gesture.
try
{
if (!IsDisconnected())
{
var gesture = QueryGesture();
PXCMGesture.GeoNode[] nodeData = new PXCMGesture.GeoNode[6];
PXCMGesture.GeoNode singleNode;
searchPattern = PXCMGesture.GeoNode.Label.LABEL_BODY_HAND_PRIMARY;
var status = gesture.QueryNodeData(0, GetSearchPattern(), out singleNode);
if (status >= pxcmStatus.PXCM_STATUS_NO_ERROR)
{
var handler = gestureRaised;
if (handler != null)
{
handler.Invoke(new GesturePositionEventArgs(singleNode.positionImage.x - 85,
singleNode.positionImage.y - 75,
singleNode.body.ToString(),
ScreenWidth,
ScreenHeight));
}
}
}
Sleep(20);
}
catch (Exception ex)
{
// Ensure that we log the exception. We don't
// want to rethrow it.
Log(ex);
}
return true;
}
private static readonly object SyncLock = new object();
private void Sleep(int time)
{
lock (SyncLock)
{
Monitor.Wait(SyncLock, time);
}
}
private PXCMGesture.GeoNode.Label searchPattern;
private PXCMGesture.GeoNode.Label GetSearchPattern()
{
return PXCMGesture.GeoNode.Label.LABEL_BODY_HAND_PRIMARY |
PXCMGesture.GeoNode.Label.LABEL_FINGER_INDEX |
PXCMGesture.GeoNode.Label.LABEL_FINGER_MIDDLE |
PXCMGesture.GeoNode.Label.LABEL_FINGER_PINKY |
PXCMGesture.GeoNode.Label.LABEL_FINGER_RING |
PXCMGesture.GeoNode.Label.LABEL_FINGER_THUMB |
PXCMGesture.GeoNode.Label.LABEL_HAND_FINGERTIP;
}
}
}

In order to use this, you’ll need my AsyncPipelineBase class:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Goldlight.Perceptual.Sdk
{
public abstract class AsyncPipelineBase : UtilMPipeline
{
private readonly object SyncLock = new object();
public async void Run()
{
try
{
await Task.Run(() => {
while (true)
{
this.LoopFrames();
lock (SyncLock)
{
Monitor.Wait(SyncLock, 1000);
}
}
});
}
finally
{
this.Dispose();
}
}
}
}

If the gesture camera wasn’t hooked up to the computer, the call to LoopFrames drops out, which means that we have to maintain a loop for the lifetime of the application. I’m simply wrapping this in a while loop which sleeps for a second if the LoopFrames drops out. This seems to be a reasonable timeout for the application to wait until it picks up again.

Another big change for me this week was dropping the carousel. I know, it seems as though I’m chopping and changing the interface constantly, but that’s what happens in a development, you try something out, refine it and drop it if there’s a better way to do things. There were three main reasons for dropping the carousel:

  1. Selecting a filter from elsewhere in the carousel also triggered the action associated with that filter. This could have been overcome, but the way it looked would have been compromised.
  2. I put the carousel in place to give me a chance to use some of the pre-canned gestures. Unfortunately, I found using them to be far too cumbersome and “laggy”. I grew impatient waiting for it to cycle through the commands.
  3. Using gestures with this just didn’t feel natural or intuitive. This was the biggest issue as far as I’m concerned.

Fortunately, the modular design of Huda allows me to take the items that were associated with the carousel and just drop them into another container with no effort. Two minutes and I had the filters in place in a different way. The downside is that Huda is becoming less and less like a Perceptual application as the interface favours touch more and more. Hmmmm, okay, I’ll revisit that statement. Huda feels less and less like an application that is trying too hard to be Perceptual. Over the last couple of weeks of development, I’ll be augmenting the touch interface with complementary gestures. For instance, I’d like to experiment with putting gesture pinch in place to trigger a x2 zoom. As the core infrastructure is in place, all I would really need to do here is hook the pinch recognition into the zoom command by passing a message. Now you see why I’ve gone with MVVM and message passing.

Part of the reason for the change in the interface came about because of something Steve talked about this week regarding “UserLaziness”. Quite naturally, the left and right hand sides of the screen relate to the operations that the user can perform to open photographs, and to manipulate the lists, but this just didn’t feel quite right. Basically, reordering the filters is something that I expect the user will do a lot less frequently than they will actually adding them, so it seems to me that we should reorder the interface a bit to move the filters to the bottom of the screen and to put the filter selection at the right hand side, where it hovers more naturally under the right hand thumb. This also means that the toolbar has become superfluous – again, it should be in the same location, minimizing the movements you should have to make.

One thing that is easy to get overlooked is that this is an Ultrabook application first and foremost. To that end, it really should make use of Ultrabook features. As a test, I hooked the Accelerometer up to the filters, so shaking it adds, appropriately enough, a blur filter.

This weeks music:

  • AC/DC – For Those About To Rock
  • AC/DC – Back In Black
  • Bon Jovi – What About Now
  • Eric Johnson – Ah Via Musicom
  • Wolfmother – Cosmic Egg
  • Airbourne – Runnin Wild
  • DeeExpus – The King Of Number 33
  • Dream Theater – A Dramatic Turn Of Events

Status Update

So where are we now? Well, Huda feels like an Ultrabook app to me first and foremost. When I start it up, I don’t really need to use the mouse or keyboard at all – the desktop side of it isn’t as necessary because the Ultrabook really makes it easier for the user to use touch to accomplish pretty much everything. I’m not too happy with the pinch zoom and touch drag though – it does very, very odd things – I’ll sort that out so that it is much closer to what you’d expect with a photo editing package.

One point of design that I’d like to touch upon. Nicole raised an interesting point about not saving the filtered version of the photo into the same location as the original. My intention was always to have a separate Export directory in which to store the images, but I still haven’t decided where that’s going to be. So Nicole – here’s your chance to really shape an application to do what you want – where would you like to see exported images reside?

Finally. While playing around with adding filters, it quickly becomes apparent that it’s possible to add them far quicker than they can be applied and rendered. Now, there are things I can do to help with this, but I am making the tough call here that the most important thing for me to concentrate on right now is adding more gestures, and making sure that it’s as robust and tested as I can possibly make it. If there’s time, and it doesn’t impact on the stability, I will mitigate the filter application. The issue is to do with the fact that Bitmap and BitmapSource conversions happen in a lot of the filters. As the filters are reapplied to the base image every time, this conversion could happen each time – I could cache earlier image conversions and just apply the new filter, but reordering the filters will still incur this overhead. As I say, I’ll assess how best to fit this in.

Well, that’s me for this week. Thanks for reading and if there’s anything you need to know, please let me know.

Of Mice and Men and Computed Observables. Oh my.

November 21, 2012 15 comments

I have recently been playing around with Knockout.js, and have been hugely impressed with just how clever it is, and how intuitive. One of the really great features that Knockout provides is the ability to have a computed observable object. That’s a fancy way of saying that it has functionality to update when objects that it is observing update. Borrowing an example from the Knockout website, this simply translates into code like this:

function AppViewModel() {
    this.firstName = ko.observable('Bob');
    this.lastName = ko.observable('Smith');
 
    this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();
    }, this);
}

So, whenever firstName or lastName change, the fullName automatically changes as well.

Wouldn’t it be great if we could do this in WPF as well? I would love it if MVVM allowed you to update one property and have all dependent properties update with it – currently, if you have a dependent property then you have to update that manually.

Well, Dan Vaughan has an amazing implementation of this functionality over on his site, and it’s quite astounding code – he really does have the brain the size of a planet. Being a simpler person though, I had to wonder if there was a simpler way that didn’t involve rewriting a lot of old ViewModels, and the word that kept coming to my mind is MarkupExtension. For those of you who don’t know what a MarkupExtension is, it’s a way to let XAML know that it has something to do that’s a little bit special – you’ve probably already used some even without knowing it – if you’ve used StaticResource, DynamicResource or Binding then you’ve used a MarkupExtension.

So, having thought it through from a few different angles, it seemed as though a MarkupExtension will do the job – and surprise surprise, it does. Before I go into the code, though, I’m going to share some code with you that you will definitely find handy if you write your own extensions that rely on the DataContext. Basically, because of the different places and ways you can declare a DataContext, I wanted some code that I could use to cope with the DataContext not being present when we fired off our extension.

So, let’s break down that extension first. Unsurprisingly, we’re going to make this an abstract extension, as it’s not much use on its own – it’s a great way to support other extensions, but it does nothing useful that we’d want to hook into in the XAML:

public abstract class DataContextBaseExtension : MarkupExtension

If you notice, we are inheriting from MarkupExtension – this is vital if we want to be able to use this as an extension. Now, by convention, extensions always end in Extension, but we don’t have to include that when we declare it in the XAML. Even though we don’t directly use this class in XAML, it’s good to name it this way so that we preserve the intent.

Next, we are going to declare some properties that we will find useful later on.

/// <summary>
/// Gets or sets the target object from the service provider
/// </summary>
protected object TargetObject { get; set; }

/// <summary>
/// Gets or sets the target property from the service provider
/// </summary>
protected object TargetProperty { get; set; }

/// <summary>
/// Gets or sets the target Dependency Object from the service provider
/// </summary>
protected DependencyObject TargetObjectDependencyObject { get; set; }

/// <summary>
/// Gets or sets the target Dependency Property from the service provider;
/// </summary>
protected DependencyProperty TargetObjectDependencyProperty { get; set; }

/// <summary>
/// Gets or sets the DataContext that this extension is hooking into.
/// </summary>
protected object DataContext { get; private set; }

Don’t worry about what they do yet – we’ll cover them more as we examine the code. Assigning these properties is taken care of in the overriden MarkupExtension.ProvideValue implementation:

/// <summary>
/// By sealing this method, we guarantee that the developer will always
/// have to use this implementation.
/// </summary>
public sealed override object ProvideValue(IServiceProvider serviceProvider)
{
    IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

    if (target != null)
    {
        TargetObject = target.TargetObject;
        TargetProperty = target.TargetProperty;

        // Convert to DP and DO if possible...
        TargetObjectDependencyProperty = TargetProperty as DependencyProperty;
        TargetObjectDependencyObject = TargetObject as DependencyObject;

        if (!FindDataContext())
        {
            SubscribeToDataContextChangedEvent();
        }
    }

    return OnProvideValue(serviceProvider);
}

As you can see, assigning these values is straight forward, but we start to see the meat of this class now. By sealing this method, we ensure that we cannot override it (giving us a stable base for this class – we can’t accidentally forget to call this implementation), which means that we have to provide a way for derived classes can do any processing that they need as well. This is why we return an abstract method that derived classes can hook into to do any further processing:

protected abstract object OnProvideValue(IServiceProvider serviceProvider);

Now, this method is only fired once, which means the DataContext could be set long after this code has finished. Fortunately, we can get round this by hooking into the DataContextChanged event.

private void SubscribeToDataContextChangedEvent()
{
    DependencyPropertyDescriptor.FromProperty(FrameworkElement.DataContextProperty, 
        TargetObjectDependencyObject.GetType())
        .AddValueChanged(TargetObjectDependencyObject, DataContextChanged);
}

But how do we actually find the DataContext? Well, this is straightforward as well

private void DataContextChanged(object sender, EventArgs e)
{
    if (FindDataContext())
    {
        UnsubscribeFromDataContextChangedEvent();
    }
}

private bool FindDataContext()
{
    var dc = TargetObjectDependencyObject.GetValue(FrameworkElement.DataContextProperty) ??
        TargetObjectDependencyObject.GetValue(FrameworkContentElement.DataContextProperty);
    if (dc != null)
    {
        DataContext = dc;

        OnDataContextFound();
    }

    return dc != null;
}

private void UnsubscribeFromDataContextChangedEvent()
{
    DependencyPropertyDescriptor.FromProperty(FrameworkElement.DataContextProperty, 
         TargetObjectDependencyObject.GetType())
        .RemoveValueChanged(TargetObjectDependencyObject, DataContextChanged);
}

The only things of any real note in there is that, once the DataContext is found in the event handler, we disconnect from the DataContextChanged event and call an abstract method to let derived classes know that the DataContext is has been found.

protected abstract void OnDataContextFound();

Right, that’s our handy DataContextBaseExtension class out the way (hold onto it – you might find it useful at some point in the future). Now let’s move onto the extension that does the markup mojo. This class adheres to the convention of ending in Extension:

public class ComputedObservableExtension : DataContextBaseExtension

Now, we want to provide the ability for the user to use a parameter name, or to have one automatically applied if we leave it out. You’ve seen this behaviour when you’ve done {Binding Name} or {Binding Path=Name}. In both cases, Path was being referred to, but MarkupExtensions are clever enough to work this out. To do this, we need to supply two constructors, and use something called a ConstructorArgument to tell the extension that a particular property is being defaulted.

public ComputedObservableExtension()
    : this(string.Empty)
{

}

public ComputedObservableExtension(string path)
    : base()
{
    this.Path = path;
}

[ConstructorArgument("path")]
public string Path
{
    get;
    set;
}

We are going to use Path to refer to the computed property. We also need something to tell the extension what properties we are going to observe the change notification on:

public string ObservableProperties
{
    get;
    set;
}

I’m not being fancy with that list. In this implementation, you simply need to supply a comma separated list of properties to this method. Now we are going to hook into the override OnProvideValue method to do a little bit of processing at the startup of this class.

protected override object OnProvideValue(IServiceProvider serviceProvider)
{
    if (TargetProperty != null)
    {
        targetProperty = TargetProperty as PropertyInfo;
        
    }

    if (!string.IsNullOrWhiteSpace(ObservableProperties))
    {
        string[] observables = ObservableProperties
            .Replace(" ", string.Empty)
            .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

        if (observables.Count() > 0)
        {
            observedProperties.AddRange(observables);

            // And update the property...
            UpdateProperty(ComputedValue());
        }
    }

    return string.Empty;
}

Basically, we start off by caching the TargetProperty as a PropertyInfo. We’ll make heavy use of this later on, so it removes a bit of reflection burden if we already have the PropertyInfo at this point. Next, we split the list of observed properties up into a list that we will use later on. The UpdateProperty method is used to actually update the computed property on the screen, so let’s take a look at that next.

private void UpdateProperty(object value)
{
    if (TargetObjectDependencyObject != null)
    {
        if (TargetObjectDependencyProperty != null)
        {
            Action update = () => TargetObjectDependencyObject
                .SetValue(TargetObjectDependencyProperty, value);

            if (TargetObjectDependencyObject.CheckAccess())
            {
                update();
            }
            else
            {
                TargetObjectDependencyObject.Dispatcher.Invoke(update);
            }
        }
        else
        {
            if (targetProperty != null)
            {
                targetProperty.SetValue(TargetObject, value, null);
            }
        }
    }
}

As you can see, this code simply sets the value – either using the DependencyProperty and DependencyObject that we first obtained in the base class, or by updating the standard property if the DP isn’t present.

The ComputedValue method is responsible for retrieving the value from the underlying DataContext.

private string ComputedValue()
{
    if (DataContext == null) return string.Empty;
    if (string.IsNullOrWhiteSpace(Path)) throw new ArgumentException("Path must be set");

    object value = GetPropertyInfoForPath().GetValue(DataContext, null);

    if (value == null)
        return string.Empty;
    return value.ToString();
}

private PropertyInfo propInfo;

private PropertyInfo GetPropertyInfoForPath()
{
    if (propInfo != null) return propInfo;

    propInfo = DataContext.GetType().GetProperty(Path);
    if (propInfo == null) throw new InvalidOperationException(string.Format("{0} is not a valid property", Path));

    return propInfo;
}

We seem to be missing a bit though. If you thought that, you would be right – the bit that’s missing is what happens when we find the DataContext. Well, when we have a DataContext, we use the fact that it’s implemented INotifyPropertyChanged to our own advantage.

protected override void OnDataContextFound()
{
    if (DataContext != null)
    {
        propChanged = DataContext as INotifyPropertyChanged;
        if (propChanged == null) 
            throw new InvalidOperationException("The data context item must support INotifyPropertyChanged");

        UpdateProperty(ComputedValue());

        if (TargetObject != null)
        {
            ((FrameworkElement)TargetObject).Unloaded += ComputedObservableExtension_Unloaded;
        }
        propChanged.PropertyChanged += propChanged_PropertyChanged;
    }
}

void ComputedObservableExtension_Unloaded(object sender, RoutedEventArgs e)
{
    if (TargetObject != null)
    {
        ((FrameworkElement)TargetObject).Unloaded -= ComputedObservableExtension_Unloaded;
    }
    propChanged.PropertyChanged -= propChanged_PropertyChanged;
}

void propChanged_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (observedProperties.Count == 0 || string.IsNullOrWhiteSpace(e.PropertyName) || 
      observedProperties.Contains(e.PropertyName))
    {
        UpdateProperty(ComputedValue());
    }
}

And that’s pretty much it – that’s all we need to give the XAML the markup that we wanted.

But, what does our MVVM code look like now? Well, let’s start with a familiar POCO VM:

public class PersonViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string firstName;
    private string surname;
    private int age;

    public string Firstname
    {
        get { return firstName; }
        set
        {
            if (firstName == value) return;
            firstName = value;
            Notify("Firstname");
        }
    }

    public string Surname
    {
        get { return surname; }
        set
        {
            if (surname == value) return;
            surname = value;
            Notify("Surname");
        }
    }

    public int Age
    {
        get { return age; }
        set
        {
            if (age == value) return;
            age = value;
            Notify("Age");
        }
    }

    public string Details
    {
        get
        {
            return firstName + " " + surname + " is " + age.ToString();
        }
    }

    private void Notify(string prop)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler == null) return;

        handler(this, new PropertyChangedEventArgs(prop));
    }
}

Now, let’s attach it to our main window.

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    private PersonViewModel person;
    public MainWindow()
    {
        InitializeComponent();
        person = new PersonViewModel { Age = 21, Firstname = "Bob", Surname = "Smith" };
        DataContext = person;
    }
}

As you can see – we’ve put no magic in here. It’s just a simple VM. So, what does the XAML look like?

<Window x:Class="ComputedObservableExtensionExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:co="clr-namespace:ComputedObservableExtensionExample"
        Title="Computed ObservableExample" Height="350" Width="525">
    <Window.Resources>
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="FontSize" Value="13.333" />
            <Setter Property="HorizontalAlignment" Value="Left" />
            <Setter Property="VerticalAlignment" Value="Center" />
        </Style>
        <Style TargetType="{x:Type TextBox}">
            <Setter Property="FontSize" Value="13.333" />
            <Setter Property="HorizontalAlignment" Value="Stretch" />
            <Setter Property="VerticalAlignment" Value="Center" />
            <Setter Property="VerticalContentAlignment" Value="Center" />
            <Setter Property="Margin" Value="2" />
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition Height="30" />
            <RowDefinition Height="30" />
            <RowDefinition Height="30" />
        </Grid.RowDefinitions>
        
        <TextBlock Text="Firstname" Grid.Row="0" Grid.Column="0" />
        <TextBlock Text="Surname" Grid.Row="1" Grid.Column="0" />
        <TextBlock Text="Age" Grid.Row="2" Grid.Column="0" />

        <TextBox Text="{Binding Firstname, UpdateSourceTrigger=PropertyChanged}" Grid.Row="0" Grid.Column="1" />
        <TextBox Text="{Binding Surname, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" Grid.Column="1" />
        <TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" Grid.Row="2" Grid.Column="1" />

        <TextBlock Text="{co:ComputedObservable Details, 
            ObservableProperties='Firstname,Surname,Age'}" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" />
    </Grid>
</Window>

And that’s it – the TextBlock with the ComputedObservable extension has been told that the Path is the Details property, and the properties that it’s observing are Firstname, Surname and Age – so changing any of those properties results in the computed property being updated as well.

Any comments, enhancements or observations, please feel to let me know. This is a first pass at this extension, and I’ve no doubt that I will be revisiting and enhancing it as I use it more and more. I will, of course, come back here and update this post as I do so.

Note – this project is a VS2012 solution, but it does not make use of any .NET 4.5 specific features, so you should be able to use the code in a VS2010 app.

Source is available here. Examplezip

Undoing MVVM

February 1, 2010 12 comments

I apologise that it’s been a while since I last blogged, but I’ve been busy working on a MVVM framework and it’s been eating up a lot of time – it’s good eat, but it is time consuming. One of the things I’ve been adding into the code is the ability to handle undo/redo functionality in a ViewModel; and more importantly, coordinating undo/redo across multiple View Models. In this blog post, I’d like to demonstrate how easy it is to add this functionality to properties that support change notification. In a future blog, I’ll be demonstrating how to extend this to supporting ObservableCollections as well.

The first thing that we’re going to do is define a simple undo/redo interface. Here it is, in all it’s glory:

using System;
namespace UndoRedoSample
{
    /// <summary>
    /// The interface describing the Undo/Redo operation.
    /// </summary>
    public interface IUndoRedo
    {
        /// <summary>
        /// The optional name for the Undo/Redo property.
        /// </summary>
        string Name { get; }
        /// <summary>
        /// Code to perform the Undo operation.
        /// </summary>
        void Undo();
        /// <summary>
        /// Code to perform the Redo operation.
        /// </summary>
        void Redo();
    }
}

Now, we need to create a class that implements this interface.

using System;
namespace UndoRedoSample
{
    /// <summary>
    /// This class encapsulates a single undoable property.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class UndoableProperty<T> : IUndoRedo
    {
        #region Member
        private object _oldValue;
        private object _newValue;
        private string _property;
        private T _instance;
        #endregion

        /// <summary>
        /// Initialize a new instance of <see cref="UndoableProperty"/>.
        /// </summary>
        /// <param name="property">The name of the property.</param>
        /// <param name="instance">The instance of the property.</param>
        /// <param name="oldValue">The pre-change property.</param>
        /// <param name="newValue">The post-change property.</param>
        public UndoableProperty(string property, T instance, object oldValue, object newValue)
            : this(property, instance, oldValue, newValue, property)
        {
        }

        /// <summary>
        /// Initialize a new instance of <see cref="UndoableProperty"/>.
        /// </summary>
        /// <param name="property">The name of the property.</param>
        /// <param name="instance">The instance of the property.</param>
        /// <param name="oldValue">The pre-change property.</param>
        /// <param name="newValue">The post-change property.</param>
        /// <param name="name">The name of the undo operation.</param>
        public UndoableProperty(string property, T instance, object oldValue, object newValue, string name)
            : base()
        {
            _instance = instance;
            _property = property;
            _oldValue = oldValue;
            _newValue = newValue;

            Name = name;

            // Notify the calling application that this should be added to the undo list.
            UndoManager.Add(this);
        }

        /// <summary>
        /// The property name.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Undo the property change.
        /// </summary>
        public void Undo()
        {
            _instance.GetType().GetProperty(_property).SetValue(_instance, _oldValue, null);
        }

        public void Redo()
        {
            _instance.GetType().GetProperty(_property).SetValue(_instance, _newValue, null);
        }
    }
}

This class simply wraps a property. Whenever Undo is called, reflection is used to set the property back to it’s prechanged value. Calling Redo reverses this change. Now, that’s all well and good, but we need to keep track of these changes and apply them – and more importantly, we need to apply them across ViewModels. This is where the UndoManager comes in:

using System;
using System.Collections.Generic;
using System.Linq;

namespace UndoRedoSample
{
    /// <summary>
    /// This class is responsible for coordinating the undo/redo messages from the various view models
    /// in the application. By having a central repository, the undo/redo state is managed without the
    /// need for the VMs having to subscribe to any complex hierarchy.
    /// </summary>
    public static class UndoManager
    {
        #region Members
        private static RangeObservableCollection<IUndoRedo> _undoList;
        private static RangeObservableCollection<IUndoRedo> _redoList;
        private static int? _maxLimit;
        #endregion

        /// <summary>
        /// Add an undoable instance into the Undo list.
        /// </summary>
        /// <typeparam name="T">The type of instance this is.</typeparam>
        /// <param name="instance">The instance this undo item applies to.</param>
        public static void Add<T>(T instance) where T : IUndoRedo
        {
            if (instance == null)
                throw new ArgumentNullException("instance");

            UndoList.Add(instance);
            RedoList.Clear();

            // Ensure that the undo list does not exceed the maximum size.
            TrimUndoList();
        }

        /// <summary>
        /// Get or set the maximum size of the undo list.
        /// </summary>
        public static int? MaximumUndoLimit
        {
            get
            {
                return _maxLimit;
            }
            set
            {
                if (value.HasValue && value.Value < 0)
                {
                    throw new ArgumentOutOfRangeException("value");
                }
                _maxLimit = value;
                TrimUndoList();
            }
        }

        /// <summary>
        /// Ensure that the undo list does not get too big by
        /// checking the size of the collection against the
        /// <see cref="MaximumUndoLimit"/>
        /// </summary>
        private static void TrimUndoList()
        {
            if (_maxLimit.HasValue && _maxLimit.Value > 0)
            {
                while (_maxLimit.Value < UndoList.Count)
                {
                    UndoList.RemoveAt(0);
                }
            }
        }

        /// <summary>
        /// Actions can only be undone if there are items in the <see cref="UndoList"/>.
        /// </summary>
        public static bool CanUndo
        {
            get
            {
                return UndoList.Count > 0;
            }
        }

        /// <summary>
        /// Actions can only be redone if there are items in the <see cref="RedoList"/>.
        /// </summary>
        public static bool CanRedo
        {
            get
            {
                return RedoList.Count > 0;
            }
        }

        /// <summary>
        /// Clear all items from the list.
        /// </summary>
        public static void ClearAll()
        {
            UndoList.Clear();
            RedoList.Clear();
        }

        /// <summary>
        /// Undo the last VM change.
        /// </summary>
        public static void Undo()
        {
            if (UndoList.Count > 0)
            {
                // Extract the item from the undo list.
                IUndoRedo item = UndoList.Last();
                UndoList.RemoveAt(UndoList.Count - 1);
                List<IUndoRedo> copyRedoList = RedoList.ToList();
                copyRedoList.Add(item);
                // We need to copy the undo list here.
                List<IUndoRedo> copyUndoList = UndoList.ToList();
                item.Undo();
                // Now repopulate the undo and redo lists.
                UpdateRedoList(copyRedoList);
                UndoList.Clear();
                UndoList.AddRange(copyUndoList);
            }
        }

        /// <summary>
        /// Redo the last undone VM change.
        /// </summary>
        /// <remarks>
        /// Unlike the undo operation, we don't need to copy the undo list out
        /// because we want the item we're redoing being added back to the redo
        /// list.
        /// </remarks>
        public static void Redo()
        {
            if (RedoList.Count > 0)
            {
                // Extract the item from the redo list.
                IUndoRedo item = RedoList.Last();
                // Now, remove it from the list.
                RedoList.RemoveAt(RedoList.Count - 1);
                // Here we need to copy the redo list out because
                // we will clear the list when the Add is called and
                // the Redo is cleared there.
                List<IUndoRedo> redoList = RedoList.ToList();
                // Redo the last operation.
                item.Redo();
                // Now reset the redo list.
                UpdateRedoList(redoList);
            }
        }

        private static void UpdateRedoList(List<IUndoRedo> redoList)
        {
            RedoList.Clear();
            RedoList.AddRange(redoList);
        }

        /// <summary>
        /// Get the undo list.
        /// </summary>
        public static RangeObservableCollection<IUndoRedo> UndoList
        {
            get
            {
                if (_undoList == null)
                    _undoList = new RangeObservableCollection<IUndoRedo>();
                return _undoList;
            }
            private set
            {
                _undoList = value;
            }
        }

        /// <summary>
        /// Get the redo list.
        /// </summary>
        public static RangeObservableCollection<IUndoRedo> RedoList
        {
            get
            {
                if (_redoList == null)
                    _redoList = new RangeObservableCollection<IUndoRedo>();
                return _redoList;
            }
            private set
            {
                _redoList = value;
            }
        }
    }
}

In this code, we have two lists – the undoable list and the redoable list. These lists wrap up the IUndoRedo interface we defined earlier and will actually handle calling Undo or Redo as appropriate. There’s a little wrinkle when you call Undo – we need to copy the Redo list out to a temporary copy so that we can add it back later on. The way that the undo works, is to extract the last item from the undo stack – which then gets removed. This item is put onto the redo stack so that we can redo it later if needs be. If you notice, in the Add method, we clear the Redo stack so that we can’t perform a Redo after a new operation. As the property gets updated and triggers the Add method, we have to copy the Redo out, and add it back in after the Add has been performed.

All you need to do now, is wire your ViewModel up to the UndoManager and Robert’s your mothers brother. I’ve attached a sample application which demonstrates this in action – this application isn’t finished yet as we’re leaving room for the next installment, where we hook into an undoable observable collection. Here’s a screenshot of the application in action:

Download: The usual rules apply, the download needs to be renamed from .doc to .zip. undoredosamplezip

The Canny Coder

Java 8 Functional Programming with Lambda Expressions

pihole.org

Adventures in theoretical computer science, with your host, chaiguy1337

Confessions of a coder

Confessions of a WPF lover

WordPress.com

WordPress.com is the best place for your personal blog or business site.

Follow

Get every new post delivered to your Inbox.

Join 38 other followers