Reputation: 25378
My view has a control inside of it that is capable of generating an image that is saved at a path I can specify (along with some other data). I don't own this control and can't get the interface to generate an image changed. I'm not quite sure how to handle this with MVVM.
The quick and dirty way would be for my view to define a method that takes the desired path, and have the viewmodel call that method.
View:
public void GenerateImage(string path) {
_control.SaveImage(path);
}
ViewModel:
(actually this is the body of a Command) {
var path = GeneratePath();
_view.GenerateImage(path);
...
}
I don't like this because I get the feeling that viewmodels are not meant to directly reference the view, instead they represent the view's state and communicate via property bindings. It works, and I'm doing this while waiting on answers. I'd like to find a way around it.
I could get cute and have the view pass a reference to the control to a Command (I'm in Xamarin Forms) via the Execute() parameter, and have the command cast and make the call. This seems like lipstick on a pig since it makes the viewmodel still aware of a particular class inside the view. But in writing this paragraph I think I came up with a solution I like.
I /could/ create:
interface IGenerateImage {
void GenerateImage(string path);
}
The obvious implementation would delegate the call to an encapsulated control. I feel like if the view passes an IGenerateImage
then I'm not creating the viewmodel-to-view dependency that I'm trying to avoid, and I can test the logic without needing to instantiate expensive UI classes.
I like that answer, but I'm pretty sure there's an obvious solution I'm missing. Is there some other useful pattern for handling it? Or is it not a big deal if the viewmodel references the view?
Upvotes: 0
Views: 1072
Reputation: 1496
You never want the View Model to know anything about the View.
It's a little unclear what you can and can't change in your post, so I'm assuming you can change the V/VM, but not _control.
The easiest way is to create an event in the View Model that the View can subscribe to.
Something like this:
View:
// Constructor
public View()
{
// However you're setting your VM, i.e. DI or new-ing up the VM
// Subscribe to the event
vm.ImageGeneratedEvent += this.OnImageGeneratedEvent;
}
private void OnImageGeneratedEvent(object sender, ImageGeneratedEventArgs args)
{
// Call your SaveImage in the event handler
_control.SaveImage(args.Path);
}
View Model:
public event EventHandler<ImageGeneratedEventArgs> ImageGeneratedEvent;
// Command body
{
var path = GeneratePath();
// Send event to the View
this.NotifyImageGeneratedEvent(path)
}
private void NotifyImageGeneratedEvent(string path)
{
ImageGeneratedEventArgs args = new ImageGeneratedEventArgs(path);
if (this.ImageGeneratedEvent!= null)
{
this.ImageGeneratedEvent(this, args);
}
}
ImageGeneratedEventArgs:
public class ImageGeneratedEventArgs : EventArgs
{
public string Path { get; set; }
public ImageGeneratedEventArgs(string path)
{
this.Path = path;
}
}
Upvotes: 3