BindingUpdateBase Behavior

Topics: Feature requests
Jun 17, 2012 at 8:31 PM

Hi Geert,

could you please move the base code from DelayedBindingUpdate into a base class BindingUpdateBase so that i'm able to derive my own for custom binding update behaviors (for example when the cultures etc changes...):

it's easier to show in code what i mean:

here's the base class:

    public class BindingUpdateBase : BehaviorBase<FrameworkElement>
    {
        private static readonly ILog Log = LogManager.GetCurrentClassLogger();

        private DependencyProperty _dependencyPropertyCache;

        private Binding _originalBinding;

        public string DependencyPropertyName { get; set; }

        public string PropertyName { get; set; }

        private string UsedDependencyPropertyName
        {
            get
            {
                DependencyProperty property;
                if (!string.IsNullOrEmpty(this.DependencyPropertyName))
                {
                    property = this.GetDependencyProperty(this.DependencyPropertyName);
                    if (property != null)
                    {
                        return this.DependencyPropertyName;
                    }
                }

                var propertyName = string.Format("{0}Property", this.PropertyName);
                property = this.GetDependencyProperty(propertyName);
                if (property != null)
                {
                    return propertyName;
                }

                return null;
            }
        }

        protected override void OnAssociatedObjectLoaded(object sender, EventArgs e)
        {
            var dependencyProperty = this.GetDependencyProperty();
            var bindingExpression = this.AssociatedObject.GetBindingExpression(dependencyProperty);
            if (bindingExpression == null)
            {
                Log.Error("No binding expression found on '{0}'", new object[] { this.UsedDependencyPropertyName });
                return;
            }

            var binding = bindingExpression.ParentBinding;
            this._originalBinding = binding;
            var newBinding = CreateBindingCopy(binding);
            newBinding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
            this.AssociatedObject.ClearValue(dependencyProperty);
            this.AssociatedObject.SetBinding(dependencyProperty, newBinding);
            Log.Debug(
                "Changed UpdateSourceTrigger from to 'Explicit' for dependency property '{0}'", 
                new object[] { this.UsedDependencyPropertyName });
            this.AssociatedObject.SubscribeToDependencyProperty(this.PropertyName, this.OnDependencyPropertyChanged);
            Log.Debug("Subscribed to property changes of the original object", new object[0]);
        }

        protected override void OnAssociatedObjectUnloaded(object sender, EventArgs e)
        {
            var dependencyProperty = this.GetDependencyProperty();
            this.AssociatedObject.ClearValue(dependencyProperty);
            this.AssociatedObject.SetBinding(dependencyProperty, this._originalBinding);
            Log.Debug(
                "Restored binding for dependency property '{0}'", new object[] { this.UsedDependencyPropertyName });
            this.AssociatedObject.UnsubscribeFromDependencyProperty(this.PropertyName, this.OnDependencyPropertyChanged);
            Log.Debug("Unsubscribed from property changes of the original object", new object[0]);
        }

        protected virtual void OnDependencyPropertyChanged(object sender, DependencyPropertyValueChangedEventArgs e)
        {
        }

        protected void UpdateBinding()
        {
            var dependencyProperty = this.GetDependencyProperty();
            var bindingExpression = this.AssociatedObject.GetBindingExpression(dependencyProperty);
            bindingExpression.UpdateSource();
        }

        protected override void ValidateRequiredProperties()
        {
            Argument.IsNotNullOrWhitespace("PropertyName", this.PropertyName);
            if (this.GetDependencyProperty() == null)
            {
                throw new InvalidOperationException(
                    "Dependency property is not found on the associated object, make sure to set the PropertyName or DependencyPropertyName");
            }
        }

        private static Binding CreateBindingCopy(Binding binding)
        {
            Argument.IsNotNull("binding", binding);
            var newBinding = new Binding();
            var properties = typeof(Binding).GetPropertiesEx(true, false);
            var array = properties;
            for (var i = 0; i < array.Length; i++)
            {
                var property = array[i];
                object propertyValue;
                if (PropertyHelper.TryGetPropertyValue(binding, property.Name, out propertyValue)
                    && propertyValue != null)
                {
                    PropertyHelper.TrySetPropertyValue(newBinding, property.Name, propertyValue);
                }
            }

            return newBinding;
        }

        private DependencyProperty GetDependencyProperty()
        {
            if (this._dependencyPropertyCache != null)
            {
                return this._dependencyPropertyCache;
            }

            DependencyProperty property;
            if (!string.IsNullOrEmpty(this.DependencyPropertyName))
            {
                property = this.GetDependencyProperty(this.DependencyPropertyName);
                if (property != null)
                {
                    return this._dependencyPropertyCache;
                }
            }

            property = this.GetDependencyProperty(string.Format("{0}Property", this.PropertyName));
            if (property != null)
            {
                this._dependencyPropertyCache = property;
                return property;
            }

            return null;
        }

         private DependencyProperty GetDependencyProperty(string dependencyPropertyName)
        {
            DependencyProperty property = null;
            var fieldInfo = this.AssociatedObject.GetType().GetFieldEx(dependencyPropertyName, true, true);
            if (fieldInfo != null)
            {
                property = fieldInfo.GetValue(null) as DependencyProperty;
            }

            if (property == null)
            {
                Log.Error(
                    "Failed to retrieve dependency property '{0}' from object '{1}'",
                    new object[] { dependencyPropertyName, this.AssociatedObject.GetType() });
            }
            else
            {
                Log.Debug(
                    "Retrieved dependency property '{0}' from object '{1}'",
                    new object[] { dependencyPropertyName, this.AssociatedObject.GetType() });
            }

            return property;
        }
    }

