WPF IValueConverter changes property: how to force update? - c#

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?

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

Within the WPF binding system, it is best to allow the system to respond to notifications directly associated with bound values (properties). It is possible to explicitly refresh bindings, as you've found. But that's an error-prone, inefficient way to approach the problem.
As explained here, the best way to do this is to write an IMultiValueConverter instead. This allows for the source properties to be each be bound via INotifyPropertyChanged events, working smoothly with the binding system just like any other properties you might bind. This way, both the main source property and the "property X" will automatically cause the converter to be invoked, updating the target property as needed if either changes.
It is also possible to write a composite property in your view model, as suggested by the other answer. But it's my preference to declare as much of the view-oriented code as possible, rather than putting imperative implementation in the code-behind. This provides the best flexibility, as well as follows the "separation of concerns" philosophy better (i.e. by isolating the "data conversion" aspect in the converter itself, rather than overloading the view model with that logic).
There are numerous examples of how to implement IMultiValueConverter, here on Stack Overflow, on Microsoft's documentation web site, and of course various tutorials on the web. I trust you'll be able to easily apply what you already know about XAML binding syntax, value converters, etc. to implement your current scenario using IMultiValueConverter.

Related

Using ViewModels in ObservableCollections in Prism

As far as I know, the default way to use a ObservableCollection that is bound to a listview is with model classes as elements (ObservableCollection<MyModel>). So when a listview element is selected, we use NavigateAsync and pass the model, which then can be used by the ViewModel to "fill itself".
The problem with this approach is, that it's not possible to use ViewModel properties for binding in the listview.
For example:
I have a View, ViewModel and Model "PickList", which contains a collection of "PickLine" objects - each having a View, ViewModel and Model themselves. The PickLine object contains a property "PickedQuantity" and a property "OpenQuantity". Now in my PickList view, I don't want to bind these two to separate items (e.g. two labels), but I want to have one label to display both I a format like for example "PickedQuantity / OpenQuantity". I know this example can be solved by using multi binding or something like this. But that's not the meaning of it all.
My PickLine ViewModel already has a property "QuantityString", that I want to bind to the label of a listview element via DataTemplate. But how can I do this. Is it even possible?
Make a property that combines the two other properties and bind to that. E.g.:
public string FullQuantity {get {return $"{PickedQuantity} / {OpenQuantity}";}}
Then in the setter for PickedQuantity and OpenQuantity, you will want to call whatever PropertyChanged method you have set up to notify the bindings of a property change and pass in the FullQuantity property name so elements that are bound to FullQuantity get updated when either PickedQuantity or OpenQuantity are changed.
This way, you are only binding one label's text to one property and that label would get updated when either of the two quantity properties are changed.
Note: I am unfamiliar with Prism, but this approach should work regardless of the Mvvm framework in use.
Your PickListViewModel should expose a collection property whose items are of type PickLineViewModel (not PickLine).
Whether you need an ObservableCollection<PickLineViewModel> depends on where changes can happen - in service / model that initially created the PickLines or in the GUI or both. In any way, you have to make sure the changes are propagated from one side (the collection of view models) to the other (the collection of models). Google wrapping observable collection as a starter (hint: avoid two-way sync if possible). These blog posts are old but still relevant and make a good reading. A trivial wrapping is described in this answer.

Calling method on control in MVVM

I'm currently working with the ArcGIS Runtime SDK .NET where I'd like to have the current viewport of the map bound to a property on my view model. However, the view port (expressed as Extent) does not have a setter, despite being a dependency property.
In order to set the viewport, I need to call a method on the control. But how do I do that from the view model? I already found similiar questions here on SO, but most of them were answered with The view model shouldn't be aware of the view. I agree with that, but unfortunately I can't change the fact that the setter of a property on a proprietary control is a different method than the actual property I can bind to and read values from.
There are many ways to do the job that might be considered MVVM friendly.
The one that I suggest is that you wrap your ArcGIS view in a custom control that you have full control over. This way you can expose your required dependency properties and handle their setters in your custom control.
I use this method almost every time I'm using a third-party component this way I make the component loosely coupled with my other code and I can replace them easily.
You can use an attached property to push make a VM request any listening view to go to a certain viewpoint. I'm using that in my sample here:
https://github.com/Esri/arcgis-runtime-demos-dotnet/blob/master/src/TurnByTurn/RoutingSample.Shared/CommandBinder.cs
In your VM you simply raise INPC for a viewpoint:
public Viewpoint ViewpointRequested
{
get { return m_ViewpointRequested; }
private set
{
m_ViewpointRequested = value;
RaisePropertyChanged("ViewpointRequested");
}
}
And then lastly just bind this to the attached property on the MapView:
<esri:MapView Map="{Binding Map}"
local:CommandBinder.RequestView="{Binding ViewpointRequested}" />

Binding to a property by variable name (ideally from xaml)

I'm trying to databind (ideally from XAML as i know how to do this in code behind but it would be far from trivial to traverse my heavily templated tree just for that) to a property who's name i only know at runtime
What i would like to do is not the usual:
Content="{Binding TheProperty}"
But something like
Content="{Binding PropertyName=TheNameIsStoredInThisProperty}"
I'm trying to do this because i generate the UI from templates when binding to my plugins, but the UI is specified in a set of POCO and separate from the ViewModel, so i want to be able to generate my UI and still be able to wire it to the correct properties on the ViewModel, any advice is most welcome.
The immediate solution might be to bind to an arbitrary property in your VM with an IValueConverter that goes both ways, and the ConverterParameter is the string containing the source property name. Once inside the value converter you can use an interception pattern to Reflect out the value you need from the POCO. You can then pass the value up to the source property in the VM. Rather like a pipeline :) This will work but still leaves you with being notified when the POCO changes.
A Markup Extension seems plausible but likely to be brittle and provide naught in the way of performance improvement.
An Attached Behaviour still leaves you with having to Reflect and does not easily solve the problem of notifications originating in the POCO (AFAIK only Unity knows how to do that).

Why ever use a multi binding converter?

Couldn't I just use a single binding converter and as a parameter pass in the DataContext and from there pick what properties I want to use?
If you pass the whole object instead of the individual properties, then the binding expression will not be re-evaluated when the individual properties change. You will be losing the benefit of the INotifyPropertyChanged mechanism.
You might want to be more explicit and take in the minimum extra information (which is just generally good programming practice), or you may want information from more than one source - e.g. Your value might be dependent on a property of the datacontext and the checked state of a checkbox somewhere else in the view.
You can do that, but the binding will not update if the relevant properties change that way. Besides the updates Multibinding is needed for more complex bindings to different controls and data-objects.

ValueConverter with data binding

Is it possible to add a dependency property to a ValueConverter and bind it to a property of the object to which the converter is applied?
It isn't possible to do this in XAML. What you'd usually want to use for the sort of problem I'm guessing you have is a IMultiValueConverter.
If you really had to you could wire it up like you suggest in the code behind for your view but I really wouldn't recommend it. First it is much more difficult to manage and second your property won't be updated when the value you've bound to the new dependency property changes (i.e. the Convert method won't be called again). You're much better off using an IMultiValueConverter.

Categories