OrientationHelper for Windows Phone and Windows 8

This is a small utility to help with WP7,WP8 and Windows Store app development where there is a need to design your UX in order to support multiple screen sizes and orientations.
(Phew i almost said Metro Style..Wait..Damn it)

It is inspired by ‘LayoutAwarePage’ , an utility class included in the templates for Windows Store Apps.

On the Windows Phone side there are 6 orientation states:

  • Landscape
  • LandscapeLeft
  • LandscapeRight
  • Portrait
  • PortraitUp
  • PortraitDown

On the Windows Store side there are 4 view states :

  • Filled
  • Snapped
  • FullScreenLandscape
  • FullScreenPortrait

Before we go into details here is an example of the utility working on the Phone in Portrait mode :
On the left side we have setup our utility to automatically run some arbitrary scale animations ,defined in XAML, based on the current Orientation or ViewState. There’s nothing sophisticated in the screenshots below. Simply showing off the functionality.

Here you can see another one automatically running on the left side in Landscape mode.

Similarly on the Windows 8 side:

(FullScreenPortrait)

(FullScreenLandscape)

(Filled)

(Snapped)

What the designer needs to do is describe the UI (Xaml) for all screens and for all possible orientations/view states in order to provide a good UX. There is no clear pattern/guidelines on how this should be done although LayoutAwarePage gives us a good ‘clean’ solution by taking advantage of a well known proven XAML solution called the VisualStateManager. We are, after all, trying to tackle switching between multiple visual states so VisualStateManager sounds like a natural fit. And it is.

The basic idea is , similar to creating Custom Controls, to declare all views for all supported orientations/view states inside the same XAML file (what if the UI is composite though?) and use VisualStates, one for each specific orientation/view state, to describe the UI changes needed to transition between View designs. We will then let VisualStateManager do it’s work by asking it programmatically to switch between states when the platform notifies us of a change in orientation/view state.

What the developer needs to do is manually listen for orientation/view state changes for all visible screens
and call VisualStateManager to transition the current screen to the current view state.

This programmatic part of the developer seems like a lot of manual work that could be done automatically.
There’s gonna be a lot of code-behind spread around listening for the same event and dealing manually
with VisualState changing (or manually enabling/disabling controls, running animations etc etc if we didnt
use VisualStateManager)
Unfortunately no one can help the poor designer (developer?) that has to rewrite/redesign the whole UI
again and again. It has to happen.

LayoutAwarePage solves it by creating a Page base class for Metro style apps.
This utility is instead an attached property and supports Pages, user controls and custom controls. Haven’t made a sample with custom controls but it should be the same as user controls.
What if though i want to create a reusable custom control/user control and i want to have IT define how it should look on different orientations/view states instead of forcing the consumer/user of the control to do it.
Sometimes ,me as a user of a control, might need to tweak more stuff “inside” the control to make it look right. Lets say for example that i have a control that defines two buttons side by side ,stretched, that fill my Landscape view across nicely. I would have liked if the control itself switched them to be stacked (one on top of the other) when in Portrait mode. I can’t do that though unless the control supported that. Note that it would be best if the control supported that but didn’t enforce it.I the user would like to have control over when and if the control is allowed to auto-adapt by itself or i will solve it outside or the control. The utility supports this scenario by letting me decide whether or not to set the attached property OrientationHelper.IsActive on the control that enables it’s orientation/view state support if of course the control has defined the appropriate VisualStates.

The solution seemed transferable to Windows Phone as well and so the utility comes in 3 assemblies supporting WP7/WP8/WIN8.

What we need to do in order to use it is
1) set the attached property OrientationHelper.IsActive=”true” on a Page, UserControl or CustomControl
2) Define one VisualStateGroup on the orientation-aware control named “PageOrientationStates” for WP
and “ApplicationViewStates” for Win8.
3) Define one VisualState for each supported Orientation/ViewState. The VisualState name should match exactly the name of the Orientation/ViewState. For example i would name “Filled” the VisualState that is meant to design my UI when in Filled state.

Setting the attached property :
In this case the UserControl set’s it for demo purposes .It’s overridable.

Defining the VisualStates (For Win8) :

Quick tip : VisualStateGroups and VisualStateManager , i found, work *only* when defined/attached on the
RootElement of your Page or UserControl. Which means it wont work if you declare a control inside a Page, declare VisualStates on it and try to call VSM on that specific control.

