简体   繁体   English

在.Net 4.0中从.Net 4.5绑定的延迟属性

[英]Delay property on Binding from .Net 4.5 in .Net 4.0

How can I implement Delay property from .Net 4.5 (described here ) on binding in .Net 4.0? 如何在.Net 4.0中实现关于绑定的.Net 4.5( 此处描述)的延迟属性?

I know I cannot inherit from BindingBase as ProvideValue is sealed. 我知道我不能继承BindingBase,因为ProvideValue是密封的。

I could implement MarkupExtension but it means I now have to rewrite all properties from BindingExtension is there any other way? 我可以实现MarkupExtension,但这意味着我现在必须重写BindingExtension中的所有属性还有其他方法吗?

t the end I've decided to implement DelayedBinding as MarkupExtension using composition. 最后,我决定使用合成将DelayedBinding实现为MarkupExtension。

The only problem I had was with DataTemplates ProvideValue should return this if TargetProperty from IProvideValueTarget is null. 如果来自IProvideValueTarget TargetProperty为null,那么我遇到的唯一问题是DataTemplates ProvideValue应返回此问题。

[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();
        }
    }
}

I would create an AttachedProperty that specifies the amount of time to Delay. 我会创建一个AttachedProperty ,指定延迟的时间。 The AttachedProperty would start (or reset) a timer when the bound value changes, and would manually update the bound source when the specified amount of time gets reached. 当绑定值更改时, AttachedProperty将启动(或重置)计时器,并在达到指定的时间量时手动更新绑定的源。

You can use the following to update the source binding: 您可以使用以下命令更新源绑定:

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

Edit 编辑

I was fixing a bug in some old code today and noticed it implemented a delayed property change notification using an Attached Behavior. 我今天正在修复一些旧代码中的错误,并注意到它使用附加行为实现了延迟的属性更改通知。 I thought of this question, so followed the link that I had commented in the code, and found myself at a question I had posted a while ago on SO about delaying a binding . 我想到了这个问题,所以按照我在代码中评论过的链接,发现自己刚才发布了一个关于延迟绑定的问题 The top answer is the one I have implemented currently, which is some attached properties that updates the source of a binding after X milliseconds have passed. 最常见的答案是我当前实现的那个,它是一些附加的属性,它们在X毫秒过去后更新绑定源。

Straightaway porting is not possible but can we "simulate" this using MultiBinding 直接移植是不可能的,但我们可以使用MultiBinding “模拟”这个

Mind you that this is very tightly coupled solution and may not perform well if many of such bindings are used on a page... 请注意,这是非常紧密耦合的解决方案,如果在页面上使用了许多此类绑定,则可能无法正常运行...

Two must haves ... 两个必须有 ......

  1. It accepts the delay in milliseconds in a single item ArrayList as a converter parameter. 它接受单个项ArrayList的延迟(以毫秒为单位)作为转换器参数。
  2. Every such delayed binding must carry its own instance of converter parameter. 每个这样的延迟绑定必须携带自己的转换器参数实例。

The Test XAML is as below... 测试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..."/>

In this example a TextBlock renders of what is typed in TextBox below after a 2 seconds delay. 在此示例中, TextBlock呈现在延迟2秒后在下面的TextBox中键入的内容。 The TextBox.Text is primary source of data. TextBox.Text是主要的数据源。

DelayHelper is multi converter that works as shown below... 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
}

So this code makes use of the facts that 所以这段代码利用了这些事实

  1. MultiBinding can accept the target UI element ( TextBlock ) and its dependency property ( TextBlock.TextProperty ) that itself is multi-bound. MultiBinding可以接受目标UI元素( TextBlock )及其自身多边界的依赖属性( TextBlock.TextProperty )。
  2. Once bound the multi binding cannot alter its properties including the ConveterParameter . 绑定后,多重绑定不能改变其属性,包括ConveterParameter But the converter parameter itself can be a reference object that maintains its reference throughout the binding is active eg ArrayList . 但转换器参数本身可以是一个参考对象,它在整个绑定过程中保持其引用是活动的,例如ArrayList
  3. The DispatcherTimer has to stop after its first Tick . DispatcherTimer必须在第一个Tick后停止。 Hence we use of the temp variable is very essential. 因此我们使用temp变量是非常必要的。
  4. The updates make 2 converter passes for each source text update. 更新为每个源文本更新生成2个转换器通道。 There is no escpae from this behavior. 没有这种行为的escpae。 This may cause slowness is many delayed bindings are used. 这可能导致缓慢使用许多延迟绑定。
  5. Make sure you do not share the same converter parameter among multiple delayed bindings 确保在多个延迟绑定中不共享相同的转换器参数

Let me know if this helps... 如果这有帮助,请告诉我......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM