I have a scenario where updating the View from the ViewModel using the INotifyPropertyChanged
interface is not suitable.
Instead I'd like to refresh some of the bindings explicitly.
In WPF this seems to be possible by obtaining a BindingExpression
from the control and call BindingExpression.UpdateTarget()
(see How to force a WPF binding to refresh?).
Is something similar possible in MAUI? BindingExpression
does not even seem to exist...
Background
I'm displaying a list of items, each of them has a CreatedAt
timestamp. In the UI I want to display this as "7 minutes ago", for example. Therefore I created a value converter that converts a DateTime
to a string. And of course the displayed string should update over time.
IMHO, updating the text in the UI should be a matter of the View in this case. I don't want to have an interval timer in my ViewModels. I prefer to have an interval timer in my View (in the code behind) that refreshes the binding of only those items currently visible.
Best Answer
"IMHO, updating the text in the UI should be a matter of the View inthis case. I don't want to have an interval timer in my ViewModels. Iprefer to have an interval timer in my View (in the code behind) thatrefreshes the binding of only those items currently visible."
I want to point out something important about MVVM.
When following MVVM architecture ViewModels should not know anything about the Views. However, your Views have access to the ViewModels all the time.
You are free to call a method from your ViewModel, at any time. Be it as response to event, received message, or something else.
Your View should already hold reference to your ViewModel, so by making a timer, and calling a method to update your data, you will not be violating any MVVM principles.
(If I did not convince you already, tell me to continue...)
Unfortunately, Maui lacks any way to access the binding expression.
One solution is to create View properties that do what you want, and trigger property changes on a timer:
<Label Text="{Binding RelativeTime1, Source={RelativeSource Self}}" />
In view's code-behind:
MyViewModel VM => BindingContext as MyViewModel;public RelativeTime1 => AsRelativeTime(VM.Time1);// Each timer tickpublic void Elapsed(){OnPropertyChanged(nameof(RelativeTime1));}
In Xamarin case I use this helper method (I hope same in MAUI)
internal static void ApplyBinding(this BindableObject bindableObject, BindableProperty property){var binding = bindableObject.GetBindableObject(property);var tBinding = typeof(BindingBase);var mUnapply = tBinding.GetMethod("Unapply", BindingFlags.NonPublic | BindingFlags.Instance);mUnapply?.Invoke(binding, new object[] { false });var mApply = tBinding.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(v => v.Name == "Apply" && v.GetParameters().Length > 2);mApply?.Invoke(binding, new[] { bindableObject.BindingContext, bindableObject, property, false });}public static BindingBase GetBindableObject(this BindableObject bindableObject, BindableProperty property){// access to private field List<BindablePropertyContext> _propertiesvar m = typeof(BindableObject).GetMethod("GetContext", BindingFlags.Instance | BindingFlags.NonPublic);var bindablePropertyContext = m?.Invoke(bindableObject, new object[] { property });if (bindablePropertyContext != null){Type bindablePropertyContextType = bindablePropertyContext.GetType();FieldInfo fieldProperty = bindablePropertyContextType.FindField("Property");if (ReferenceEquals(fieldProperty.GetValue(bindablePropertyContext), property)){FieldInfo fieldBinding = bindablePropertyContextType.FindField("Binding");BindingBase b = fieldBinding.GetValue(bindablePropertyContext) as BindingBase;if (b != null){if (typeof(BindingBase).GetMethod("Clone", BindingFlags.Instance | BindingFlags.NonPublic) is { } mClone){b = mClone.Invoke(b, new object[] {}) as BindingBase;}}return b;}}return null;}
Usage in code behind's timer "Elapsed" is (if we are inside custom CommunityToolkit's TabView class for ex.)
this.ApplyBinding(TabItemsSourceProperty);
PS This solution require that you indirectly use binding expression of binding (for TabItemsSource for example), for xaml it looks like following
<views:TabView TabItemsSource="{Binding Settings.ProjectTabs}" />
Here I interpret the place of "Settings.ProjectTabs" as "binding expression" from your question.