我如何在.Net 4.0中将.Net 4.5中描述的Delay属性(在这里)应用于数据绑定?
我知道我无法从BindingBase继承,因为ProvideValue是sealed的。
我可以实现MarkupExtension,但这意味着我现在必须重写所有来自BindingExtension的属性,还有其他的方法吗?
我如何在.Net 4.0中将.Net 4.5中描述的Delay属性(在这里)应用于数据绑定?
我知道我无法从BindingBase继承,因为ProvideValue是sealed的。
我可以实现MarkupExtension,但这意味着我现在必须重写所有来自BindingExtension的属性,还有其他的方法吗?
[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();
}
}
}
BindingOperations.GetBindingExpressionBase(
dependencyObject, dependencyProperty).UpdateSource();
编辑
今天我在修复一些旧代码中的bug时注意到它使用了附加行为来实现延迟属性更改通知。我想起了这个问题,于是我按照之前在代码中注释的链接,找到了我之前在Stack Overflow上发布的一个关于延迟绑定的问题。目前,我已经实现了最佳答案,即使用一些附加属性,在经过X毫秒后更新绑定源。
BindingBase
来实现相同的效果。我通常只会重写它们以避免反复设置默认值,但我不明白为什么你不能改变绑定行为。 - Rachel直接移植是不可能的,但我们可以使用MultiBinding
来“模拟”这个过程。
请注意,这是一个非常紧密耦合的解决方案,如果在页面上使用了许多这样的绑定,性能可能会受到影响...
有两个必须具备的条件...
ArrayList
项。测试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
}
所以这段代码利用了以下事实:
TextBlock
)及其依赖属性(TextBlock.TextProperty
),该属性本身是多绑定的。ConveterParameter
。但转换器参数本身可以是引用对象,在整个绑定过程中保持其引用,例如 ArrayList
。DispatcherTimer
在第一次 Tick
后必须停止。因此使用 temp
变量非常重要。请告诉我这是否有帮助...