0

I've implemented Wiesław Šoltés' awesome ZoomBorder, but I'm struggling a bit with WPF and MVVM. For the questions' completeness sake, ZoomBorder is a UIElement that inherits from Border and gives the user the possibility of zooming and panning the content of the inherited border. It also has the ability to reset the zoom and panning.

I'd like to make ZoomBorder react to an event being published by certain view model, so that when that event is published, ZoomBorder resets the zoom. In my implementation, the ZoomBorder's DataContext is a ContentViewModel, which has an IEventAggregator (Prism.Events) injected via Autofac. Ideally, I would have liked to inject the event aggregator directly into ZoomBorder, so that it can subscribe to the event, but I can't because the constructor needs to be parameterless.

So ContentViewModel has to subscribe to the event, but how would I invoke ZoomBorder's Reset method from ContentViewModel? I understand I'd be transgressing MVVM, but I don't know how else to do it. I thought about making ZoomBorder expose a Command via a dependency property, but then the Reset code would have to be on the view model, which it can't.

Pona
  • 167
  • 1
  • 17

3 Answers3

1

You can use the ServiceLocator inside of views or controls to resolve types from the container.

public ZoomBorder()
{
   _eventAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>();
}

If you use AutoFac and the event aggregator without Prism, you can use the package Autofac.Extras.CommonServiceLocator and register your container to ServiceLocator.

var builder = new ContainerBuilder();
var container = builder.Build();

var csl = new AutofacServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => csl);
thatguy
  • 21,059
  • 6
  • 30
  • 40
1

I would use binding.

Add a dependency property to Zoomborder.

    public bool? ResetZoom
    {
        get
        {
            return (bool?)GetValue(ResetZoomProperty);
        }
        set
        {
            SetValue(ResetZoomProperty, value);
        }
    }
    public static readonly DependencyProperty ResetZoomProperty =
        DependencyProperty.Register("ResetZoom",
                    typeof(bool?),
                    typeof(CloseMe),
                    new PropertyMetadata(null, new PropertyChangedCallback(ResetZoomChanged)));
    private static void ResetZoomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if ((bool?)e.NewValue != true)
        {
            return;
        }
        ZoomBorder zb = (ZoomBorder)d;
        zb.Reset();
        zb.SetCurrentValue(ResetZoomProperty, false);
    }

You can then bind that to a public bool property in your ContentViewModel.

When set to true, the border will reset.

Andy
  • 11,864
  • 2
  • 17
  • 20
  • Thanks. I considered that option (sorry for not mentioning it in the question), but it didn't convince me that setting it to false didn't have any effect. It's like you'd be mantaining a state for something that isn't actually a state, but more like a signal to do something. – Pona Aug 13 '20 at 15:01
  • Setting it to false is explicitly trapped so there is definitely no effect. And yes it is a signal to do something but using a simple standard wpf mechanism. – Andy Aug 13 '20 at 15:45
1

This video shows how to create abstractions from Views/UI components and call methods on them from a VM using an interface. Don't let the title fool you. This look like it will fit this scenario perfectly

"How to Close Windows from a ViewModel in C#"

https://youtu.be/U7Qclpe2joo

Instead of calling the Close method on a window, you can adapt it to call your Reset method on your control from the VM.