Listen to changes of dependency property - c#

Is there any way to listen to changes of a DependencyProperty? I want to be notified and perform some actions when the value changes but I cannot use binding. It is a DependencyProperty of another class.

This method is definitely missing here:
DependencyPropertyDescriptor
.FromProperty(RadioButton.IsCheckedProperty, typeof(RadioButton))
.AddValueChanged(radioButton, (s,e) => { /* ... */ });
Caution: Because DependencyPropertyDescriptor has a static list of all handlers in application every object referenced in those handlers will leak if the handler is not eventually removed. (It does not work like common events on instance objects.)
Always remove a handler again using descriptor.RemoveValueChanged(...).

If it's a DependencyProperty of a separate class, the easiest way is to bind a value to it, and listen to changes on that value.
If the DP is one you're implementing in your own class, then you can register a PropertyChangedCallback when you create the DependencyProperty. You can use this to listen to changes of the property.
If you're working with a subclass, you can use OverrideMetadata to add your own PropertyChangedCallback to the DP that will get called instead of any original one.

I wrote this utility class:
It gives DependencyPropertyChangedEventArgs with old & new value.
The source is stored in a weak reference in the binding.
Not sure if exposing Binding & BindingExpression is a good idea.
No leaks.
using System;
using System.Collections.Concurrent;
using System.Windows;
using System.Windows.Data;
public sealed class DependencyPropertyListener : DependencyObject, IDisposable
{
private static readonly ConcurrentDictionary<DependencyProperty, PropertyPath> Cache = new ConcurrentDictionary<DependencyProperty, PropertyPath>();
private static readonly DependencyProperty ProxyProperty = DependencyProperty.Register(
"Proxy",
typeof(object),
typeof(DependencyPropertyListener),
new PropertyMetadata(null, OnSourceChanged));
private readonly Action<DependencyPropertyChangedEventArgs> onChanged;
private bool disposed;
public DependencyPropertyListener(
DependencyObject source,
DependencyProperty property,
Action<DependencyPropertyChangedEventArgs> onChanged = null)
: this(source, Cache.GetOrAdd(property, x => new PropertyPath(x)), onChanged)
{
}
public DependencyPropertyListener(
DependencyObject source,
PropertyPath property,
Action<DependencyPropertyChangedEventArgs> onChanged)
{
this.Binding = new Binding
{
Source = source,
Path = property,
Mode = BindingMode.OneWay,
};
this.BindingExpression = (BindingExpression)BindingOperations.SetBinding(this, ProxyProperty, this.Binding);
this.onChanged = onChanged;
}
public event EventHandler<DependencyPropertyChangedEventArgs> Changed;
public BindingExpression BindingExpression { get; }
public Binding Binding { get; }
public DependencyObject Source => (DependencyObject)this.Binding.Source;
public void Dispose()
{
if (this.disposed)
{
return;
}
this.disposed = true;
BindingOperations.ClearBinding(this, ProxyProperty);
}
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listener = (DependencyPropertyListener)d;
if (listener.disposed)
{
return;
}
listener.onChanged?.Invoke(e);
listener.OnChanged(e);
}
private void OnChanged(DependencyPropertyChangedEventArgs e)
{
this.Changed?.Invoke(this, e);
}
}
using System;
using System.Windows;
public static class Observe
{
public static IDisposable PropertyChanged(
this DependencyObject source,
DependencyProperty property,
Action<DependencyPropertyChangedEventArgs> onChanged = null)
{
return new DependencyPropertyListener(source, property, onChanged);
}
}

You could inherit the Control you're trying to listen, and then have direct access to:
protected void OnPropertyChanged(string name)
No risk of memory leak.
Don't be afraid of standard OO techniques.

There are multiple ways to achieve this. Here is a way to convert a dependent property to an observable, such that it can be subscribed to using System.Reactive:
public static class DependencyObjectExtensions
{
public static IObservable<EventArgs> Observe<T>(this T component, DependencyProperty dependencyProperty)
where T:DependencyObject
{
return Observable.Create<EventArgs>(observer =>
{
EventHandler update = (sender, args) => observer.OnNext(args);
var property = DependencyPropertyDescriptor.FromProperty(dependencyProperty, typeof(T));
property.AddValueChanged(component, update);
return Disposable.Create(() => property.RemoveValueChanged(component, update));
});
}
}
Usage
Remember to dispose the subscriptions to prevent memory leaks:
public partial sealed class MyControl : UserControl, IDisposable
{
public MyControl()
{
InitializeComponent();
// this is the interesting part
var subscription = this.Observe(MyProperty)
.Subscribe(args => { /* ... */}));
// the rest of the class is infrastructure for proper disposing
Subscriptions.Add(subscription);
Dispatcher.ShutdownStarted += DispatcherOnShutdownStarted;
}
private IList<IDisposable> Subscriptions { get; } = new List<IDisposable>();
private void DispatcherOnShutdownStarted(object sender, EventArgs eventArgs)
{
Dispose();
}
Dispose(){
Dispose(true);
}
~MyClass(){
Dispose(false);
}
bool _isDisposed;
void Dispose(bool isDisposing)
{
if(_disposed) return;
foreach(var subscription in Subscriptions)
{
subscription?.Dispose();
}
_isDisposed = true;
if(isDisposing) GC.SupressFinalize(this);
}
}

