我有一个看起来像这样的多绑定:
<UserControl.Visibility>
<MultiBinding Converter="{StaticResource isMouseOverToVisibiltyConverter}">
<Binding ElementName="otherElement" Path="IsMouseOver" />
<Binding RelativeSource="{RelativeSource Self}" Path="IsMouseOver" />
</MultiBinding>
</UserControl.Visibility>
而且,我希望能够在两个绑定的IsMouseOver之间添加一个延迟,并将Visibility设置为Collapsed。
我发现了这个DelayBinding实现: http://www.paulstovell.com/wpf-delaybinding
但是,这对MultiBinding不起作用,而且我一直无法弄清楚如何使用MultiBinding。
我可以选择在代码隐藏中的事件中对Visibility进行更改,这样就行了,但是如果通过绑定系统有某种方法可以做到这一点会很好。
有没有办法为MultiBinding添加延迟?
编辑:雷,为了让你的课程编译&amp;跑,我不得不做一些修复。但是,由于更新未被传播,因此仍然存在问题。它似乎只更新了一次目标属性。
[ContentProperty("Bindings")]
public class DelayedMultiBindingExtension : MarkupExtension, IMultiValueConverter, INotifyPropertyChanged
{
public Collection<BindingBase> Bindings { get; private set; }
public IMultiValueConverter Converter { get; set; }
public object ConverterParameter { get; set; }
public CultureInfo ConverterCulture { get; set; }
public BindingMode Mode { get; set; }
public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
public object CurrentValue { get { return _delayedValue; } set { _delayedValue = _undelayedValue = value; _timer.Stop(); } }
private object _undelayedValue;
private object _delayedValue;
private DispatcherTimer _timer;
public int ChangeCount { get; private set; } // Public so Binding can bind to it
public DelayedMultiBindingExtension()
{
this.Bindings = new Collection<BindingBase>();
_timer = new DispatcherTimer();
_timer.Tick += _timer_Tick;
_timer.Interval = TimeSpan.FromMilliseconds(500);
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (valueProvider != null)
{
var bindingTarget = valueProvider.TargetObject as DependencyObject;
var bindingProperty = valueProvider.TargetProperty as DependencyProperty;
var multi = new MultiBinding { Converter = this, Mode = Mode, UpdateSourceTrigger = UpdateSourceTrigger };
foreach (var binding in Bindings)
multi.Bindings.Add(binding);
multi.Bindings.Add(new Binding("ChangeCount") { Source = this, Mode = BindingMode.OneWay });
var bindingExpression = BindingOperations.SetBinding(bindingTarget, bindingProperty, multi);
return bindingTarget.GetValue(bindingProperty);
}
return null;
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
object newValue =
Converter.Convert(
values.Take(values.Length - 1).ToArray(),
targetType,
ConverterParameter,
ConverterCulture ?? culture);
if (!object.Equals(newValue, _undelayedValue))
{
_undelayedValue = newValue;
_timer.Stop();
_timer.Start();
}
return _delayedValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return
Converter.ConvertBack(value, targetTypes, ConverterParameter, ConverterCulture ?? culture)
.Concat(new object[] { ChangeCount }).ToArray();
}
private void _timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
_delayedValue = _undelayedValue;
ChangeCount++;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ChangeCount"));
}
public event PropertyChangedEventHandler PropertyChanged;
}
EDIT2:虽然我无法让Ray的代码工作,但我已将其标记为答案,因为它会导致我使用一些可行的代码。请参阅下面的答案,了解我使用的代码。
答案 0 :(得分:6)
您链接的DelayBinding类只会延迟源更新,而不会延迟目标更新。延迟目标更新(这是您要求的)要简单得多。这样的事情可以解决问题:
public class DelayedMultiBindingExtension : MarkupExtension, IMultiValueConverter, INotifyPropertyChanged
{
public Collection<Binding> Bindings { get; set; }
public IMultiValueConverter Converter { get; set; }
public object ConverterParameter { get; set; }
public CultureInfo ConverterCulture { get; set; }
public BindingMode Mode { get; set; }
public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
public object CurrentValue { get { return _delayedValue; } set { _delayedValue = _undelayedValue = value; _timer.Stop(); } }
object _undelayedValue;
object _delayedValue;
DispatcherTimer _timer;
public int ChangeCount { get; set; } // Public so Binding can bind to it
public DelayedMultiBindingExtension()
{
_timer = new DispatcherTimer();
_timer.Tick += _timer_Tick;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var multi = new MultiBinding { Converter = this, Mode = Mode, UpdateSourceTrigger = UpdateSourceTrigger };
foreach(var binding in Bindings)
multi.Bindings.Add(binding);
multi.Bindings.Add(new Binding("ChangeCount") { Source = this });
return multi;
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
object newValue =
Converter.Convert(
values.Take(values.Length-1).ToArray(),
targetType,
ConverterParameter,
ConverterCulture ?? culture);
if(!object.Equals(newValue, _undelayedValue))
{
_undelayedValue = newValue;
_timer.Stop();
_timer.Start();
}
return _delayedValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return
Converter.ConvertBack(value, targetTypes, ConverterParameter, ConverterCulture ?? culture)
.Concat(new object[] { ChangeCount }).ToArray();
}
void _timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
_delayedValue = _undelayedValue;
ChangeCount++;
if(PropertyChanged!=null)
PropertyChanged(this, new PropertyChangedEventArgs("ChangeCount"));
}
public event PropertyChangedEventHandler PropertyChanged;
}
工作原理:构造一个MultiBinding,它对标记扩展本身的ChangeCount属性有一个额外的绑定。标记扩展本身也会注册为转换器。每当源值更改时,绑定计算并调用转换器。这反过来调用“真实”转换器来计算值。不是立即更新值,而是将其存储在_undelayedValue中,并返回先前的值(_delayedValue)。此外,如果值已更改,则启动(或重新启动)计时器。当计时器触发时,该值被复制到_delayedValue中并且ChangeCount递增,从而强制重新评估绑定。这次返回新的_delayedValue。
答案 1 :(得分:3)
注意这只回答了“我一直无法弄清楚如何制作一个与MultiBinding配合使用”的部分问题,并解释了如何解决问题。其他人可能会觉得这些信息很有用,所以我会把它留在这里并添加另一个回答主要问题的答案。
将链接到的DelayBinding标记扩展更改为与MultiBinding工作方式相同的DelayMultiBinding类非常简单。
在标记扩展名中:
Bindings
Collection<BindingBase>
属性
Converter
属性ProvideValue
中,构建一个DelayMultiBinding
而不是DelayBinding
,传入所有绑定。在延迟绑定类中:
现在不写MultiBinding
,而是写DelayMultiBindingExtension
:
<UserControl.Visibility>
<my:DelayMultiBindingExtension Delay="0:0:1" Converter="{StaticResource isMouseOverToVisibiltyConverter}">
<Binding ElementName="otherElement" Path="IsMouseOver" />
<Binding RelativeSource="{RelativeSource Self}" Path="IsMouseOver" />
</my:DelayMultiBindingExtension>
</UserControl.Visibility>
我个人也会通过将两个类转换为一个MarkupExtension类来处理计时器来清理它。
请注意,DelayBinding类和此类都会延迟对源的更新,而不是对目标的更新。如果您想延迟目标的更新(您这样做),请参阅我的其他答案。
答案 2 :(得分:2)
以Ray的代码为出发点,我写了一些有用的代码,但并不完全优雅。
XAML:
<local:IsMouseOverToVisibilityConverter x:Key="isMouseOverToVisibiltyConverter" />
<local:DelayingMultiConverter x:Key="delayedIsMouseOverToVisibiltyConverter" Delay="00:00:00.500" Converter="{StaticResource isMouseOverToVisibiltyConverter}" />
...
<UserControl.Visibility>
<MultiBinding Converter="{StaticResource delayedIsMouseOverToVisibiltyConverter}">
<Binding ElementName="otherElement" Path="IsMouseOver" />
<Binding RelativeSource="{RelativeSource Self}" Path="IsMouseOver" />
<Binding Source="{StaticResource delayedIsMouseOverToVisibiltyConverter}" Path="ChangeCount" />
</MultiBinding>
</UserControl.Visibility>
DelayingMultiConverter:
internal class DelayingMultiConverter : IMultiValueConverter, INotifyPropertyChanged
{
private object undelayedValue;
private object delayedValue;
private DispatcherTimer timer;
private int changeCount;
public int ChangeCount
{
get { return this.changeCount; }
private set
{
this.changeCount = value;
this.NotifyPropertyChanged("ChangeCount");
}
}
public IMultiValueConverter Converter { get; set; }
public CultureInfo ConverterCulture { get; set; }
public object ConverterParameter { get; set; }
public TimeSpan Delay
{
get { return this.timer.Interval; }
set { this.timer.Interval = value; }
}
public DelayingMultiConverter()
{
this.timer = new DispatcherTimer();
this.timer.Tick += Timer_Tick;
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
object newValue =
Converter.Convert(
values.Take(values.Length - 1).ToArray(),
targetType,
ConverterParameter,
ConverterCulture ?? culture);
if (!object.Equals(newValue, undelayedValue))
{
undelayedValue = newValue;
timer.Stop();
timer.Start();
}
return delayedValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return
Converter.ConvertBack(value, targetTypes, ConverterParameter, ConverterCulture ?? culture)
.Concat(new object[] { ChangeCount }).ToArray();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private void Timer_Tick(object sender, EventArgs e)
{
timer.Stop();
delayedValue = undelayedValue;
ChangeCount++;
}
}
答案 3 :(得分:2)
我在一个项目中有类似的要求,所以我创建了两个名为DelayBindingExtension
和DelayMultiBindingExtension
的标记扩展。
它们的工作方式与普通Bindings
相同,但您可以指定UpdateSourceDelay
和/或UpdateTargetDelay
,这两者都是TimeSpan
属性。在你的情况下,你可以像这样使用它
<UserControl.Visibility>
<db:DelayMultiBinding Converter="{StaticResource yourConverter}"
UpdateTargetDelay="00:00:01">
<Binding ElementName="otherElement" Path="IsMouseOver" />
<Binding RelativeSource="{RelativeSource Self}" Path="IsMouseOver" />
</db:DelayMultiBinding>
</UserControl.Visibility>
DelayBinding
和DelayMultiBinding
的源代码和示例用法可以下载here。
如果您对实施细节感兴趣,可以在此处查看我的博客文章:DelayBinding and DelayMultiBinding with Source and Target delay