2010-11-10 12 views
6

Tengo una unión múltiple que se ve algo como esto:WPF - Retraso en MultiBinding

<UserControl.Visibility> 
    <MultiBinding Converter="{StaticResource isMouseOverToVisibiltyConverter}"> 
     <Binding ElementName="otherElement" Path="IsMouseOver" /> 
     <Binding RelativeSource="{RelativeSource Self}" Path="IsMouseOver" /> 
    </MultiBinding> 
</UserControl.Visibility> 

Y, quiero ser capaz de añadir un retardo entre IsMouseOver va a false para fijaciones, así como la visibilidad que se puso a Colapsado

me encontré con esta implementación DelayBinding: http://www.paulstovell.com/wpf-delaybinding

embargo, esto no funciona para MultiBinding, y he sido incapaz de encontrar la manera de hacer que funciona con MultiBinding.

Tengo la opción de hacer los cambios a Visibilidad en eventos en el código subyacente, y eso funcionaría, pero sería bueno si hubiera alguna manera de hacerlo a través del sistema vinculante.

¿Hay alguna manera de agregar un retraso a un enlace múltiple?

EDITAR: Ray, para que tu clase compile & ejecutar, tuve que hacer algunas correcciones. Sin embargo, algo sigue estando mal, ya que las actualizaciones no se propagan. Parece que solo actualiza la propiedad de destino una vez.

[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: A pesar de que no pude obtener el código de Ray a trabajar, he marcado como la respuesta, ya que conducen a mí un código que funciona. Ver mi respuesta a continuación para el código que utilicé.

+0

Esta pregunta me ayudó a resolver mi problema, ¡muchas gracias! – worldpart

Respuesta

6

La clase DelayBinding con la que se vincula solo retrasa una actualización de origen, no una actualización de destino. Para retrasar una actualización objetivo, que es lo que está pidiendo, es mucho más simple. Algo como esto debe hacer el truco:

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; 
} 

Cómo funciona: Una MultiBinding está construida que tiene una extra de unión, a una propiedad ChangeCount en la propia extensión de marcado. Además, la extensión de marcado se registra como el convertidor. Cada vez que un valor fuente cambia, el enlace se evalúa y se llama al convertidor. Esto a su vez llama al convertidor "real" para calcular el valor. En lugar de actualizar el valor inmediatamente, simplemente lo almacena en _undelayedValue y devuelve el valor anterior (_delayedValue). Además, si el valor ha cambiado, inicia (o reinicia) un temporizador. Cuando el temporizador dispara, el valor se copia en _delayedValue y ChangeCount se incrementa, lo que obliga a volver a evaluar el enlace. Esta vez, se devuelve el nuevo valor _delayedValue.

+0

Ver la edición de la pregunta. – Ashley

+0

¡Gracias, esto ayudó mucho! – Ashley

3

Nota Esto solo responde a la pregunta "No he podido averiguar cómo hacer uno que funcione con MultiBinding" explicando cómo hacerlo. Otros pueden encontrar esta información útil, así que la dejaré aquí y agregaré otra respuesta que responda a la pregunta principal.


es bastante trivial para cambiar la extensión de marcado DelayBinding se conectó a una clase en DelayMultiBinding que funciona de la misma manera pero con MultiBinding.

En la extensión de marcado:

  1. Cambiar nombre para DelayMultiBindingExtension
  2. añadir la propiedad Bindings de tipo Collection<BindingBase>
  3. cambiar el tipo de la propiedad Converter
  4. En ProvideValue, construir un DelayMultiBinding en lugar de una DelayBinding, pasando en todas las ataduras.

En la clase de unión de retardo:

  1. Renombrar para DelayMultiBinding
  2. gama Toma de consolidaciones en lugar de solo valor Agregar
  3. unión cambió manipuladores de cada propiedad
  4. Construir el MultiBinding igual usted construyó el Enlace

Ahora inste anuncio de escribir MultiBinding, escribir 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> 

Personalmente también sería limpiarlo mediante la conversión de las dos clases en una sola clase, que es un MarkupExtension y también maneja el temporizador.

Tenga en cuenta que la clase DelayBinding y esta clase de ambas actualizaciones de retardo a la fuente , no cambios a la objetivo. Si desea retrasar las actualizaciones del objetivo (que lo hace), consulte mi otra respuesta.

+0

Esto definitivamente ayuda a apuntarme en la dirección correcta, pero no veo cómo retrasar la llamada a UpdateTarget(). MultiBinding tiene UpdateSourceTrigger para UpdateSource(), pero nada similar para UpdateTarget() AFAIK. – Ashley

+0

Además, no estoy del todo seguro de cómo agregar los manipuladores de valor cambiado para la propiedad enlazada en el origen. – Ashley

+0

Bueno, creo que lo tengo más o menos implementado, pero obtengo una excepción cuando intento llamar a UpdateTarget() en el temporizador. "No se puede realizar esta operación cuando se suelta el enlace". – Ashley

2

Usando el código de Ray como punto de partida, he escrito algún código que funciona, pero no es del todo elegante.

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++; 
    } 
} 
2

que tenían un requisito similar en un proyecto hace un tiempo por lo que creó dos extensiones de marcado llamados DelayBindingExtension y DelayMultiBindingExtension.

Ellos funcionan como normal Bindings con la particularidad de que se puede especificar UpdateSourceDelay y/o UpdateTargetDelay, ambos de los cuales son TimeSpan propiedades. En su caso se podría utilizar de esta manera

<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> 

El código fuente y ejemplos de uso de DelayBinding y DelayMultiBinding puede ser descargado here.
Si está interesado en los detalles de implementación, puede consultar la publicación de mi blog al respecto aquí: DelayBinding and DelayMultiBinding with Source and Target delay