Reputation: 415
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.
Upvotes: 1
Views: 477
Reputation: 7413
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.
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);
}
}
Upvotes: 1
Reputation: 3451
To control you View via binding, your control needs something to bind to.
Hope that helps.
Upvotes: 2
Reputation: 4680
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.
Upvotes: 0