Here’s the code for the OrientationHelper.

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

#if WINDOWS_PHONE
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;
using Orientation = Microsoft.Phone.Controls.PageOrientation;
#else
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Orientation = Windows.UI.ViewManagement.ApplicationViewState;
#endif

namespace OrientationHelper
{
    public static class OrientationHelper
    {
#if WINDOWS_PHONE
        private const String VisualStateGroupName = "PageOrientationStates";
#else
        private const String VisualStateGroupName = "ApplicationViewStates";
#endif

        #region IsActive DP

        public static readonly DependencyProperty IsActiveProperty =
            DependencyProperty.RegisterAttached("IsActive", typeof (bool), typeof (OrientationHelper),
                                                new PropertyMetadata(default(bool), OnIsActivePropertyChanged));

        public static void SetIsActive(UIElement element, bool value)
        {
            element.SetValue(IsActiveProperty, value);
        }

        public static bool GetIsActive(UIElement element)
        {
            return (bool) element.GetValue(IsActiveProperty);
        }

        private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var control = d as Control;
            if (control == null)
                return;

            SetupOrientationAwareControl(control, (bool)e.NewValue);

#if WINDOWS_PHONE
            //The control itself can be a page.In every case we need to retry until the containing page is loaded.
            control.Loaded += OnControlLoaded;
#endif

        }

        #endregion

#if WINDOWS_PHONE
        //Note: On the phone side Orientation is Page-specific so we keep the orientation aware controls
        //on the page itself using a Dependency Property
        #region OrientationAwareControls Private DP

        private static readonly DependencyProperty OrientationAwareControlsProperty =
            DependencyProperty.RegisterAttached("OrientationAwareControls", typeof (IList),
                                                typeof (OrientationHelper), null);

        private static void SetOrientationAwareControls(DependencyObject element, IList value)
        {
            element.SetValue(OrientationAwareControlsProperty, value);
        }

        private static IList GetOrientationAwareControls(DependencyObject element)
        {
            return (IList) element.GetValue(OrientationAwareControlsProperty);
        }

        #endregion
#else
        //Note: On the Windows 8 side Orientation is global
        private static IList<WeakReference> orientationAwareControls;
#endif

        private static void SetupOrientationAwareControl(Control control, bool isActive)
        {
#if WINDOWS_PHONE
            var page = FindParentPage(control);
            if (page == null)
                return;
            if (isActive)
            {
                //Add the orientation aware control to the list stored on the Page
                if (GetOrientationAwareControls(page) == null)
                {
                    SetOrientationAwareControls(page, new List());
                    //Start listening for changes
                    page.OrientationChanged += PageOnOrientationChanged;
                }
                if (!GetOrientationAwareControls(page).Contains(control))
                    GetOrientationAwareControls(page).Add(control);
                UpdateOrientationAwareControls(new[] { control }, page.Orientation); //Update this control only

            }
            else
            {
                var orientationAwareControls = GetOrientationAwareControls(page);
                if (orientationAwareControls != null)
                {
                    if (orientationAwareControls.Contains(control))
                    {
                        orientationAwareControls.Remove(control);
                        control.Loaded -= OnControlLoaded;
                    }
                    //Do cleanup for this page if there are no more controls
                    if (!orientationAwareControls.Any())
                    {
                        SetOrientationAwareControls(page, null);
                        page.OrientationChanged -= PageOnOrientationChanged;
                    }
                }
            }
#else
            if (isActive)
            {
                if (orientationAwareControls == null)
                    orientationAwareControls = new List();

                if (!GetOrientationAwareControls().Contains(control))
                {
                    orientationAwareControls.Add(new WeakReference(control));
                    // Note: On the Windows 8 side, probably due to aggressive optimizations,
                    // if the control is not named then when changing orientations/view states any
                    // WeakReference to that control will show it as GCed!
                    // Disclaimer : I ONLY TESTED ON THE SIMULATOR!!
                    if (string.IsNullOrEmpty(control.Name))
                    {
                        control.Name = Guid.NewGuid().ToString();
                    }
                    control.Loaded += OnControlLoaded;

                    if (control.Parent != null)
                    {
                        UpdateOrientationAwareControls(new[] { control }, ApplicationView.Value);
                    }
                }
                if (GetOrientationAwareControls().Any())
                {
                    //TODO: Handle Window changes (Closed)
                    Window.Current.SizeChanged += OnWindowSizeChanged;
                }

            }
            else
            {
                if (GetOrientationAwareControls().Contains(control))
                {
                    var controlWeakRef = orientationAwareControls.Single(weak => control.Equals(weak.Target));
                    orientationAwareControls.Remove(controlWeakRef);
                    control.Loaded -= OnControlLoaded;
                }
                if (!GetOrientationAwareControls().Any())
                {
                    orientationAwareControls = null;
                    Window.Current.SizeChanged -= OnWindowSizeChanged;
                }
            }
#endif
        }

