简体   繁体   中英

Manipulate View from ViewModel

Good morning, thanks in advance for answering.

My view implements a pan & zoom library: A WPF Custom Control for Zooming and Panning

In the View, there is a control to zoom to a Point based on a mouse double click:

private void zoomAndPanControl_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
   if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0)
   {
      Point doubleClickPoint = e.GetPosition(content);
      zoomAndPanControl.AnimatedSnapTo(doubleClickPoint);
   }
}

I would like to force "zoomAndPanControl. AnimatedSnapTo (doubleClickPoint);" to a particular Point based on ViewModel data for a geometry point that I draw, when I pull it. So, that the view will pan to the point of the new geometry x,y coords. The geometry/points are already bound to the view.

As an added note, pulling data for the geometry point is happening in a DispatchTimer. As the new geometry coords are read, I'd like the view to pan & follow these coords.

Is there an easy way to access this control from the ViewModel, when I get data? Possibly simulate a mouse event with a custom point? I'm not sure the best way to go about it.

To control you View via binding, your control needs something to bind to.

  1. Add an Dependency Property to your custom control that accepts a 'Point' type.
  2. Bind a property with INotifyPropertyChanged implemented from your view model to that DP - note, a ValueConverter can be added in here should you want to refrain from using the 'Point' class in your VM.
  3. In the definition of your DP, adapt the setter to trigger your AnimatedSnapTo(...) method

Hope that helps.

The way I usually handle these kind of scenarios is by defining a delegate on the ViewModel, set it in the View, and invoke it when needed:

Define a method in your ViewModel that takes in an Action<Point> and a Point object for current mouse position:

public void ExecuteAnimatedSnapTo(Action<Point> animatedSnapToAction, Point pointerPosition)
{
    if (animatedSnapToAction != null && pointerPosition != null)
    {
        // Create a new point based on the one passed in and data in ViewModel
        Point newPoint =
            new Point(pointerPosition.X + viewModelData.X, pointerPosition.Y + viewModelData.Y);

        // Invoke the delegate using the new point
        animatedSnapToAction(newPoint);
    }
}

Then in your View's code behind, execute this method:

private void zoomAndPanControl_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
   if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0)
   {
      Point doubleClickPoint = e.GetPosition(content);

      var viewModel = (MyViewModel)this.DataContext;

      viewModel.ExecuteAnimatedSnapTo(zoomAndPanControl.AnimatedSnapTo, doubleClickPoint);
   }
}   

With this approach, you're still preserving the isolation of the View from the ViewModel. When [unit] testing the VM, the delegate and the point will probably be null when passed into the method. The if block then prevents the "UI" logic from being tested.

One thing you need to be careful about is if the ViewModel data is calculated on a different thread, you HAVE TO execute the delegate on the UI dispatcher.

Edit

I was under the impression that the ViewModel had all the data needed to invoke the delegate when MouseDoubleClick gets fired. If that's not the case, a better solution would be to expose the Action as a property on VM and call it when needed:

public Action<Point> AnimatedSnapToAction { get; set; }

When you create an instance of the VM on the View, set the property as well:

public MyView()
{
    InitializeComponent();

    MyViewModel viewModel = new MyViewModel();
    viewModel.AnimatedSnapToAction = zoomAndPanControl.AnimatedSnapTo;

    this.DataContext = viewModel;
}

Now you can execute the delegate on the VM whenever needed. For example, if it needs to be called on a DispatcherTimer 's tick, it would look like this:

private void dispatcherTimer_Tick(object sender, EventArgs e)
{
    // Calculate geometry data

    if(AnimatedToSnapAction != null)
    {
        AnimatedSnapToAction(pointCalculatedUsingGeometryData);
    }
}

A view model should expose the data and behaviour that your view needs to display and interact, it should never have a reference to the View itself.

The View implicitly or explicitly has a reference to the ViewModel. Via this reference you can either call methods, bind data, hook up events, etc...

To keep it simple in this case you probably want to execute a command on your ViewModel when the user double clicks on the control and then any public properties exposed by the ViewModel can be modified in the command execution. These properties can then be used to determine the point you want to zoom to.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM