从.net 4.5到.net 4.0的绑定延迟属性

本文关键字:net 绑定 延迟 属性 | 更新日期: 2023-09-27 18:10:25

如何在。net 4.0的绑定上实现。net 4.5的延迟属性(在这里描述)?

我知道我不能继承BindingBase作为ProvideValue是密封的。

我可以实现MarkupExtension,但这意味着我现在必须重写BindingExtension的所有属性,还有其他方法吗?

从.net 4.5到.net 4.0的绑定延迟属性

最后,我决定使用组合实现DelayedBinding作为MarkupExtension。

我唯一的问题是DataTemplates ProvideValue应该返回这个,如果IProvideValueTargetTargetProperty是null。

[MarkupExtensionReturnType(typeof(object))]
public class DelayedBindingExtension : MarkupExtension
{
    private readonly Binding _binding = new Binding();
    public DelayedBindingExtension()
    {
        //Default value for delay
        Delay = TimeSpan.FromSeconds(0.5);
    }
    public DelayedBindingExtension(PropertyPath path)
        : this()
    {
        Path = path;
    }
    #region properties
    [DefaultValue(null)]
    public object AsyncState
    {
        get { return _binding.AsyncState; }
        set { _binding.AsyncState = value; }
    }
    [DefaultValue(false)]
    public bool BindsDirectlyToSource
    {
        get { return _binding.BindsDirectlyToSource; }
        set { _binding.BindsDirectlyToSource = value; }
    }
    [DefaultValue(null)]
    public IValueConverter Converter
    {
        get { return _binding.Converter; }
        set { _binding.Converter = value; }
    }
    [TypeConverter(typeof(CultureInfoIetfLanguageTagConverter)), DefaultValue(null)]
    public CultureInfo ConverterCulture
    {
        get { return _binding.ConverterCulture; }
        set { _binding.ConverterCulture = value; }
    }
    [DefaultValue(null)]
    public object ConverterParameter
    {
        get { return _binding.ConverterParameter; }
        set { _binding.ConverterParameter = value; }
    }
    [DefaultValue(null)]
    public string ElementName
    {
        get { return _binding.ElementName; }
        set { _binding.ElementName = value; }
    }
    [DefaultValue(null)]
    public object FallbackValue
    {
        get { return _binding.FallbackValue; }
        set { _binding.FallbackValue = value; }
    }
    [DefaultValue(false)]
    public bool IsAsync
    {
        get { return _binding.IsAsync; }
        set { _binding.IsAsync = value; }
    }
    [DefaultValue(BindingMode.Default)]
    public BindingMode Mode
    {
        get { return _binding.Mode; }
        set { _binding.Mode = value; }
    }
    [DefaultValue(false)]
    public bool NotifyOnSourceUpdated
    {
        get { return _binding.NotifyOnSourceUpdated; }
        set { _binding.NotifyOnSourceUpdated = value; }
    }
    [DefaultValue(false)]
    public bool NotifyOnTargetUpdated
    {
        get { return _binding.NotifyOnTargetUpdated; }
        set { _binding.NotifyOnTargetUpdated = value; }
    }
    [DefaultValue(false)]
    public bool NotifyOnValidationError
    {
        get { return _binding.NotifyOnValidationError; }
        set { _binding.NotifyOnValidationError = value; }
    }
    [DefaultValue(null)]
    public PropertyPath Path
    {
        get { return _binding.Path; }
        set { _binding.Path = value; }
    }
    [DefaultValue(null)]
    public RelativeSource RelativeSource
    {
        get { return _binding.RelativeSource; }
        set { _binding.RelativeSource = value; }
    }
    [DefaultValue(null)]
    public object Source
    {
        get { return _binding.Source; }
        set { _binding.Source = value; }
    }
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public UpdateSourceExceptionFilterCallback UpdateSourceExceptionFilter
    {
        get { return _binding.UpdateSourceExceptionFilter; }
        set { _binding.UpdateSourceExceptionFilter = value; }
    }
    [DefaultValue(UpdateSourceTrigger.Default)]
    public UpdateSourceTrigger UpdateSourceTrigger
    {
        get { return _binding.UpdateSourceTrigger; }
        set { _binding.UpdateSourceTrigger = value; }
    }
    [DefaultValue(null)]
    public object TargetNullValue
    {
        get { return _binding.TargetNullValue; }
        set { _binding.TargetNullValue = value; }
    }
    [DefaultValue(null)]
    public string StringFormat
    {
        get { return _binding.StringFormat; }
        set { _binding.StringFormat = value; }
    }
    [DefaultValue(false)]
    public bool ValidatesOnDataErrors
    {
        get { return _binding.ValidatesOnDataErrors; }
        set { _binding.ValidatesOnDataErrors = value; }
    }
    [DefaultValue(false)]
    public bool ValidatesOnExceptions
    {
        get { return _binding.ValidatesOnExceptions; }
        set { _binding.ValidatesOnExceptions = value; }
    }
    [DefaultValue(null)]
    public string XPath
    {
        get { return _binding.XPath; }
        set { _binding.XPath = value; }
    }
    [DefaultValue(null)]
    public Collection<ValidationRule> ValidationRules
    {
        get { return _binding.ValidationRules; }
    }
    #endregion
    [DefaultValue(null)]
    public TimeSpan Delay { get; set; }
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        try
        {
            _binding.Mode = BindingMode.TwoWay;
            _binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
        }
        catch (InvalidOperationException)  //Binding in use already don't change it
        {
        }
        var valueProvider = serviceProvider.GetService(typeof (IProvideValueTarget)) as IProvideValueTarget;
        if (valueProvider != null)
        {
            var bindingTarget = valueProvider.TargetObject as DependencyObject;
            var bindingProperty = valueProvider.TargetProperty as DependencyProperty;
            if (bindingProperty != null && bindingTarget != null)
            {
                var result = (BindingExpression)_binding.ProvideValue(serviceProvider);
                new DelayBindingManager(result, bindingTarget, bindingProperty, Delay);
                return result;
            }
        }
        return this;
    }
    private class DelayBindingManager
    {
        private readonly BindingExpressionBase _bindingExpression;
        private readonly DependencyProperty _bindingTargetProperty;
        private DependencyPropertyDescriptor _descriptor;
        private readonly DispatcherTimer _timer;
        public DelayBindingManager(BindingExpressionBase bindingExpression, DependencyObject bindingTarget, DependencyProperty bindingTargetProperty, TimeSpan delay)
        {
            _bindingExpression = bindingExpression;
            _bindingTargetProperty = bindingTargetProperty;
            _descriptor = DependencyPropertyDescriptor.FromProperty(_bindingTargetProperty, bindingTarget.GetType());
            if (_descriptor != null)
                _descriptor.AddValueChanged(bindingTarget, BindingTargetTargetPropertyChanged);
            _timer = new DispatcherTimer();
            _timer.Tick += TimerTick;
            _timer.Interval = delay;
        }
        private void BindingTargetTargetPropertyChanged(object sender, EventArgs e)
        {
            var source = (DependencyObject)sender;
            if (!BindingOperations.IsDataBound(source, _bindingTargetProperty))
            {
                if (_descriptor != null)
                {
                    _descriptor.RemoveValueChanged(source, BindingTargetTargetPropertyChanged);
                    _descriptor = null;
                }
                return;
            }
            _timer.Stop();
            _timer.Start();
        }
        private void TimerTick(object sender, EventArgs e)
        {
            _timer.Stop();
            _bindingExpression.UpdateSource();
        }
    }
}

我将创建一个AttachedProperty来指定要延迟的时间量。当绑定值发生变化时,AttachedProperty将启动(或重置)计时器,并在达到指定的时间量时手动更新绑定源。

您可以使用以下命令来更新源绑定:

BindingOperations.GetBindingExpressionBase(
    dependencyObject, dependencyProperty).UpdateSource();

编辑

我今天正在修复一些旧代码中的错误,并注意到它使用附加行为实现了延迟的属性更改通知。我想到了这个问题,所以跟随我在代码中评论的链接,发现自己在我不久前在so上发布的一个关于延迟绑定的问题。上面的答案是我目前已经实现的,它是一些附加的属性,在X毫秒后更新绑定的源。

直接移植是不可能的,但我们可以使用MultiBinding"模拟"这一点

请注意,这是一个非常紧密耦合的解决方案,如果在一个页面上使用了许多这样的绑定,可能不会执行得很好。

两个必须有

  1. 它接受单个项目ArrayList中以毫秒为单位的延迟作为转换器参数。
  2. 每个这样的延迟绑定必须携带自己的转换器参数实例。

测试XAML如下…

    <TextBlock xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib"
               xmlns:System="clr-namespace:System;assembly=mscorlib" >
        <TextBlock.Resources>
            <local:DelayHelper x:Key="DelayHelper"/>
            <Collections:ArrayList x:Key="MultiConverterParameter">
                <System:Int32>2000</System:Int32>
            </Collections:ArrayList>
        </TextBlock.Resources>
        <TextBlock.Text>
            <MultiBinding UpdateSourceTrigger="LostFocus"
                 Converter="{StaticResource DelayHelper}"
                 ConverterParameter="{StaticResource MultiConverterParameter}">
                <Binding Path="Text" ElementName="MyTextBox" Mode="OneWay" />
                <Binding RelativeSource="{RelativeSource Self}"/>                    
                <Binding BindsDirectlyToSource="True"
                         Source="{x:Static TextBlock.TextProperty}"/>
            </MultiBinding>
        </TextBlock.Text>
    </TextBlock>
    <TextBox x:Name="MyTextBox" Text="Test..."/>

