WPF and MVVM: GeoViewTapped Event

982
13
03-04-2019 08:30 AM
Highlighted
New Contributor III

Hello, 

I am trying to create binding for the GeoViewTappedAsync event so that I can place the code in my MapViewModel to follow an MVVM pattern for my WPF application, however I'm not sure how to wire this up. Is there some sample code available that demonstrates this pattern? After looking through GeoNet, I have found some suggestions but they are a few years old.

<esri:MapView x:Name="MainMap" Map="{Binding Map}"  GeoViewTapped="Binding {to what does this bind?}">

Thank you!

Jen

Reply
0 Kudos
13 Replies
Highlighted
Esri Frequent Contributor

You don't bind directly from view events to VM code in WPF. The way you would handle this is very similar to how you for instance would handle a click event from a button.

It's all a little different based on which MVVM framework you use, but typically it's called "EventToCommand" or similar approach, and there's quite a lot of examples online if you search for that.

Also whichever suggestions you found should still be relevant (albeit code might have to be tweaked), since reacting to view events in MVVM hasn't really changed.

Another MUCH simpler way, is to simply create a GeoViewTapped event handler in code-behind, and all that handler is doing is forwarding the event directly to the viewmodel (and possible filtering anything view-specific away). This is _still_ pure MVVM, as your code-behind is only view-specific code, and thus still gets the separation MVVM likes to promote. And really it's not much different than a pure-xaml version of it is, as the XAML is really just generating code in the same view class. I personally prefer this more pragmatic approach as it's much simpler to follow the flow from view events to the view model. TBH I find the EventToCommand approach a little less MVVM as it forwards view-specific event arguments directly to your viewmodel. But it's all religion at this point which approach you choose to take.

Highlighted
New Contributor III

Hi Morton,

Thanks for your feedback on this. Currently, I'm looking into the MVVM Light framework to use with my application. It seems Pluralsight has a good intro provided by Laurent Bugnion. I managed to bind the button click without the use of any framework but I think using an existing one will be important as I move forward with this application.

I do like your suggestion of putting view specific code in the code-behind, and then forwarding the event on to the viewmodel for simplification purposes. Although I want to adhere to the MVVM pattern, I am not opposed to this approach if it makes things easier to follow.

Reply
0 Kudos
Highlighted
MVP Regular Contributor

Like Morten Nielsen‌ says, just wire up the events to an EventAggregator in the code behind of the view that contains MapView.  This does assume you are using a framework that provides event aggregation.  I use Prism, but MVVM Light would work as well

private void WireUpMapViewEventHandlers(IEventAggregator eventAggregator)
{
MapView.GeoViewTapped += (s, e) => { eventAggregator.GetEvent<GeoViewTappedEvent>().Publish(e); };
MapView.GeoViewDoubleTapped += (s, e) => { eventAggregator.GetEvent<GeoViewDoubleTappedEvent>().Publish(e); };
MapView.GeoViewHolding += (s, e) => { eventAggregator.GetEvent<GeoViewHoldingEvent>().Publish(e); };
MapView.ViewpointChanged += (s, e) => { eventAggregator.GetEvent<ViewpointChangedEvent>().Publish(MapView.VisibleArea.Extent); };
MapView.NavigationCompleted += (s, e) => { eventAggregator.GetEvent<NavigationCompletedEvent>().Publish(MapView.VisibleArea.Extent); };
}

Now just need to add an event handler anywhere in the application you want to respond to the event.

The problem (to me) with using an EventToCommand type approach is that this only allows response to these events in the MapViewModel.  This is fine in a simple application but if you want the application to grow beyond the single ViewModel it really won't support it (imo).

As pointed out by Morten, the code in the code behind is only associated to the MapView.  So, I would not consider it breaking any MVVM principle.  No other part of the application  knows anything about the MapView other than that it fires these events

Highlighted
New Contributor III

Hi Joe,

Thanks for your insight and code snippet! I am looking at MVVM Light as it seems like it may be simpler to me to follow as someone new to these frameworks. I've started an intro course on Pluralsight so I'll see how it goes. Thanks again for your help. 

Reply
0 Kudos
Highlighted
Occasional Contributor

Hi, 

I am able to bind tapped event with MVVM Light. See below code, might be helpful. 

MapVM:

