Задержка свойства при привязке с .Net 4.5 в .Net 4.0
Как я могу реализовать свойство Delay из .Net 4.5 (описано здесь) о привязке в .Net 4.0?
Я знаю, что не могу наследовать от BindingBase, поскольку ProvideValue запечатан.
Я могу реализовать MarkupExtension, но это означает, что теперь мне нужно переписать все свойства из BindingExtension, есть ли другой способ?
Ответы
Ответ 1
Я бы создал AttachedProperty
, который задает время Delay. AttachedProperty
запускает (или reset) таймер при изменении значения привязки и вручную обновляет связанный источник, когда будет достигнуто указанное количество времени.
Вы можете использовать следующее для обновления привязки источника:
BindingOperations.GetBindingExpressionBase(
dependencyObject, dependencyProperty).UpdateSource();
Изменить
Я исправлял ошибку в каком-то старом кодексе сегодня и заметил, что он реализовал уведомление об изменении с задержкой с использованием Attached Behavior. Я подумал об этом вопросе, так что следил за ссылкой, которую я прокомментировал в коде, и оказался на вопросе, который я недавно опубликовал в SO о отсрочке привязки, Главный ответ - тот, который я реализовал в настоящее время, который является некоторыми прикрепленными свойствами, которые обновляют источник привязки после прохода X миллисекунд.
Ответ 2
В конце я решил реализовать DelayedBinding как MarkupExtension, используя композицию.
Единственная проблема, с которой я столкнулся, это DataTemplates ProvideValue
, должен вернуть это, если TargetProperty
из IProvideValueTarget
имеет значение 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();
}
}
}
Ответ 3
Прямое портирование невозможно, но мы можем "имитировать" это с помощью 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..."/>
В этом примере a 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
}
Таким образом, этот код использует факты, что
- MultiBinding может принимать целевой элемент пользовательского интерфейса (
TextBlock
) и его свойство зависимостей (TextBlock.TextProperty
), которое само мультисвязывается.
- После привязки привязка не может изменять свои свойства, включая
ConveterParameter
. Но сам параметр преобразователя может быть ссылочным объектом, который поддерживает свою ссылку во время привязки, активен, например. ArrayList
.
-
DispatcherTimer
должен остановиться после первого Tick
. Следовательно, использование переменной temp
очень важно.
- Обновления делают 2 конверта для каждого обновления исходного текста. От этого поведения нет никаких препятствий. Это может привести к замедлению, так как используется много замедленных привязок.
- Убедитесь, что вы не используете один и тот же параметр преобразователя из нескольких задержек привязки
Сообщите мне, если это поможет...