here's the code for the DelayedBindingUpdate behavior now derived from the new base class:

   public class DelayBindingUpdate : BindingUpdateBase
    {
        private readonly DispatcherTimer _timer;

        public DelayBindingUpdate()
        {
            this.UpdateDelay = 100;
            this._timer = new DispatcherTimer();
        }

         public int UpdateDelay { get; set; }

        protected override void OnAssociatedObjectLoaded(object sender, EventArgs e)
        {
            base.OnAssociatedObjectLoaded(sender, e);
            this._timer.Tick += this.OnTimerTick;
        }

         protected override void OnAssociatedObjectUnloaded(object sender, EventArgs e)
        {
            base.OnAssociatedObjectUnloaded(sender, e);
            this._timer.Stop();
            this._timer.Tick -= this.OnTimerTick;
        }

         protected override void OnDependencyPropertyChanged(object sender, DependencyPropertyValueChangedEventArgs e)
        {
            if (this.UpdateDelay < 50)
            {
                this.UpdateBinding();
                return;
            }

            if (this.UpdateDelay > 5000)
            {
                this.UpdateDelay = 5000;
            }

            if (this._timer.IsEnabled)
            {
                this._timer.Stop();
            }

            this._timer.Interval = new TimeSpan(0, 0, 0, 0, this.UpdateDelay);
            this._timer.Start();
        }

         private void OnTimerTick(object sender, EventArgs e)
        {
            this._timer.Stop();
            this.UpdateBinding();
        }
    }
Many Thanks!
Coordinator
Jun 18, 2012 at 8:19 AM

Will do, but not for this release, we want 3.2 out, so we will only make "safe" changes from now on. We are currently making the last changes to reflection, then we want RC1 out. So this will be included in 3.3.

Jun 18, 2012 at 12:45 PM

Thanks a lot for including it in 3.3!

Coordinator
Jun 24, 2012 at 1:46 PM

I made it a bit simpler, I hope it still works for you:

public class UpdateBindingBehaviorBase<T> : BehaviorBase<TextBox>
    where T : FrameworkElement
{
    #region Constructors
    /// <summary>
    /// Initializes a new instance of the <see cref="UpdateBindingOnTextChanged"/> class.
    /// </summary>
    /// <param name="dependencyPropertyName">Name of the dependency property.</param>
    /// <exception cref="ArgumentException">The <paramref name="dependencyPropertyName"/> is <c>null</c> or whitespace.</exception>
    public UpdateBindingBehaviorBase(string dependencyPropertyName)
    {
        Argument.IsNotNullOrWhitespace("dependencyPropertyName", dependencyPropertyName);

        DependencyPropertyName = dependencyPropertyName;
    }
    #endregion

    #region Properties
    /// <summary>
    /// Gets the name of the dependency property.
    /// </summary>
    /// <remarks></remarks>
    protected string DependencyPropertyName { get; private set; }

    /// <summary>
    /// Gets the dependency property, which is retrieved at runtime.
    /// <para />
    /// This property can only be used when the associated object is attached.
    /// </summary>
    protected DependencyProperty DependencyProperty { get { return AssociatedObject.GetDependencyPropertyByName(DependencyPropertyName); } }
    #endregion

    #region Methods
    /// <summary>
    ///   Updates the binding value.
    /// </summary>
    protected void UpdateBinding()
    {
        var binding = AssociatedObject.GetBindingExpression(DependencyProperty);
        binding.UpdateSource();
    }
    #endregion
}

Jun 24, 2012 at 11:19 PM

Hi Geert,

your implementation looks smart and i'm looking forward to integrate it!

Btw: Today I integrated the 3.2 RC 1 and everything works fine!

Thanks a lot!

Coordinator
Jun 25, 2012 at 7:19 AM

Great, thanks for letting us know! If you run into any problems, please let us know asap. Then we can fix it for the final release which we hopefully release next weekend.