I use the MVVM pattern to provide data to a user control in a window. I would like to implement a method on the view model object which forwards any calls to itself to the corresponding user control, that it supplies with data. For this I would need to have a reference to the control in the view model object.
I could do this by declaring a property on the user control that returns a reference to the control itself, and then bind to the view model object, but it's quite ugly:
<MyUserControl ReferenceToMyself="{Binding CorrespondingUserControl, Mode=OneWayToSource}"/>
And then in the view model object:
public void MethodThatForwardsCalls() => CorrespondingUserControl.ThisIsCalled();
Is there a better way to bind a control itself to a view model object?
Have you instead considered using an event-like interaction between the ViewModel and the View to avoid having to keep references of the View in the ViewModel?
In the ViewModel, I would create a property of type Action
and call it something like ThisIsCalledEvent
;
#region ThisIsCalledEvent
private Action m_ThisIsCalledEvent;
public Action ThisIsCalledEvent
{
get
{
return m_ThisIsCalledEvent;
}
set
{
if (m_ThisIsCalledEvent != value)
{
m_ThisIsCalledEvent = value;
OnPropertyChanged(nameof(ThisIsCalledEvent));
}
}
}
#endregion
Then, in MyUserControl
, add an event callback to the DataContextChanged
event;
this.DataContextChanged += MyUserControl_DataContextChanged;
in the CS constructor, or
DataContextChanged="MyUserControl_DataContextChanged"
in the XAML constructor.
Now, in the event callback, you can set the Action
for the ThisIsCalledEvent
property of the ViewModel;
private void MyUserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
MyUserControlViewModel vm = DataContext as MyUserControlViewModel; //Get the ViewModel from the View's DataContext
if(vm == null)
return;
vm.ThisIsCalledEvent = delegate () { ThisIsCalled(); }; //When the action is invoked, call the desired function
}
Now, back to the ViewModel, to invoke you simply change your method's body to;
public void MethodThatForwardsCalls()
{
this.ThisIsCalledEvent?.Invoke(); //Invoke the Action, if it exists, that has been set by the View
}
This way, you never keep an instance of the View in the ViewModel, and each time a new view is instantiated from the ViewModel, it sets the Action
callback.
EDIT
As suggested by Jürgen Röhr in the comments, in the DataContextChanged
event callback, you can use the args.NewValue
to bind to the new DataContext. Furthermore, if you register the delegate to the Action
property in addition instead of setting it, you can use args.OldValue
to de-register it from the previous DataContext.