Triggering an action from a binding

Jul 20, 2009 at 6:26 AM

Hi Kent, nice project!

I have a different scenario in mind for Truss, and I was hoping it could be worked into your framework. Basically I'd like to be able to trigger an action when a binding changes. So for example, if I have a ViewModel that needs to refresh it's Commands based on a value in a POCO, I'd like to be able to "bind" to the POCO with a simple API and be able to call a refresh method when the binding updates.

Currently the only way to achieve this is to make a public property in the ViewModel, bind that to the POCO, and trigger the action when the ViewModel property is set. Not the nicest solution. I also tried modifying Truss itself to do this, and I have a simple ActionBinding, but again it works by having a PropertyExpression that calls the action when the setter is called. The problem there is that the Truss framework assumes you're binding from a property to a property.

Is there another way? If not, could I get this added as a feature? I'd be happy to add my semi-hacked ActionBinding as a patch, but I suspect you might want to refactor the framework to not rely on properties :)

Thanks

Cameron MacFarland

Coordinator
Jul 20, 2009 at 5:14 PM

Hi Cameron,

Thanks for the suggestion.

This is quite simple for you to do yourself by extending the BindingBase class as per my example below. Note that my example isn't perfect - you should detach from the event at appropriate points, for example. You're right though - this could be a really nice feature to integrate into Truss, especially with a corresponding fluent interface. I'll certainly keep it in mind and think about whether it's something I want directly in the library or not.

Best,
Kent

using System;
using System.ComponentModel;
using System.Linq.Expressions;
using Kent.Boogaart.Truss;
using Kent.Boogaart.Truss.Primitive;

namespace ConsoleApplication1
{
	class Program : INotifyPropertyChanged
	{
		static void Main(string[] args)
		{
			var program = new Program();

			using (var bindingManager = new BindingManager())
			{
				bindingManager.Bindings.Add(new PropertyWatcher<Program>(program, p => p.TestProperty, o => Console.WriteLine("TestProperty changed to '{0}'", o)));

				program.TestProperty = "Some value";
				program.TestProperty = "Some other value";
			}

			Console.ReadKey();
		}

		private string _testProperty;
		public string TestProperty
		{
			get { return _testProperty; }
			set
			{
				if (_testProperty != value)
				{
					_testProperty = value;
					OnPropertyChanged("TestProperty");
				}
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;

		private void OnPropertyChanged(string propertyName)
		{
			var handler = PropertyChanged;

			if (handler != null)
			{
				handler(this, new PropertyChangedEventArgs(propertyName));
			}
		}
	}

	public class PropertyWatcher<TTarget> : BindingBase
	{
		private Expression<Func<TTarget, object>> _targetExpression;
		private Action<object> _action;

		public PropertyWatcher()
		{
		}

		public PropertyWatcher(TTarget targetObject, Expression<Func<TTarget, object>> targetExpression, Action<object> action)
		{
			TargetObject = targetObject;
			_targetExpression = targetExpression;
			_action = action;
		}

		public Expression<Func<TTarget, object>> TargetExpression
		{
			get { return _targetExpression; }
			set { _targetExpression = value; }
		}

		public Action<object> Action
		{
			get { return _action; }
			set { _action = value; }
		}

		protected override PropertyExpression AttemptCreateTargetPropertyExpression()
		{
			var targetObject = TargetObject;

			if (targetObject == null || _targetExpression == null)
			{
				return null;
			}

			return LambdaPropertyExpression.FromLambdaExpression(targetObject, _targetExpression);
		}

		protected override void OnActivated()
		{
			base.OnActivated();
			TargetPropertyExpression.ValueChanged += TargetPropertyExpression_ValueChanged;
		}

		private void TargetPropertyExpression_ValueChanged(object sender, EventArgs e)
		{
			if (_action != null)
			{
				_action(TargetPropertyExpression.Value);
			}
		}
	}
}

Jul 20, 2009 at 11:55 PM

That's exactly what I did, just instead of calling it PropertyWatcher I called it ActionBinding.

It just felt a bit dirty triggering the action in a method for "setting" the target objects value. Especially when there is no target.

Coordinator
Jul 21, 2009 at 8:20 AM

There is a target: it's the property you're watching. There is no source, at least, not a property-based source. The "source" in this case is the action.

That's going to be the biggest challenge to incorporating this into Truss: the terminology. Some things may not quite fit (BindingBase.Mode comes to mind), so may require refactoring or further thought.

Best,
Kent

Jul 21, 2009 at 9:54 AM

Ah, I was thinking about it the other way around. The source was the object being watched, and the target was the action.