从.net 4.5到.net 4.0的绑定延迟属性
本文关键字:net 绑定 延迟 属性 | 更新日期: 2023-09-27 18:10:25
如何在。net 4.0的绑定上实现。net 4.5的延迟属性(在这里描述)?
我知道我不能继承BindingBase作为ProvideValue是密封的。
我可以实现MarkupExtension,但这意味着我现在必须重写BindingExtension的所有属性,还有其他方法吗?
最后,我决定使用组合实现DelayedBinding作为MarkupExtension。
我唯一的问题是DataTemplates ProvideValue
应该返回这个,如果IProvideValueTarget
的TargetProperty
是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
"模拟"这一点
请注意,这是一个非常紧密耦合的解决方案,如果在一个页面上使用了许多这样的绑定,可能不会执行得很好。
两个必须有…
- 它接受单个项目
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
在延迟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
}
所以这段代码利用了
- MultiBinding可以接受目标UI元素(
TextBlock
)及其依赖属性(TextBlock.TextProperty
)本身是多绑定的。 - 一旦绑定,多重绑定不能改变其属性,包括
ConveterParameter
。但是转换器参数本身可以是一个引用对象,在整个绑定活动期间保持其引用,例如ArrayList
。 -
DispatcherTimer
必须在其第一个Tick
之后停止。因此我们使用temp
变量是非常必要的。 - 更新为每个源文本更新进行2次转换。这种行为是无法逃避的。这可能会导致缓慢,因为许多延迟绑定被使用。
- 确保在多个延迟绑定之间不共享相同的转换器参数