Using SketchEditor in MVVM

1066
2
11-10-2020 05:16 AM
ModyBuchbinder
Esri Regular Contributor

Hi all

I have a program in runtime that uses MVVM.

I would like to use SketchEditor in one of the commands to draw a line on screen.

The map is send to the model but not the MapView.

I found that in the xml I can add SketchEditor="{binding Draw}" but I am not sure how to use it.

Anybody have a code sample?

Thanks

0 Kudos
2 Replies
JenniferNery
Esri Regular Contributor

There may be a few SketchEditor commands that you can specify in XAML (like Complete/Cancel/Undo/Redo/Delete). However, you can only begin a draw in code-behind using one of the StartAsync methods. Based on SketchCreation mode, shape is drawn based on MapView events. For example, tap/click for point or adding vertices in polyline/polygon, drag/move for freehand, arrow, rectangle, triangle, circle, ellipse shapes.

Few XAML example code would be to set DataContext to MapView's SketchEditor and use Command Binding.

            <Grid DataContext="{Binding ElementName=MyMapView,Path=SketchEditor}">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <CheckBox IsChecked="{Binding IsEnabled, Mode=TwoWay}"
                      Content="IsEnabled" />
                <CheckBox IsChecked="{Binding IsVisible, Mode=TwoWay}"
                          Content="IsVisible"
                          Grid.Column="1" />
                <Slider Minimum="0"
                        Maximum="1"
                        Value="{Binding Opacity, Mode=TwoWay}"
                        Grid.ColumnSpan="2"
                        Grid.Row="1" />
                <Button x:Name="AddButton"
                        Content="Add"
                        Command="{Binding AddCommand}"
                        Grid.Row="2" />
                <Button Content="Delete"
                        Command="{Binding DeleteCommand}"
                        Grid.Row="2"
                        Grid.Column="1" />
                <Button Content="Undo"
                        Command="{Binding UndoCommand}"
                        Grid.Row="3" />
                <Button Content="Redo"
                        Command="{Binding RedoCommand}"
                        Grid.Row="3"
                        Grid.Column="1" />
                <Button Content="Cancel"
                        Command="{Binding CancelCommand}"
                        Grid.Row="4" />
                <Button Content="Complete"
                        Command="{Binding CompleteCommand}"
                        Grid.Row="4"
                        Grid.Column="1" />
            </Grid>

If you have provided SketchEditorConfiguration in resource, you can access this by key and update it's properties using XAML-binding too.

    <Window.Resources>        
        <esri:SketchEditConfiguration x:Key="MySketchEditConfiguration" />
    </Window.Resources>


            <StackPanel DataContext="{StaticResource MySketchEditConfiguration}">
                <ComboBox x:Name="ResizeModes"
                          SelectedItem="{Binding ResizeMode, Mode=TwoWay}" />
                <CheckBox IsChecked="{Binding RequireSelectionBeforeDrag, Mode=TwoWay}"
                          Content="RequireSelectionBeforeDrag" />
                <CheckBox IsChecked="{Binding AllowMove, Mode=TwoWay}"
                          Content="AllowMove" />
                <ComboBox x:Name="VertexEditModes"
                          SelectedItem="{Binding VertexEditMode, Mode=TwoWay}" />
                <CheckBox IsChecked="{Binding AllowVertexEditing, Mode=TwoWay}"
                          Content="AllowVertexEditing" />
                <CheckBox IsChecked="{Binding AllowRotate, Mode=TwoWay}"
                          Content="AllowRotate" />
            </StackPanel>x‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Depending whether you are creating a new shape or editing an existing geometry, you may call any of the StartAsync methods.

var editConfig = this.Resources["MySketchEditConfiguration"] as SketchEditConfiguration;
var geometry = await MyMapView.SketchEditor.StartAsync(creationMode, editConfig);
0 Kudos
JoeHershman
MVP Regular Contributor

I use a different approach and is based on the idea that one may want to manage drawing in a view other than the main view.  I believe the approach from @JenniferNery would only work in the view model tied to the view that contains MapView.  There is also an assumption that a library which provides event aggregation is being used (e.g., MVVMLight although I use Prism).  One might argue is more complex, but I believe it offers a way to provide a true MVVM solution in a complex application that can go beyond a single view.

I have a custom MapView control, which has it's own ViewModel.  My Xaml (with only the relevant pieces) looks like this

 

<esri:MapView xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="http://prismlibrary.com"
             xmlns:esri="clr-namespace:Esri.ArcGISRuntime.Xamarin.Forms;assembly=Esri.ArcGISRuntime.Xamarin.Forms"
             xmlns:behaviors="clr-namespace:Mobile.AsBuilt.Framework.MapView.Behaviors;assembly=Mobile.AsBuilt.Framework"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="Mobile.AsBuilt.Framework.MapView.MapViewControl"
             x:Name="mapView" 

             Map="{Binding Map}"
                   SketchEditor="{Binding SketchEditor}"
                   GraphicsOverlays="{Binding GraphicsOverlays}"               
</esri:MapView>

 

In the supporting ViewModel I inject the IEventAggregator, also one thing I found is that the bound SketchEditor needs to be initialized in the constructor .  

 

public MapViewControlViewModel(IEventAggregator eventAggregator, ...)
{
	eventAggregator.GetEvent<MapLoadedEvent>().Subscribe(OnMapLoaded);

	EventAggregator = eventAggregator;
	SketchEditor = new SketchEditor();
}

 

The idea now is that the SketchEditor will be sent out to the rest of the application by publishing an event.  There is one other caveat, and that is we can not send off our SketchEditor until the map in initialized.

So we need a couple events.  One event that can be published to the application to indicate the Map is loaded (MapLoadedEvent), and another that can send the SketchEditor out (SketchEditorInitializedEvent).  We put a handler for our MapLoaded event in the MapViewControlViewModel and then in our handler publish out the SketchEditorInitializedEvent

 

private void OnMapLoaded(MapLoadedEventArgs obj)
{
	try
	{
		Map = obj.Map;
		EventAggregator.GetEvent<SketchEditorInitializedEvent>().Publish(SketchEditor);
	}
	catch (Exception e)
	{
		Log.Error(e, e.Message);
	}
}

 

Now anywhere I need the SketchEditor I can handle this event

 

protected EditBaseViewModel(IEventAggregator eventAggregator...) : base(eventAggregator)
{
	SubscriptionTokens.Add(EventAggregator.GetEvent<SketchEditorInitializedEvent>().Subscribe(se => SketchEditor = se));     
}

 

This will then support a modular application with the ability to draw from any ViewModel that provides this functionality

 

Thanks,
-Joe
0 Kudos