        private static void OnControlLoaded(object sender, RoutedEventArgs routedEventArgs)
        {
            var control = sender as Control;
#if WINDOWS_PHONE
            SetupOrientationAwareControl(control, GetIsActive(control));
#else
            UpdateOrientationAwareControls(new[] { control }, ApplicationView.Value);
#endif
        }

#if WINDOWS_PHONE

        private static PhoneApplicationPage FindParentPage(FrameworkElement el)
        {
            FrameworkElement parent = el;
            while(parent != null)
            {
                if (parent is PhoneApplicationPage)
                    return parent as PhoneApplicationPage;
                parent = parent.Parent as FrameworkElement;
            }
            return null;
        }

        private static void PageOnOrientationChanged(object sender, OrientationChangedEventArgs e)
        {
            var page = sender as PhoneApplicationPage;
            UpdateOrientationAwareControls(GetOrientationAwareControls(page), page.Orientation); //Update all controls
        }
#else
        private static void OnWindowSizeChanged(object sender, WindowSizeChangedEventArgs windowSizeChangedEventArgs)
        {
            UpdateOrientationAwareControls(GetOrientationAwareControls(), ApplicationView.Value);
        }
        private static IEnumerable<Control> GetOrientationAwareControls()
        {
            if(orientationAwareControls == null || !orientationAwareControls.Any())
                return Enumerable.Empty();

            IList<Control> aliveControls = new List<Control>();
            foreach (var controlWeakRef in orientationAwareControls.ToList())
            {
                if (controlWeakRef.IsAlive)
                    aliveControls.Add(controlWeakRef.Target as Control);
                else
                    orientationAwareControls.Remove(controlWeakRef);
            }
            return aliveControls;
        }
#endif

        private static void UpdateOrientationAwareControls(IEnumerable controls, Orientation orientation)
        {
            if (controls == null || !controls.Any())
                return;

            foreach (var control in controls)
            {
                VisualStateManager.GoToState(control, GetVisualStateName(orientation), true);
            }
        }

        private static String GetVisualStateName(Orientation orientation)
        {
            return orientation.ToString();
        }
    }
}

Here’s the whole solution : OrientationHelper.zip

Feel free to comment if you found it useful or found any issue.

Au revoir

Advertisements

Solving MvvmLight’s EventToCommand Memory Leak (WP7)

By using MvvmLight‘s EventToCommand you are going to leak serious memory.
Controls and whole Pages you navigate to will never be garbage collected.
Won’t be so “Light” after all if your not carefull.

Some links on the issue here and here.

First let’s try to get a good understanding on why and how this happens and then we will solve it ourselves!

All the code included below for your pleasure 😉

Lets analyze the resulting object reference graph and visualize the leak.

Your ViewModel keeps a reference to a Command.

  • ViewModel -> Command

EventToCommand hooks onto CanExecuteChanged of the Command and therefore EventToCommand will stay alive as long as the Command stays alive.

  • Command -> EventToCommand

EventToCommand is a TriggerAction which means it keeps a reference to the element it has attached on (AssociatedObject property).

  • EventToCommand -> FrameworkElement

UIElements in the visual tree keep a reference to their parent which recursively means they hold on to the Page.

  • FrameworkElement -> Page

So we have this reference chain : ViewModel -> Command -> EventToCommand -> FrameworkElement -> Page

So every Page will live in memory as long as all ViewModels referenced by EventToCommands inside it stay alive.Now its commonplace for ViewModels to stay alive for a long time through an IoC or even being statically declared in App.xaml and so there we go.We found it!
Sounds easy and obvious now but it wasn’t so easy to locate.It took plenty of time fiddling around with the Windows Phone Performance Analysis Tool.Excellent tool.Could be better.Huge improvement compared to WinDbg 🙂

