0

In general, Bindings are a very handy way of connecting data in C# with its visual representation in Xaml. When the data changes, the Visual Representation automatically updates as well. However, in a case where you have thousands of bindings, and most of them are on pages that aren't on the screen right now, it seems silly and expensive to keep all the hidden visual representations up to date. But as you can see if you run the example below, that is exactly what a binding does by default! Even when I remove CountingBlock from the screen, it keeps on calling my BreakpointConverter every time the Counter updates.

To me, it makes more sense to stop updating while Unloaded, and then as part of the LoadedEvent, reactivate all my bindings, checking for what the source has been updated to in the meantime. Is there a way to accomplish this?

Thanks,

The Xaml:

<Window x:Class="DeactivateBindings.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="450" Width="800">
  <UniformGrid>
    <ContentControl Name="Container"/>
    <Button Content="Show or Hide" Click="ShowOrHide_Click"/>
  </UniformGrid>
</Window>

The Converter:

class BreakpointConverter : System.Windows.Data.IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
         return value; // does nothing, but you can set a breakpoint here to see if the binding is active.
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}

The Code Behind:

public partial class MainWindow : Window
{
    bool _isCounterVisible;
    Label CountingBlock = new Label { FontSize = 25 };

    public MainWindow()
    {
        InitializeComponent();
        ContingBlock.SetBinding(Label.ContentProperty, new Binding
        {
            Source = this,
            Path = new PropertyPath(nameof(Counter)),
            // this Converter is the key component that tells us the binding is still active, because we can breakpoint it.
            Converter = new BreakpointConverter(), 
        });

        StartCounting();
    }

    private async void StartCounting()
    {
        while(true)
        {
             Counter++;
             await Task.Delay(500);
        }
    }

    private void ShowOrHide_Click(object sender, RoutedEventArgs e)
    {
        _isCounterVisible = !_isCounterVisible;

        Countainer.Content = _isCounterVisible ? CountingBlock : null;
    }

    public int Counter
    {
         get { return (int)GetValue(CounterProperty); }
         set {SetValue(CounterProperty, value); }
    }
    public static readonly DependencyProperty CounterProperty = 
        DependencyProperty.Register(nameof(Counter), typeof(int), typeof(MainWindow), new PropertyMetadata(0));
}
bwall
  • 984
  • 8
  • 22

1 Answers1

0

One option is to change all the bindings to explicit when you leave the page, then change them back later. Explicit means the binding will only update when you tell it to, rather than any time the backing property changes. When the UIElement is reloaded, you can Reactivate all the bindings again.

// this will allow us to keep track of the real UpdateSourceTriggers for our bindings
private Dictionary<BindingExpression, UpdateSourceTrigger> _deactivatedBindings = new Dictionary<BindingExpression, UpdateSourceTrigger>();

public static void OnUnloaded(object sender, RoutedEventArgs e)
{
    foreach(BindingExpreesion expression in GetBindingPaths(sender as DependencyObject))
        expression.DeactivateBindings();
}

public static void OnLoaded(object sender, RoutedEventArgs e)
{
    foreach(BindingExpreesion expression in GetBindingPaths(sender as DependencyObject))
        expression.ReactivateBindings();
}

public static asynce Task DeactivateBindings(this BindingExpression oldExpression)
{
    // you can't modify bindings once they've been used, so we'll copy the old one, modify it, and use that one instead
    Binding duplicateBinding = oldExpression.ParentBinding.Clone();

    // this means that the binding won't update when the property changes, just when we tell it to.
    duplicateBinding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;

    // here we save the 'real' value - the one we use when the element is loaded.
    _deactivatedBindings[duplicateBinding] = oldExpression.ParentBinding.UpdateSourceTrigger;

    BindingOperations.SetBinding(oldExpression.Target, oldExpression.TargetProperty, duplicateBinding);
}

public static async Task ReactivateBindings(this BindingExpression oldExpression)
{
    if(_deactivatedBindings.TryGetValue(oldExpression.ParentBinding, out UpdateSourceTrigger realTrigger))
    {
         _deactivatedBindings.Remove(oldExpression.ParentBinding);
         Binding duplicateBinding = oldExpression.ParentBinding.Clone();
         duplicateBinding.UpdateSourceTrigger = realTrigger;
         // This has the added advantage of refreshing the value so it's up to date. :)
         BindingOperations.SetBinding(oldExpression.Target, oldExpression.TargetProperty, duplicateBinding);
    }
}

Clone() Method

GetBindingPaths() Method - just save the BindingExpressions instead of the paths.

bwall
  • 984
  • 8
  • 22