Hello,
I want to be able to register a mouse click event and then plot a point graphic at that location. Right now I get the mouse click, get the location of that mouse click and then use those coordinates to create a graphic. After doing all this the point still does not display onto the map..
//MainWindow.xaml.cs
private void MainMapView_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
MapViewModel mvm = new MapViewModel();
Point p = e.GetPosition(MainMapView);
MapPoint mp = MainMapView.ScreenToLocation(e.GetPosition(MainMapView));
mvm.createPoint(mp);
}
//MapViewModel.cs
public void createPoint(MapPoint point)
{
// Create a new graphics overlay to contain a variety of graphics.
var graphicsOverlay = new GraphicsOverlay();
// Add the overlay to a graphics overlay collection.
GraphicsOverlayCollection overlays = new GraphicsOverlayCollection
{
graphicsOverlay
};
// Set the view model's "GraphicsOverlays" property (will be consumed by the map view).
this.GraphicsOverlays = overlays;
var mapPoint = new MapPoint(point.X, point.Y, SpatialReferences.Wgs84);
// Create a symbol to define how the point is displayed.
var pointSymbol = new SimpleMarkerSymbol
{
Style = SimpleMarkerSymbolStyle.X,
Color = System.Drawing.Color.Red,
Size = 10.0
};
// Add an outline to the symbol.
pointSymbol.Outline = new SimpleLineSymbol
{
Style = SimpleLineSymbolStyle.Solid,
Color = System.Drawing.Color.Red,
Width = 2.0
};
// Create a point graphic with the geometry and symbol.
var pointGraphic = new Graphic(mapPoint, pointSymbol);
// Add the point graphic to graphics overlay.
graphicsOverlay.Graphics.Add(pointGraphic);
}
Solved! Go to Solution.
Hi,
It looks like there are a few problems in the code. If you are using the default basemaps, the map's spatial reference will be WebMercator. Currently, the code is creating a new point with WebMercator coordinates while having a WGS84 spatial reference.
var mapPoint = new MapPoint(point.X, point.Y, SpatialReferences.Wgs84);
should change to
var mapPoint = new MapPoint(point.X, point.Y, point.SpatialReference);
But note that this step shouldn't be necessary at all. You could reference `point` directly without creating `mapPoint`.
I also recommend a few other changes:
Consider using `GeoViewTapped` instead of `MouseLeftButtonUp`, as that will save you from needing to call `ScreenToLocation` https://developers.arcgis.com/net/wpf/api-reference/html/E_Esri_ArcGISRuntime_UI_Controls_GeoView_Ge... GeoViewInputEventArgs has a Location property.
I hope that helps. Let me know if you have any other questions.
When a graphic is added to a graphics overlay that is already in a mapview or sceneview, the graphic will be shown automatically, assuming there is a renderer or the graphic is configured with a symbol.
Based on the code you shared, it looks like you're following the pattern set in the template projects and the developer tutorials. If that is the case, you will see something like the following in your MainWindow.xaml file:
<Window.Resources>
<local:MapViewModel x:Key="MapViewModel" />
</Window.Resources>
That code will create an instance of your viewmodel when InitializeComponent is called.
The MapView's Map and GraphicsOverlays properties are set via binding:
<esri:MapView x:Name="MainMapView"
Map="{Binding Map, Source={StaticResource MapViewModel}}"
GraphicsOverlays="{Binding GraphicsOverlays, Source={StaticResource MapViewModel}}" />
The view is bound to the specific instance of the viewmodel that is added to the `Window.Resources` dictionary. Your code is creating another instance of viewmodel, but that instance is never connected to the view in any way, so the view won't update to reflect changes in viewmodel state. You could fix this by re-setting up bindings when you create the viewmodel, or by using the existing viewmodel instance created in XAML.
I recommend updating your code to reference the copy of the ViewModel created in XAML:
You could use something like the following to get a reference to the viewmodel:
mvm = FindResource("MapViewModel") as MapViewModel;
Hi,
It looks like there are a few problems in the code. If you are using the default basemaps, the map's spatial reference will be WebMercator. Currently, the code is creating a new point with WebMercator coordinates while having a WGS84 spatial reference.
var mapPoint = new MapPoint(point.X, point.Y, SpatialReferences.Wgs84);
should change to
var mapPoint = new MapPoint(point.X, point.Y, point.SpatialReference);
But note that this step shouldn't be necessary at all. You could reference `point` directly without creating `mapPoint`.
I also recommend a few other changes:
Consider using `GeoViewTapped` instead of `MouseLeftButtonUp`, as that will save you from needing to call `ScreenToLocation` https://developers.arcgis.com/net/wpf/api-reference/html/E_Esri_ArcGISRuntime_UI_Controls_GeoView_Ge... GeoViewInputEventArgs has a Location property.
I hope that helps. Let me know if you have any other questions.
@NathanCastle1 Thank you for those suggestions. Most of it is clear and makes sense as to why they would be better practice/necessary changes to be made.
Is there anything I need to do to call update to the view after creating this point? Or once I add it to the graphics overlay with graphicsOverlay.Graphics.Add(pointGraphic); will it automatically update the view?
Another thing, I have the two classes MainWindow.xaml/MW.xaml.cs and MapViewModel.cs. MainWindow in it's constructor calls InitializeComponent(); which in turns eventually calls the constructor of MapViewModel as part of setup. And then when I create an object of MapViewModel so that I can access the createPoint() method I call the constructor of MapViewModel again which will call setup() which just assigns the basemap type again. Would that mess this up?
I can include code snippets of this if needed.
How are you setting the data context? You should not need to have the code where you new up the ViewModel
//MainWindow.xaml.cs
public partial class MainWindow : Window
{
private MapViewModel mvm;
public MainWindow()
{
InitializeComponent();
mvm = new MapViewModel();
MapPoint mapCenterPoint = new MapPoint(-117.6709, 35.6225, SpatialReferences.Wgs84);
MainMapView.SetViewpoint(new Viewpoint(mapCenterPoint, 100000));
}
private void MainMapView_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point p = e.GetPosition(MainMapView);
MapPoint mp = MainMapView.ScreenToLocation(e.GetPosition(MainMapView));
mvm.createPoint(mp);
}
}
//MapViewModel.cs
class MapViewModel : INotifyPropertyChanged
{
public MapViewModel()
{
SetupMap();
}
private void SetupMap()
{
// Create a new map with a 'topographic vector' basemap.
Map = new Map(BasemapStyle.ArcGISTopographic);
}
What is in your Xaml? Somewhere you must be setting the data context of the view. This would be why it runs the constructor. Are you using any MVVM libraries or just core WPF?
When a graphic is added to a graphics overlay that is already in a mapview or sceneview, the graphic will be shown automatically, assuming there is a renderer or the graphic is configured with a symbol.
Based on the code you shared, it looks like you're following the pattern set in the template projects and the developer tutorials. If that is the case, you will see something like the following in your MainWindow.xaml file:
<Window.Resources>
<local:MapViewModel x:Key="MapViewModel" />
</Window.Resources>
That code will create an instance of your viewmodel when InitializeComponent is called.
The MapView's Map and GraphicsOverlays properties are set via binding:
<esri:MapView x:Name="MainMapView"
Map="{Binding Map, Source={StaticResource MapViewModel}}"
GraphicsOverlays="{Binding GraphicsOverlays, Source={StaticResource MapViewModel}}" />
The view is bound to the specific instance of the viewmodel that is added to the `Window.Resources` dictionary. Your code is creating another instance of viewmodel, but that instance is never connected to the view in any way, so the view won't update to reflect changes in viewmodel state. You could fix this by re-setting up bindings when you create the viewmodel, or by using the existing viewmodel instance created in XAML.
I recommend updating your code to reference the copy of the ViewModel created in XAML:
You could use something like the following to get a reference to the viewmodel:
mvm = FindResource("MapViewModel") as MapViewModel;
@NathanCastle1 Thank you for your explanation. It was very clear and helpful. I was following the template project while trying to look things up that it did not explain (the InitializeComponent method being one of those things).
Assuming @NathanCastle1 is correct, which is what I also assume and why I asked about what is in the Xaml. I'm going to go out on a limb and assume the DataContext is being set in the Xaml also. However, without seeing the Xaml I cannot be sure exactly what is being done but I am going with this assumption.
One nice MVVM approach to adding functionality, imo, is using TriggerActions. Especially if you are not using an external library to handle Commanding and Eventing
Based on the code you have above we could create an Action that handles the GeoViewTapped and then creates the point.
public class CreatePointAction : TriggerAction<MapView>
{
private static bool _doubleTapped;
protected override void OnAttached()
{
base.OnAttached();
if (!(AssociatedObject is MapView mapView)) return;
//This seems silly to me, but a GeoViewDoubleTapped fires both a tapped and a double tapped.
//This indicate was double tapped
mapView.GeoViewDoubleTapped += (s, e) => { _doubleTapped = true; };
}
protected override async void Invoke(object parameter)
{
await Task.Delay(250);
if (_doubleTapped)
{
_doubleTapped = false;
return;
}
if (!(AssociatedObject is MapView mapView)) return;
if (!(parameter is GeoViewInputEventArgs args)) return;
//I'm just going to assume only the one layer
var graphicsOverlay = mapView.GraphicsOverlays.FirstOrDefault();
if ( graphicsOverlay == null ) return;
AddPoint(graphicsOverlay, args.Location);
}
private void AddPoint(GraphicsOverlay graphicsOverlay, MapPoint mapPoint)
{
var pointSymbol = new SimpleMarkerSymbol
{
Style = SimpleMarkerSymbolStyle.X,
Color = System.Drawing.Color.Red,
Size = 10.0,
Outline = new SimpleLineSymbol
{
Style = SimpleLineSymbolStyle.Solid, Color = System.Drawing.Color.Red, Width = 2.0
}
};
// Create a point graphic with the geometry and symbol.
var pointGraphic = new Graphic(mapPoint, pointSymbol);
// Add the point graphic to graphics overlay.
graphicsOverlay.Graphics.Add(pointGraphic);
}
}
To wire this in we add the Action as an EventTrigger in our MapView definition
<esri:MapView x:Name="MapView" Map="{Binding Map}" GraphicsOverlays="{Binding GraphicsOverlays}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GeoViewTapped">
<local:CreatePointAction></local:CreatePointAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</esri:MapView>
Similar to what you are already doing you can setup the view point in the View constructor. But we do not need to instantiate the VM, this was done and we are not using the VM in any code in the code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MapPoint mapCenterPoint = new MapPoint(-117.6709, 35.6225, SpatialReferences.Wgs84);
MainMapView.SetViewpoint(new Viewpoint(mapCenterPoint, 100000));
}
}
And in the ViewModel the map initialization can be done as before
public class MapViewModel
{
protected GraphicsOverlay EditGraphicsOverlay { get; } = new GraphicsOverlay { Id = nameof(EditGraphicsOverlay) };
public MapViewModel()
{
SetupMap();
AddGraphicsOverlays();
}
private void SetupMap()
{
// Create a new map with a 'topographic vector' basemap.
Map = new Map(BasemapStyle.ArcGISTopographic);
}
private void AddGraphicsOverlays()
{
if (!GraphicsOverlays.Contains(EditGraphicsOverlay))
{
GraphicsOverlays.Add(EditGraphicsOverlay);
}
}
}
With that we should have everything wired up. There is no functional code in code behind all logic is in the Action and ViewModel. The Action could be reused in another project without need to change anything, just include the definition in the MapView xaml
That is what my MainWindow.xaml looks like.
<Window x:Class="map_display.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:map_display"
xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:MapViewModel x:Key="MapViewModel" />
</Window.Resources>
<Grid>
<esri:MapView x:Name="MainMapView"
Map="{Binding Map, Source={StaticResource MapViewModel}}"
GraphicsOverlays="{Binding GraphicsOverlays, Source={StaticResource MapViewModel}}"
MouseLeftButtonUp ="MainMapView_MouseLeftButtonUp" />
</Grid>
</Window>