Ok so lets solve it.Whats the weakest link in that chain?

You guessed it its the CanExecuteChanged event.
The command does not need to reference EventToCommand.
This is where WeakEventListener comes in.Its not the purpose of this post to explain how WeakEvents and listeners work so go ahead and do some reading on the net to get you acquainted.

We know EventToCommand adds and removes an EventHandler to/from Command.CanExecuteChanged.
What would be perfect is if EventToCommand itself added a WeakEventListener to the Command and solve this thing for us.
UPDATE: Apparently MvvmLight is maybe trying to “solve” this issue in the next release but in a way that
totally doesn’t make sense to me.By using WeakActions inside of RelayCommand.I have opened a discussion on the issue here with detail explanation.

For those interested here’s one way of solving it inside EventToCommand that i found just playing around (without a lot of design and classes) :

//Need this instance field in order to unhook the correct handler
private EventHandler hSafeCanExecuteChanged = null;
private static void OnCommandChanged(EventToCommand element, DependencyPropertyChangedEventArgs e)
{
	//Effectively the idea here is that instead of implicitly "closure-ing" this (meaning EventToCommand)
	//inside of the EventHandler we don't access anything from the surrounding environment and instead
        //pass this as a WeakReference thus allowing us to be GCed.
	if (element != null)
	{
		if (e.OldValue != null)
		{
			((ICommand)e.OldValue).CanExecuteChanged -= element.hSafeCanExecuteChanged;
			element.hSafeCanExecuteChanged = null;
		}
		ICommand newValue = (ICommand)e.NewValue;
		if (newValue != null)
		{
			var weakElement = new WeakReference(element);
			element.hSafeCanExecuteChanged =
				(o, args) =>
				{
					var evtToCommand = weakElement.Target as EventToCommand;
					if (evtToCommand != null)
						evtToCommand.OnCommandCanExecuteChanged(args);
				};
			newValue.CanExecuteChanged += element.hSafeCanExecuteChanged;
		}
		element.EnableDisableElement();
	}
}

UPDATE: Here is the whole EventToCommand with the fix:

public class EventToCommand : TriggerAction<FrameworkElement>
{
	#region Command Dependency Property

	public ICommand Command
	{
		get { return (ICommand)GetValue(CommandProperty); }
		set { SetValue(CommandProperty, value); }
	}

	public static readonly DependencyProperty CommandProperty =
		DependencyProperty.Register("Command",
									typeof(ICommand),
									typeof(EventToCommand),
									new PropertyMetadata(null, OnCommandPropertyChanged));