If that is the case, One hack. You could introduce a Static class with a DependencyProperty. You source class also binds to that dp and your destination class also binds to the DP.

Related

Trigger Dependency Property Changed when child Dependency Property has been changed

I have the following class:
public class dm_fourvalues : DependencyObject
{
[JsonProperty]
public double First
{
get { return (double)GetValue(FirstProperty); }
set
{
SetValue(FirstProperty, value);
}
}
public static readonly DependencyProperty FirstProperty =
DependencyProperty.Register("First", typeof(double), typeof(dm_fourvalues),
new FrameworkPropertyMetadata(1d,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnFirstPropertyChanged)));
private static void OnFirstPropertyChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
dm_fourvalues uci = sender as dm_fourvalues;
if (uci != null)
{
uci.OnFirstChanged();
}
}
private void OnFirstChanged()
{
Console.WriteLine("first on dm fourvalues changed");
}
Now if use this class as identical property on another object, when the First property gets changed,
this object's OnChanged is not triggered, thus also a binding would not work.
What defines that a parent dependencyobject has been changed?

Raise event when property is changed

I need to raise an event when the value of the property is changed. In my case this is when webView.Source is changed. I can't make a derived class because the class is marked as sealed. Is there any way to raise an event ?
Thank you.
Raise event when property is changed
For this scenario, you could create a DependencyPropertyWatcher to detect DependencyProperty changed event. The follow is tool class that you could use directly.
public class DependencyPropertyWatcher<T> : DependencyObject, IDisposable
{
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(object),
typeof(DependencyPropertyWatcher<T>),
new PropertyMetadata(null, OnPropertyChanged));
public event DependencyPropertyChangedEventHandler PropertyChanged;
public DependencyPropertyWatcher(DependencyObject target, string propertyPath)
{
this.Target = target;
BindingOperations.SetBinding(
this,
ValueProperty,
new Binding() { Source = target, Path = new PropertyPath(propertyPath), Mode = BindingMode.OneWay });
}
public DependencyObject Target { get; private set; }
public T Value
{
get { return (T)this.GetValue(ValueProperty); }
}
public static void OnPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
{
DependencyPropertyWatcher<T> source = (DependencyPropertyWatcher<T>)sender;
if (source.PropertyChanged != null)
{
source.PropertyChanged(source.Target, args);
}
}
public void Dispose()
{
this.ClearValue(ValueProperty);
}
}
Usage
var watcher = new DependencyPropertyWatcher<string>(this.MyWebView, "Source");
watcher.PropertyChanged += Watcher_PropertyChanged;
private void Watcher_PropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
}
You can use a decorator to wrap the original class and raise an event for decorated properties.

Detect when ScrollBar Maximum is changed

Simple question. Having:
<ScrollBar ... />
How can I detect when Maximum is changed? E.g. for Value there is an event.
Typically there would be a binding of some kind. I was thinking maybe it is possible to get this binding, create dependency property and bind to it instead, then I can register a callback when this new dependency property is changed... but that sounds complicated nor I am sure it is acceptable solution to all cases (e.g. what if another binding is set, how can I detect this kind of change). Polling?
You can create a custom class such as:
public class MScrollBar : System.Windows.Controls.Primitives.ScrollBar
{
protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
{
// do stuff
base.OnMaximumChanged(oldMaximum, newMaximum);
}
}
Or
public class MScrollBar : System.Windows.Controls.Primitives.ScrollBar
{
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == System.Windows.Controls.Primitives.ScrollBar.MaximumProperty)
{
// do stuff
}
base.OnPropertyChanged(e);
}
}
It is important to understand what any property can be a source for multiple bindings. We can create a new target (new dependency property) which is then perfectly able to report about any change done to a property:
Create a new dependency property with callback.
Bind it to any other property to monitor for changes.
public partial class MainWindow : Window
{
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(MainWindow), new PropertyMetadata(0, (d, e) =>
{
// value has changed
}));
public MainWindow()
{
InitializeComponent();
var scrollBar = ... // instance of scrollbar
BindingOperations.SetBinding(this, MaximumProperty,
new Binding(nameof(RangeBase.Maximum)) { Source = scrollBar });
}
}

