Tag Archives: Reactive Rx

DXGrid, Immediate Updates & Nested Attached Behaviours

With the Devexpress grid control, when you edit a row, it doesn’t update the view model until the row loses focus. I needed immediate update so that my view model would get notified when a checkbox in the grid was toggled as soon as it happened. So I wrote an attached behaviour to facilitate this without having to resort to code behind.

This example is also interesting as it shows how to maintain extra state on a control, using a second attached behaviour managed internally by the main one. In this case, this is necessary to manage the lifetime of the subscription to the controls events. I couldn’t find any info about this technique on the net, so not sure how well known it is.

The code is fairly well commented so should be self explanatory. You’ll also notice that the events are being converted to IObservable with Reactive extensions. I love the functional approach of reactive and the way that reactive returns an IDisposable makes the lifetime management really easy.

n.b. Since this class is dependent on Reactive Extensions, your references will need to look something like this:

image

You can grab Reactive (Rx) from nuget.

using System;
using System.Windows;
using DevExpress.Xpf.Grid;
using System.Reactive.Linq;
using System.Reactive;

namespace Utils.DevExpress
{
	/// <summary>
	/// Attached behaviour for Dev Express GridView's that forces the grid to immediately update the bound
	/// datasource row when values are changed in it. By default it will only update when the row loses
	/// focus.
	/// 
	/// Usage in xaml:
	/// 1. Add Reference
	///    xmlns:dxu="clr-namespace:Utils.DevExpress;assembly=Utils.DevExpress"
	/// 
	/// 2. Attach to GridView
	///    {dxg:TableView ShowGroupPanel="False"  MultiSelectMode="Row"
	///                   dxu:DXGridViewUpdateBehaviour.ImmediateUpdate="True"       
	///                   AllowBestFit="True" AutoWidth="True"/}
	///    {/dxg:GridControl.View}
	/// 
	/// Derived from DevExpress recommended work around which was based on code behind:
	///    http://www.devexpress.com/Support/Center/p/E2832.aspx
	/// 
	/// Author : Daniel Harman
	/// Date   : 07.09.2011
	/// </summary>

	public class DXGridViewUpdateBehaviour : DependencyObject
	{

		#region Immediate Update Attached Property

		public static bool GetImmediateUpdate(DependencyObject obj)
		{
			return (bool)obj.GetValue(ImmediateUpdateProperty);
		}

		public static void SetImmediateUpdate(DependencyObject obj, bool value)
		{
			obj.SetValue(ImmediateUpdateProperty, value);
		}

		/// Immediate update is a boolean attached DP that when set to true, will ensure that grid row updates
		/// are immediately propagated to the view model, rather than only when the row loses focus.
		public static readonly DependencyProperty ImmediateUpdateProperty = DependencyProperty.RegisterAttached(
				"ImmediateUpdate", typeof(bool), typeof(GridViewBase),
				new UIPropertyMetadata(false, OnImmediateUpdatePropertyChanged));

		#endregion

		#region Grid View Update Behaviour Subscription Attached Property

		public static DXGridViewUpdateBehaviourSubscription GetGridViewUpdateBehaviourSubscription(DependencyObject obj)
		{
			return (DXGridViewUpdateBehaviourSubscription)obj.GetValue(GridViewUpdateBehaviourProperty);
		}

		public static void SetGridViewUpdateBehaviourSubscription(
			DependencyObject obj, DXGridViewUpdateBehaviourSubscription value)
		{
			obj.SetValue(GridViewUpdateBehaviourProperty, value);
		}

		/// This property is used to store an instance of the subscription on the control. This instance
		/// contains the rxEventHandler IDisposable so that we can clean up when we want to change the binding
		/// etc.
		public static readonly DependencyProperty GridViewUpdateBehaviourProperty = DependencyProperty.RegisterAttached(
				 "GridViewUpdateBehaviour", typeof(DXGridViewUpdateBehaviourSubscription), typeof(DXGridViewUpdateBehaviour),
				 new UIPropertyMetadata(null));

		#endregion

		/// <summary>
		/// Handle changes to ImmediateUpdate.
		/// </summary>
		/// <param name="d">A child of GridViewBase</param>
		/// <param name="e">true/false for value of attached DP.</param>
		private static void OnImmediateUpdatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var gridViewBase = (GridViewBase)d;
			var oldValue = (bool)e.OldValue;
			var newValue = (bool)e.NewValue;

			if (oldValue == newValue)
				return;

			// Remove old sub if it exists.
			var oldSub = GetGridViewUpdateBehaviourSubscription(d);

			if (oldSub != null)
				oldSub.Dispose();

			// If ImmediateUpdate==true then create new sub.
			if (newValue)
				SetGridViewUpdateBehaviourSubscription(d, new DXGridViewUpdateBehaviourSubscription(gridViewBase));
		}

		/// <summary>
		/// Nested class to manage the lifetime of the reactive event handler. This is attached the GridView
		/// as an attached dependency property.
		/// </summary>
		public class DXGridViewUpdateBehaviourSubscription : IDisposable
		{
			IDisposable rxEventHandler;

			public DXGridViewUpdateBehaviourSubscription(GridViewBase gridViewBase)
			{
				// Create an rx observable of the events and subscribe handler to it.
				rxEventHandler = Observable
						 .FromEventPattern<CellValueChangedEventHandler, CellValueChangedEventArgs>(
								 h => gridViewBase.CellValueChanging += h,
								 h => gridViewBase.CellValueChanging -= h)
						 .Subscribe(OnCellValueChanging);
			}

			/// <summary>
			/// On notification that a cell value is changing, force the TableView to update the view model immediately.
			/// </summary>
			/// <param name="ep"></param>
			void OnCellValueChanging(EventPattern<CellValueChangedEventArgs> ep)
			{
				(ep.Sender as GridViewBase).PostEditor();
			}

			#region IDisposable Members

			public void Dispose()
			{
				rxEventHandler.Dispose();
			}

			#endregion
		}
	}
}