	private static void OnCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
	{
		var instance = d as EventToCommand;
		if (instance != null)
			instance.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue);
	}

	#endregion Command Dependency Property

	#region CommandParameter Dependency Property

	public object CommandParameter
	{
		get { return (object)GetValue(CommandParameterProperty); }
		set { SetValue(CommandParameterProperty, value); }
	}

	public static readonly DependencyProperty CommandParameterProperty =
		DependencyProperty.Register("CommandParameter",
									typeof(object),
									typeof(EventToCommand),
									new PropertyMetadata(null, OnCommandParameterPropertyChanged));

	private static void OnCommandParameterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
	{
		var instance = d as EventToCommand;
		if (instance != null && instance.AssociatedObject != null)
			instance.EnableDisableElement();
	}

	#endregion CommandParameter Dependency Property

	#region MustToggleIsEnabled Dependency Property

	public bool MustToggleIsEnabled
	{
		get { return (bool)GetValue(MustToggleIsEnabledProperty); }
		set { SetValue(MustToggleIsEnabledProperty, value); }
	}

	public static readonly DependencyProperty MustToggleIsEnabledProperty =
		DependencyProperty.Register("MustToggleIsEnabled",
									typeof(bool),
									typeof(EventToCommand),
									new PropertyMetadata(OnMustToggleIsEnabledPropertyChanged));

	private static void OnMustToggleIsEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
	{
		var instance = d as EventToCommand;
		if (instance != null && instance.AssociatedObject != null)
			instance.EnableDisableElement();
	}

	#endregion MustToggleIsEnabled Dependency Property

	#region Public
	public bool PassEventArgsToCommand { get; set; }

	public object CommandParameterValue
	{
		get
		{
			return (this._commandParameterValue ?? this.CommandParameter);
		}
		set
		{
			this._commandParameterValue = value;
			this.EnableDisableElement();
		}
	}
	public void Invoke()
	{
		this.Invoke(null);
	}
	#endregion

	private object _commandParameterValue;
	private WeakEventListener<EventToCommand, Object, EventArgs> weakCanExecuteChangedListener;

	protected void OnCommandChanged(ICommand oldCommand, ICommand newCommand)
	{
		if (oldCommand != null)
		{
			weakCanExecuteChangedListener.Detach();
			weakCanExecuteChangedListener = null;
		}
		if (newCommand != null)
		{
			weakCanExecuteChangedListener = new WeakEventListener<EventToCommand, object, EventArgs>(this);
			weakCanExecuteChangedListener.OnEventAction =
				(me, sender, args) =>
				{
					me.EnableDisableElement();
				};
			weakCanExecuteChangedListener.OnDetachAction =
				(weakListener) =>
				{
					newCommand.CanExecuteChanged -= weakListener.OnEvent;
				};
			newCommand.CanExecuteChanged += weakCanExecuteChangedListener.OnEvent;
		}
		this.EnableDisableElement();
	}

	protected override void Invoke(object parameter)
	{
		if (!this.AssociatedElementIsDisabled())
		{
			ICommand command = this.GetCommand();
			object commandParameterValue = this.CommandParameterValue;
			if ((commandParameterValue == null) && this.PassEventArgsToCommand)
			{
				commandParameterValue = parameter;
			}
			if ((command != null) && command.CanExecute(commandParameterValue))
			{
				command.Execute(commandParameterValue);
			}
		}

	}

	protected void EnableDisableElement()
	{
		Control associatedObject = this.GetAssociatedObject();
		if (associatedObject != null)
		{
			if (this.ReadLocalValue(MustToggleIsEnabledProperty) != DependencyProperty.UnsetValue
				&& MustToggleIsEnabled)
			{
				ICommand command = this.GetCommand();
				if (command != null)
				{
					associatedObject.IsEnabled = command.CanExecute(this.CommandParameterValue);
				}
			}
		}
	}
	protected override void OnAttached()
	{
		base.OnAttached();
		this.EnableDisableElement();
	}

	#region Private
	private Control GetAssociatedObject()
	{
		return (base.AssociatedObject as Control);
	}
	private ICommand GetCommand()
	{
		return this.Command;
	}
	private bool AssociatedElementIsDisabled()
	{
		Control associatedObject = this.GetAssociatedObject();
		return ((associatedObject != null) && !associatedObject.IsEnabled);
	}
	#endregion

}

Until that happens though lets hack our way around it cause that’s more fun shall we.
We know also we have to extend it but we do not have access unfortunately to any of its internals.We do have access though to the Command DP since it has to be public.What could we do?
How about replacing (behind the scenes) the original Command with our own Wrapper (Adapter) Command?
Lets call it WeakCommand.It will internally hold on to the original command but this time we will listen to it using a WeakEventListener. WeakCommand will of course itself be an ICommand which will just delegate all calls to and from the original.Nice.Seems that might do the trick.
So how do we “replace” the original command that ETC gets data bound to? We need to listen to it for changes and when it changes we will update it by calling
ETC.Command = new WeakCommand(ETC.Command);

NOTE: This will remove the ViewModel-Command Binding! If for some reason you change your Command from within your ViewModel and expect it to rebind this won’t happen.
That’s an uncommon case though.
This might be solvable as well but not sure yet.Am thinking something along the lines of grabbing the original BindingExpression (and therefore the Binding object) from the Command DP and apply that to BindingListener.Will look into that as well.


This will call ETC’s code to unhook from the Original command (what we wanted) and then immediatelly hook onto our own WeakCommand.
How do we listen for Dependency Property changes when there is no event though?
Easy.We’ll make a BindingListener class with one generic DependencyProperty of type Object and bind that to the Command DP of ETC.This way when ETC.Command changes that surrogate is gonna notify us so we know to replace the command.The binding surrogate is not at all new btw.It’s probably as old as Silverlight.
Hmm.Sounds good but will that work? Of course it will.Would i be making this post if it didnt? 😛

Lets start one by one.The BindingListener :

///
<summary> /// Helper class to be able to listen for DependencyProperty Changes (for example Visibility)
/// </summary>
public class BindingListener : DependencyObject
{
	private readonly Action<Object, Object> valueChangedCallback;