Dependency Properties not working (making composite behavior)

I'm trying to make a composite behavior which is composed of arbitrary simple behaviors. I found behaviors very flexible way of making custom controls.
Currently I have 5 behaviors implemented for slider. but they can conflict with each other.
These behaviors are designed for one control. I could design each of them to work independently without conflicting with each other (its worth mentioning that I did this and it worked successfully. but I removed all of it because it was just ugly.)
There are a lot of share points, I don't want to rewrite same code for every behavior.
So I'm trying to make a composite behavior for one control. this behavior has some attached properties which is shared for all of its containing behaviors. therefor these behaviors don't conflict with each other. And also a lot of code redundancy is gone. now containing behaviors becomes a lot simpler.
Here is the XAML sample for you to better get the idea.
<i:Interaction.Behaviors>
<b:SliderCompositeBehavior SourceValue="{Binding SharedValue}">
<sb:FreeSlideBehavior/>
<sb:LockOnDragBehavior/>
<sb:CancellableDragBehavior/>
<sb:KeepRatioBehavior/>
<sb:DragCompletedCommandBehavior Command="{Binding SeekTo}"/>
</b:SliderCompositeBehavior>
</i:Interaction.Behaviors>
Also all of these behaviors are designed to work stand alone. i.e putting it like this works just fine.
<i:Interaction.Behaviors>
<sb:FreeSlideBehavior/>
</i:Interaction.Behaviors>
Here is CompositeBehavior<T> : Behavior<T> :
[ContentProperty(nameof(BehaviorCollection))]
public abstract class CompositeBehavior<T> : Behavior<T>
where T : DependencyObject
{
public static readonly DependencyProperty BehaviorCollectionProperty =
DependencyProperty.Register(
$"{nameof(CompositeBehavior<T>)}<{typeof(T).Name}>",
typeof(ObservableCollection<Behavior<T>>),
typeof(CompositeBehavior<T>),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.NotDataBindable));
public ObservableCollection<Behavior<T>> BehaviorCollection
{
get
{
var collection = GetValue(BehaviorCollectionProperty) as ObservableCollection<Behavior<T>>;
if (collection == null)
{
collection = new ObservableCollection<Behavior<T>>();
collection.CollectionChanged += OnCollectionChanged;
SetValue(BehaviorCollectionProperty, collection);
}
return collection;
}
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// some code to throw exception when same behavior is set more than once.
}
protected override void OnAttached()
{
foreach (var behavior in BehaviorCollection)
{
behavior.Attach(AssociatedObject);
}
}
protected override void OnDetaching()
{
foreach (var behavior in BehaviorCollection)
{
behavior.Detach();
}
}
}
Here is the SliderCompositeBehavior : CompositeBehavior<Slider> (only one dependency is shown for sake of simplicity)
public sealed class SliderCompositeBehavior : CompositeBehavior<Slider>
{
private Slider Host => AssociatedObject;
public static readonly DependencyProperty SourceValueProperty =
DependencyProperty.Register(
nameof(SourceValue),
typeof(double),
typeof(SliderCompositeBehavior),
new FrameworkPropertyMetadata(
0d,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSourceValueChanged));
// does the binding
public double SourceValue
{
get { return (double)Host.GetValue(SourceValueProperty); }
set { Host.SetValue(SourceValueProperty, value); }
}
// attached property for containing behaviors.
public static void SetSourceValue(Slider host, double value)
{
host.SetValue(SourceValueProperty, value);
}
public static double GetSourceValue(Slider host)
{
return (double)host.GetValue(SourceValueProperty);
}
private static void OnSourceValueChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var soruce = (SliderCompositeBehavior)dpo;
soruce.Host.Value = (double)args.NewValue;
}
}
Now there are two problems I can see.
Dependency property definitions inside containing behaviors do not function at all.
Overrinding metadata of dependency property does not work for containing properties.
Inside DragCompletedCommandBehavior : Behavior<Slider> I have
public sealed class DragCompletedCommandBehavior : Behavior<Slider>
{
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
nameof(Command),
typeof(ICommand),
typeof(DragCompletedCommandBehavior));
}
I get this error on output. (this does not throw exception. it was hidden somewhere in output display after program started.)
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=SeekTo; DataItem=null; target element is 'DragCompletedCommandBehavior' (HashCode=52056421); target property is 'Command' (type 'ICommand')
In another behavior I have this.
public sealed class LockOnDragBehavior : Behavior<Slider>
{
static LockOnDragBehavior()
{
SliderCompositeBehavior.SourceValueProperty.OverrideMetadata(
typeof(LockOnDragBehavior),
new FrameworkPropertyMetadata(
0d,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSourceValueChanged));
}
private static void OnSourceValueChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
// do something
}
}
But OnSourceValueChanged never fires. the main OnSourceValueChanged inside SliderCompositeBehavior still fires though. but new meta data is just doing nothing.
How can I fix these problems? I don't understand why Dependency properties inside containing behaviors does not work. can some one please explain why? Thank you so much.
I found it out. after reading this post I understood that elements (in my case nested behaviors) were not part of the visual or logical tree. So data context was not accessible. and therefor binding did not work.
But instead of using ProxyBinding which was used here I came up with better solution.
The special collection BehaviorCollection does some magic when attaching behaviors. but I was using ObservableCollection therfor behaviors did not attached correctly.
Unfortunately the constructor of BehaviorCollection is internal. But who cares when you have power of reflection? ;)
Using BehaviorCollection instead essentially fixed the binding problem.
how ever overriding metadata problem is still not fixed. but I guess I will try other approaches (like using another dependency property) rather than overriding metadata of dependency property.
Here is the correction to CompositeBehavior<T> class.
[ContentProperty(nameof(BehaviorCollection))]
public abstract class CompositeBehavior<T> : Behavior<T>
where T : DependencyObject
{
#region Behavior Collection
public static readonly DependencyProperty BehaviorCollectionProperty =
DependencyProperty.Register(
$"{nameof(CompositeBehavior<T>)}<{typeof(T).Name}>",
typeof(BehaviorCollection),
typeof(CompositeBehavior<T>),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.NotDataBindable));
public BehaviorCollection BehaviorCollection
{
get
{
var collection = GetValue(BehaviorCollectionProperty) as BehaviorCollection;
if (collection == null)
{
var constructor = typeof(BehaviorCollection)
.GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, Type.EmptyTypes, null);
collection = (BehaviorCollection) constructor.Invoke(null);
collection.Changed += OnCollectionChanged;
SetValue(BehaviorCollectionProperty, collection);
}
return collection;
}
}
private void OnCollectionChanged(object sender, EventArgs eventArgs)
{
var hashset = new HashSet<Type>();
foreach (var behavior in BehaviorCollection)
{
if (behavior is Behavior<T> == false)
{
throw new InvalidOperationException($"{behavior.GetType()} does not inherit from {typeof(Behavior<T>)}.");
}
if (hashset.Add(behavior.GetType()) == false)
{
throw new InvalidOperationException($"{behavior.GetType()} is set more than once.");
}
}
}
#endregion
protected sealed override void OnAttached()
{
OnSelfAttached();
foreach (var behavior in BehaviorCollection)
{
behavior.Attach(AssociatedObject);
}
}
protected sealed override void OnDetaching()
{
OnSelfDetaching();
foreach (var behavior in BehaviorCollection)
{
behavior.Detach();
}
}
protected virtual void OnSelfAttached()
{
}
protected virtual void OnSelfDetaching()
{
}
}