在这个例子中,TextBlock在延迟2秒后呈现TextBox中输入的内容。TextBox.Text是数据的主要来源。

DelayHelper是多转换器,工作原理如下所示…

public class DelayHelper : IMultiValueConverter
{
    #region IMultiValueConverter Members
    public object Convert(
         object[] values,
         Type targetType,
         object parameter,
         System.Globalization.CultureInfo culture)
    {
        var sourceElement = values[1] as FrameworkElement;
        var dp = values[2] as DependencyProperty;
        var paramArray = parameter as ArrayList;
        var existingValue
                = paramArray != null && paramArray.Count == 2
                      ? paramArray[1] : sourceElement.GetValue(dp);
        var newValue = values[0];
        var bndExp = BindingOperations.GetMultiBindingExpression(sourceElement, dp);
        var temp = new DispatcherTimer() { IsEnabled = false };
        var dspTimer
            = new DispatcherTimer(
                new TimeSpan(0,0,0,0, int.Parse(paramArray[0].ToString())),
                DispatcherPriority.Background,
                new EventHandler(
                    delegate
                    {
                        if (bndExp != null && existingValue != newValue)
                        {
                            var array
                                 = bndExp.ParentMultiBinding.ConverterParameter
                                     as ArrayList;
                            var existingInterval = array[0];
                            array.Clear();
                            array.Add(existingInterval);
                            array.Add(newValue);
                            bndExp.UpdateTarget();
                        }
                        temp.Stop();
                    }),
                sourceElement.Dispatcher);
        temp = dspTimer;
        dspTimer.Start();
        return existingValue;
    }
    public object[] ConvertBack(
         object value,
         Type[] targetTypes,
         object parameter,
         System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
    #endregion
}

所以这段代码利用了

  1. MultiBinding可以接受目标UI元素(TextBlock)及其依赖属性(TextBlock.TextProperty)本身是多绑定的。
  2. 一旦绑定,多重绑定不能改变其属性,包括ConveterParameter。但是转换器参数本身可以是一个引用对象,在整个绑定活动期间保持其引用,例如ArrayList
  3. DispatcherTimer必须在其第一个Tick之后停止。因此我们使用temp变量是非常必要的。
  4. 更新为每个源文本更新进行2次转换。这种行为是无法逃避的。这可能会导致缓慢,因为许多延迟绑定被使用。
  5. 确保在多个延迟绑定之间不共享相同的转换器参数