public ICommand gvtapped { get; set; }
public MapControlViewModel(): ViewModelBase
{
gvtapped = new RelayCommand<Esri.ArcGISRuntime.UI.Controls.GeoViewInputEventArgs>(Ongvtapped);
}

private void Ongvtapped(GeoViewInputEventArgs obj)
{
}

Map.xaml:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"

<esri:MapView >
         <i:Interaction.Triggers>
         <i:EventTrigger EventName="GeoViewTapped">
            <cmd:EventToCommand Command="{Binding gvtapped}" PassEventArgsToCommand="True" />
         </i:EventTrigger>
      </i:Interaction.Triggers>
</esri:MapView>

Regards,

Prashant 

Highlighted
MVP Regular Contributor

There is nothing wrong with this approach, but as I mentioned in my previous post it does not scale to a large application that may need access to a MapView event from an object that is not bound directly MapViews view.  Using an event approach one can respond to a MapView event from anywhere, even a separate .dll.  Again not a bad approach, but does not scale to a really large application.

Highlighted
Occasional Contributor III

Hello all,

I've been doing research on this learning with WPF and it seems so odd that functionality like Identifying/Querying Map Layers is only available on the MapView and that it is really difficult to call methods on it using MVVM.  

I understand more or less how to relay the events from the View to the View Model (using mvvm light), but what about simply calling a method on the MapView?  Does that still require creating Controllers?  I've been working with the samples available online for Navigating from the View Model, and Identification Controllers, and am so worried because I just don't understand how to make them work for me.  I think the Navigating from the View Model sample provides the technique for calling methods on the View, but I am having issues with implementing it.

What I am doing is attempting to Identify the layers at a point.  I have the point from the Location MapPoint because I can pass that property with the Command as a CommandParameter to my ViewModel, but to perform the query on the layers I need to call a method on the MapView.  That is where I need help.

Am I just way off the path here?  My background is in Python and javascript/typescript so I am learning the .net sdk and WPF with a plan to transition to UWP after I get a better grasp of how to structure these runtime apps.

Thanks,

Rich 

Reply
0 Kudos
Highlighted
Occasional Contributor

Try this, I haven't tested but this should work

    <esri:MapView x:Name="MyMapView" />

In Code behind of map control access map view and assign to viewmodel prperty

public MainWindow()
        {
            InitializeComponent();
            ((MapViewModel)this.DataContext).MyMapView = this.MyMapView;
        }

MapViewModel:

 public MapView MyMapView { get; set; }

 public MapView MyMapView { get; set; }

All members of MapView  will be available in view model

Regards,

Prashant 

Reply
0 Kudos
Highlighted
Esri Frequent Contributor

> it seems so odd that functionality like Identifying/Querying Map Layers is only available on the MapView 

This is fair criticism, but something to keep in mind is that Identify is a very UI-centric thing. It (typically) deals with the user actually clicking on the view, it's inputs are actual screen coordinates (not map unit coordinates), and the hit test is done based on the rendering of the features (so a large point symbol will still get found even if you don't click exactly at the center of the point).


You could instead do a spatial query of features based on map geometry directly against the layers without the view, but that's not really "identify" in the usual respect.

There really is only two operations directly on the view: The various Identify and SetViewpoint* operations, which all are very specific to working with the view. Pretty much everything else is tucked into objects you can put in your view model, like the Map, GraphicsOverlay, the location datasource, editor etc.

A good equivalent is the ScrollViewer/ListViews: They have view-specific operations for scrolling to a certain item/offset, and very much akin to the SetViewpoint method, and clicking an item is very much identical to the identify operation. None of these you can't really do without some view specific stuff.

Also one thing to consider: IMHO the purist MVVM approach where absolutely no code-behind can ever exist isn't really true to what MVVM is saying. You can have code-behind, as long as that code-behind is view-specific. For example a click handler that performs the identify, and then forwards the result to the view model is still completely ok and within MVVM. Sure you can do all sorts of tricks and jumps to mask that code into various attached properties etc, but you're just adding an enormous amount of complexity only to pretend you moved your view code out of your code behind, when really all you did is move your view code into some other view code, and in the process made your code much harder to follow and understand. It might make sense to do these tricks if you have any different views and want to reuse that same logic over and over again, but always I'd say to weigh the added complexity with the cost.

Reply
0 Kudos