Zooming a plot via the MVVM pattern?

I have a complex Plot RenderingControl that I have placed into a View. What would be the ideal way to handle zooming with respect to the MVVM pattern? I want the user to be able to zoom by clicking and dragging on the plot.
One approach I see would be to take the MouseMove, MouseUp, MouseDown events of the Plot control and wire them up to commands in the PlotViewModel. Now in response to the commands the ViewModel could update it's ZoomLevel property, which could be bound to the view, and cause the View to zoom in. While the user is clicking and dragging I would also like to display a rectangle indicating the region that will be zoomed. Would it make sense to keep an AnnotationViewModel in PlotViewModel for the zoom preview?
Another approach would be to handle it all in the View and not involve the ViewModel at all.
The main difference I see is that capturing the behavior in the ViewModel will make that behavior much more re-useable than in the View. Though I have a feeling that the underlying Plot control and the resulting View are complex enough that there isn't going to be much of chance for re-use anyway. What do you think?
I think there are several ways to solve your problem. HighCore right, when he says that Zoom applies to View, so it is advisable to leave it on the side View. But there are alternatives, we consider them below. Unfortunately, I did not deal with Plot RenderingControl, so I will describe a solution based on an abstract, independent of the control.
AttachedBehavior
In this case, I would have tried to identify possible all the work with the control via an attached behavior, it is ideally suited for the MVVM pattern, and it can be used in the Blend.
Example of work
In your View, control is defined and an attached behavior, like so:
<RenderingControl Name="MyPlotControl"
AttachedBehaviors:ZoomBehavior.IsStart="True" ... />
And in code-behind:
public static class ZoomBehavior
{
public static readonly DependencyProperty IsStartProperty;
public static void SetIsStart(DependencyObject DepObject, bool value)
{
DepObject.SetValue(IsStartProperty, value);
}
public static bool GetIsStart(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsStartProperty);
}
static ZoomBehavior()
{
IsStartMoveProperty = DependencyProperty.RegisterAttached("IsStart",
typeof(bool),
typeof(ZoomBehavior),
new UIPropertyMetadata(false, IsStart));
}
private static void IsStart(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
UIElement uiElement = sender as UIElement;
if (uiElement != null)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
uiElement.MouseDown += new MouseButtonEventHandler(ObjectMouseDown);
uiElement.MouseMove += new MouseEventHandler(ObjectMouseMove);
uiElement.MouseUp += new MouseButtonEventHandler(ObjectMouseUp);
}
}
}
// Below is event handlers
}
Once you're set to true for property IsStart, PropertyChanged handler is triggered and it set the handlers for events that contain the basic logic.
For the transmission of additional data in you behavior register additional dependency properties, for example:
<RenderingControl Name="MyPlotControl"
AttachedBehaviors:ZoomBehavior.IsStart="True"
AttachedBehaviors:ZoomBehavior.ZoomValue="50" />
In code-behind:
// ... Here registered property
public static void SetZoomValue(DependencyObject DepObject, int value)
{
DepObject.SetValue(ZoomValueProperty, value);
}
public static int GetZoomValue(DependencyObject DepObject)
{
return (int)DepObject.GetValue(ZoomValueProperty);
}
// ... Somewhere in handler
int value = GetZoomValue(plotControl);
To retrieve data on the behavior, I use a singleton pattern. This pattern represents global static access point to the object and must guarantee the existence of a single instance of the class.
Example of using this pattern (taken from the behavior, who worked with the time display in the View):
public class TimeBehavior : INotifyPropertyChanged
{
// Global instance
private static TimeBehavior _instance = new TimeBehavior();
public static TimeBehavior Instance
{
get
{
return _instance;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private string _currentTime = DateTime.Now.ToString("HH:mm");
public string CurrentTime
{
get
{
return _currentTime;
}
set
{
if (_currentTime != value)
{
_currentTime = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CurrentTime"));
}
}
}
}
private string _currentDayString = ReturnDayString();
public string CurrentDayString
{
get
{
return _currentDayString;
}
set
{
if (_currentDayString != value)
{
_currentDayString = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CurrentDayString"));
}
}
}
}
private string _currentMonthAndDayNumber = ReturnMonthAndDayNumber();
public string CurrentMonthAndDayNumber
{
get
{
return _currentMonthAndDayNumber;
}
set
{
if (_currentMonthAndDayNumber != value)
{
_currentMonthAndDayNumber = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CurrentMonthAndDayNumber"));
}
}
}
}
public static readonly DependencyProperty IsTimerStartProperty;
public static void SetIsTimerStart(DependencyObject DepObject, bool value)
{
DepObject.SetValue(IsTimerStartProperty, value);
}
public static bool GetIsTimerStart(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsTimerStartProperty);
}
static void OnIsTimerStartPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(1000);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
}
static TimeBehavior()
{
IsTimerStartProperty = DependencyProperty.RegisterAttached("IsTimerStart",
typeof(bool),
typeof(TimeBehavior),
new PropertyMetadata(new PropertyChangedCallback(OnIsTimerStartPropertyChanged)));
}
private static void timer_Tick(object sender, EventArgs e)
{
_instance.CurrentTime = DateTime.Now.ToString("HH:mm");
_instance.CurrentDayString = ReturnDayString();
_instance.CurrentMonthAndDayNumber = ReturnMonthAndDayNumber();
}
}
Access to data in the View:
<TextBlock Name="WidgetTimeTextBlock"
Text="{Binding Path=CurrentTime,
Source={x:Static Member=AttachedBehaviors:TimeBehavior.Instance}}" />
Alternatives
Work in View via Interface
The point of this way is that we call a method in View via ViewModel, which does all the work, and he does not know about the View. This is accomplished by the operation of the interface and the well described here:
Talk to View
Using ServiceLocator
ServiceLocator allows you to work in the ViewModel, without violating the principles of MVVM. You have a RegisterService method where you register the instance of the service you want to provide and a GetService method which you would use to get the service you want.
More information can be found here:
Service Locator in MVVM

Categories

Resources