	public BindingListener(Action<Object,Object> valueChangedCallback)
	{
		if (valueChangedCallback == null)
			throw new ArgumentNullException("valueChangedCallback");
		this.valueChangedCallback = valueChangedCallback;
	}

	public object Value
	{
		get { return (object)GetValue(ValueProperty); }
		set { SetValue(ValueProperty, value); }
	}
	public static readonly DependencyProperty ValueProperty =
		DependencyProperty.Register("Value", typeof(object), typeof(BindingListener), new PropertyMetadata(null, OnValuePropertyChanged));

	private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
	{
		var source = d as BindingListener;
		if (source != null)
		{
			if (!source.valueChangingBecauseOfUnbind)
			{
				source.valueChangedCallback(e.OldValue, e.NewValue);
			}
		}
	}

	public void Bind(Object source, String sourceProperty)
	{
		var binding = new System.Windows.Data.Binding(sourceProperty);
		binding.Source = source;
		System.Windows.Data.BindingOperations.SetBinding(this, ValueProperty, binding);
	}
	private bool valueChangingBecauseOfUnbind;
	public void UnBind()
	{
		valueChangingBecauseOfUnbind = true;
		Value = null;
		valueChangingBecauseOfUnbind = false;
	}

}

Next up WeakCommand :


///
<summary> /// What it all means (Goal: WE should be able to be garbage collected) :
/// - WE hold a reference to the long living wrapped command
/// - The long living command will hold the Listener instead and NOT us therefore we can die
/// - When we die the long living command stills holds the listener so on the next event
/// he fires the listener will be removed as well.
/// </summary>
public class WeakCommand : ICommand, IDisposable
{
	private WeakEventListener<WeakCommand, Object, EventArgs> weakListener;
	private ICommand wrapped;
	public WeakCommand(ICommand wrapped)
	{
		this.wrapped = wrapped;
		weakListener = new WeakEventListener<WeakCommand, Object, EventArgs>(this);
                //Don't worry.You are not alone.It IS really tricky to understand this 😛
		weakListener.OnEventAction =
			(me, wrappedCommand, args) =>
			{
				//DON'T CLOSURE ANYTHING HERE.USE PARAMETERS ONLY
				if (me.CanExecuteChanged != null)
					me.CanExecuteChanged(wrappedCommand, args);
			};
		weakListener.OnDetachAction =
			(listener) =>
			{
				//If I die then remove the Listener instance as well from wrapped command
				wrapped.CanExecuteChanged -= listener.OnEvent;
			};
		wrapped.CanExecuteChanged += weakListener.OnEvent;
	}

	#region IDisposable
	private bool isDisposed;
	public void Dispose()
	{
		if (!isDisposed)
		{
			weakListener.Detach();
			weakListener = null;
			wrapped = null;
			isDisposed = true;
		}

	}
	#endregion

	#region ICommand
	public bool CanExecute(object parameter)
	{
		ThrowIfDisposed();
		return wrapped.CanExecute(parameter);
	}

	public event EventHandler CanExecuteChanged;

	public void Execute(object parameter)
	{
		ThrowIfDisposed();
		wrapped.Execute(parameter);
	}
	#endregion

	private void ThrowIfDisposed()
	{
		if (isDisposed)
			throw new ObjectDisposedException("WeakCommand");
	}
}

Lets get WeakEventListener in there as well:

// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

using System.Diagnostics.CodeAnalysis;
using System;

namespace WeakEventListener
{
	///
<summary> /// Implements a weak event listener that allows the owner to be garbage
 /// collected if its only remaining link is an event handler.
 /// </summary>
	/// Type of instance listening for the event.
	/// Type of source for the event.
	/// Type of event arguments for the event.
	[SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Used as link target in several projects.")]
	public class WeakEventListener where TInstance : class
	{
		///
<summary> /// WeakReference to the instance listening for the event.
 /// </summary>
		private WeakReference _weakInstance;

		///
<summary> /// Gets or sets the method to call when the event fires.
 /// </summary>
		public Action OnEventAction { get; set; }

		///
<summary> /// Gets or sets the method to call when detaching from the event.
 /// </summary>
		public Action OnDetachAction { get; set; }

		///
<summary> /// Initializes a new instances of the WeakEventListener class.
 /// </summary>
		///Instance subscribing to the event.
		public WeakEventListener(TInstance instance)
		{
			if (null == instance)
			{
				throw new ArgumentNullException("instance");
			}
			_weakInstance = new WeakReference(instance);
		}

		///
<summary> /// Handler for the subscribed event calls OnEventAction to handle it.
 /// </summary>
		///Event source.
		///Event arguments.
		public void OnEvent(TSource source, TEventArgs eventArgs)
		{
			TInstance target = (TInstance)_weakInstance.Target;
			if (null != target)
			{
				// Call registered action
				if (null != OnEventAction)
				{
					OnEventAction(target, source, eventArgs);
				}
			}
			else
			{
				// Detach from event
				Detach();
			}
		}

		///
<summary> /// Detaches from the subscribed event.
 /// </summary>
		public void Detach()
		{
			if (null != OnDetachAction)
			{
				OnDetachAction(this);
				OnDetachAction = null;
			}
		}
	}
}

Finally its time to start messing with EventToCommand.Here it is :

public class EventToCommandEx : GalaSoft.MvvmLight.Command.EventToCommand
{
	///
<summary> /// Helper to be able to listen to Command changes.
 /// </summary>
	private BindingListener commandBindingListener;

	protected override void OnAttached()
	{
		base.OnAttached();
		commandBindingListener = new BindingListener(
			(oldValue, newValue) =>
				{
					var weakOld = oldValue as WeakCommand;
					if(weakOld != null)
					{
						weakOld.Dispose();
					}
					if (newValue is WeakCommand)
						return; //We were called because we changed the Command below.Ignore
					var newCommand = newValue as ICommand;
					if (newCommand != null)
					{
						this.Command = new WeakCommand(newCommand);
					}
				});
		commandBindingListener.Bind(this, "Command");
	}

	protected override void OnDetaching()
	{
		base.OnDetaching();
		commandBindingListener.UnBind();
		commandBindingListener = null;
	}
}

Have fun looking at those pages getting GCed! Full Source Code Here

If you have read so far then i assume you are interested (hacking IS fun :P) so lets take a look at another way of doing it.
This also has limitations.I’ll get to those after the code.

public class EventToCommandEx : GalaSoft.MvvmLight.Command.EventToCommand
{
	protected override void OnAttached()
	{
		base.OnAttached();
		this.AssociatedObject.Loaded += OnAssociatedObjectLoaded;
		this.AssociatedObject.Unloaded += OnAssociatedObjectUnloaded;
	}

	private ICommand commandBackup;
	protected virtual void OnAssociatedObjectUnloaded(object sender, RoutedEventArgs e)
	{

		if (Command != null)
		{
			commandBackup = this.Command;
			this.Command = null; //Note : We kill the Binding with this.
		}
		else
			commandBackup = null;

	}
	protected virtual void OnAssociatedObjectLoaded(object sender, RoutedEventArgs e)
	{

		if (commandBackup != null)
		{
			//Restore the Command
			this.Command = commandBackup;
		}
		commandBackup = null;

	}
	protected override void OnDetaching()
	{
		base.OnDetaching();
		this.Command = null; //unhook from CanExecuteChanged
		commandBackup = null;
		this.AssociatedObject.Loaded -= OnAssociatedObjectLoaded;
		this.AssociatedObject.Unloaded -= OnAssociatedObjectUnloaded;
	}
}

Ok.So what did we try to do.The idea is that since EventToCommand never unhooks from the event
(Dont be fooled by the Detach method.This is called when the Trigger is removed from the TriggerCollection) a good time to unhook it is probably when the element is removed from the visual tree.Either it was defined in a DataTemplate and the collection binding changed so everything gets refreshed or the user left the Page containing it.Pretty safe.Well not exactly.You have to account for the cases where the same element for some reason gets removed and re-added. That is why, after removing the Command to avoid the leak, we keep a reference to it in case it gets re-added in which case we re-apply.Seems to work nicely but you need to keep these in mind :

  • The Binding gets removed when you set Command = null (same as BindingOperations.ClearBinding() in WPF). If you don’t change the Command from inside the VM ,which i expect you don’t, then it shouldn’t be a problem.
  • You have to know for sure that the element you are attaching to fires Loaded/Unloaded events as expected! I noticed a case with a BindableApplicationBar wrapper that this did not happen.