问题 绑定的延迟属性来自.Net 4.0中的.Net 4.5


如何从.Net 4.5实现Delay属性(描述 这里).Net 4.0中的绑定?

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

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


11233
2017-10-05 12:15


起源

你能不继承BindingBase并创建一个新方法,只需在该方法中调用ProvideValue? - Security Hound
@Ramhound否因为ProvideValue已被密封,因此不会使用新方法。 - baalazamon
这仅适用于特定应用程序还是一般实现。作为一个关闭可以有一个属性调用另一个,只是延迟与Thread.Sleep()?后面的代码延迟,后面的代码调用真正的库。 - paparazzo
@BalamBalam肯定这是最简单的方法。我实际上认为在开始时但是有多个属性,它添加了很多逻辑,几乎没有连接到我的viewModel。 - baalazamon


答案:


我会创建一个 AttachedProperty 指定延迟的时间量。该 AttachedProperty 将在绑定值更改时启动(或重置)计时器,并在达到指定的时间量时手动更新绑定的源。

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

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

编辑 

我今天正在修复一些旧代码中的错误,并注意到它使用附加行为实现了延迟的属性更改通知。我想到了这个问题,所以按照我在代码中评论过的链接,发现自己刚才发布的一个问题,关于SO 延迟约束力。最常见的答案是我当前实现的一个,它是一些附加的属性,它们在X毫秒过去后更新绑定源。


3
2017-10-05 13:32



我认为你需要2个附加属性,一个带延迟,第二个用于识别应该延迟哪个属性绑定。另外,我觉得如果在datatemplate或样式上进行绑定,将很难获得带有附加属性的BindingExpression。 - baalazamon
@baalazamon Hrrm这是真的,我想如果你知道你想延迟什么样的绑定,它才会起作用。你也可以尝试覆盖一个 BindingBase 像Ramhound所说的那样完成同样的事情。我通常只覆盖那些以避免必须重复设置默认值,但我不明白为什么你不能改变绑定行为。 - Rachel
您不能通过继承来修改绑定makupextension行为,因为XAML解析器使用的ProvideValue方法是密封的:( - baalazamon
@baalazamon自定义MarkupExtension而不是绑定怎么样?这是一个例子.. hardcodet.net/2008/04/wpf-custom-binding-class - Rachel
确定它会工作我知道如何做到这一点,但有一个小缺点(我提到这个问题),它将所有属性从Binding添加到我的新扩展。 - baalazamon


最后,我决定使用合成将DelayedBinding实现为MarkupExtension。

我遇到的唯一问题是DataTemplates ProvideValue 应该返回这个if TargetProperty 从 IProvideValueTarget 一片空白。

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

6
2017-10-06 16:00





直接移植是不可能的,但我们可以使用“模拟”这个 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 呈现输入的内容 TextBox 延迟2秒后下面。该 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个转换器通道。没有这种行为的escpae。这可能导致缓慢使用许多延迟绑定。
  5. 确保在多个延迟绑定中不共享相同的转换器参数 

如果这有帮助,请告诉我......


2
2017-10-05 13:41



你能详细说说吗? - baalazamon
不幸的是,据我所知,转换器在UI线程中进行评估,IsAsync不会影响它。 - baalazamon
@baalazamon,请再次看我编辑的回复。 :-) - WPF-it
谢谢!我稍后会尝试,但看起来有点复杂,绑定很难理解。 - baalazamon
我使用IsAsync = true的转换器。也有。 FlowDocument派生自Dispatcher,不适用于Async。我序列化为字符串,然后在转换器中反序列化。 - paparazzo