Search code examples
c#wpfdata-binding

WPF IValueConverter changes property: how to force update?


In a MVVM model, my view uses a value converter. This value converter has a property X that influences the converted value. This property X might change

Whenever X changes, all values that were converted using this value converter need to be updated. Of course My ViewModel does not know that my Views use this converter, so It can't notify PropertyChanged. Besides I think it is not neat to let the ViewModel know in what format values are converted.

My value converter does not know for which values it is used. Luckily my XAML and its code behind class do.

So, my view has two converters as resources, and two text blocks that use these resources:

MyView.XAML:

<UserControl.Resources>
    <convert:FormattedStringConverter x:Key="SelectedHistoryConverter" />
    <vm:TimeFrameConverter x:Key="TimeFrameConverter"/>
</UserControl.Resources>

...

<TextBlock Height="20" Name="HistoryTime"
           Text="{vm:CultureInfoBinding Path=SelectedHistoryTime,
           Converter= {StaticResource SelectedHistoryConverter},
           ConverterParameter='History Time: {0:G}'}"/>

<TextBlock Height="20" Name="Timeframe"
           Text="{vm:CultureInfoBinding Path=Timeframe,
           Converter= {StaticResource TimeFrameConverter},
           ConverterParameter='Time Frame: [{0:G}, {1:G}]'}"/>

Event handler in MyView.XAML.cs

private void OnMyParameterChanged(object sender, EventArgs e)
{
    UpdateConverterParameters(); // updates the changed property in the converters
    UpdateTargets();             // forces an Update of all Targets that use these converters
}

In UpdateTargets I need to tell the two TextBlocks to update their values, which will use the changed converters.

For this I used the accepted answer in StackOverflow: How to force a WPF binding to refresh?

public void UpdateTargets()
{
    BindingExpression historyTimeExpression = HistoryTime.GetBindingExpression(TextBlock.TextProperty);
    historyTimeExpression.UpdateTarget();

    BindingExpression timeframeExpression = Timeframe.GetBindingExpression(TextBlock.TextProperty);
    timeframeExpression .UpdateTarget();
}

This works fine. However this means that whenever I add an element in XAML that uses this binding I'll have to add this element to UpdateTargets.

Is there a way for a class Derived from Binding to know which Targets are bound to it?


Solution

  • Whenever X changes, all values that were converted using this value converter need to be updated.

    In this situation, instead of a converter, perhaps have the VM provide the computed values via on demand properties which the controls will subsequently bind to.


    So to somewhat borrow from your example I have three properties, two are computed (which do the job of the converter(s)) and one is the existing one which the computed properties utilize.

    Computed

    public string HistoryTime { get { return SelectedHistoryTime.AddDays(-2).ToShortDate(); } }
    public string Timeframe { 
          get 
          {
             return $"Time Frame: [{SelectedHistoryTime.AddDays(-14)}, {SelectedHistoryTime.AddDays(+14).ToShortDate()}]"; 
          }
     }
    

    Existing Notifies All

    Then whenever all corresponding properties which HistoryTime and TimeFrame use, in their setters notify a change such as

    public DateTime SelectedHistoryTime 
    { 
        get { return _SelectedHistoryTime; }
        set
           {
             _SelectedHistoryTime = value;
             NotifyChange("SelectedHistoryTime");
    
             // These rely on this property, so they change too
             NotifyChange("HistoryTime");
             NotifyChange("Timeframe");
           }
    }