Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing MVVM with ArcGIS Runtime local server

I am trying to setup a ESRI Local Server for displaying .mpk. I have a Model like

public class Model
{
    private string basemapLayerUri = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer";
    private string mapPackage = "D:\\App\\Data\\Canada.mpk";
    public Model() { }

    public string BasemapLayerUri
    {
        get { return this.basemapLayerUri; }
        set
        {
            if (value != this.basemapLayerUri)
            {
                this.basemapLayerUri = value;
            }
        }
    }

    public string MapPackage
    {
        get { return this.mapPackage; }
        set
        {
            if (value != this.mapPackage)
            {
                this.mapPackage = value;
            }
        }
    }
}

in ViewModel.cs Class I have

public class ViewModel : INotifyPropertyChanged
{
    public Model myModel { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    public ViewModel()
    {
        myModel = new Model();
        this.CreateLocalServiceAndDynamicLayer();
    }

    public string BasemapUri
    {
        get { return myModel.BasemapLayerUri; }
        set
        {
            this.myModel.BasemapLayerUri = value;
            OnPropertyChanged("BasemapUri");
        }
    }

    public async void CreateLocalServiceAndDynamicLayer()
    {
        LocalMapService localMapService = new LocalMapService(this.MAPKMap);
        await localMapService.StartAsync();

        ArcGISDynamicMapServiceLayer arcGISDynamicMapServiceLayer = new ArcGISDynamicMapServiceLayer()
        {
            ID = "mpklayer",
            ServiceUri = localMapService.UrlMapService,
        };

        //myModel.Map.Layers.Add(arcGISDynamicMapServiceLayer);
    }

    public string MAPKMap
    {
        get { return myModel.MapPackage; }
        set
        {
            this.myModel.MapPackage = value;
            OnPropertyChanged("MAPKMap");
        }
    }

    protected void OnPropertyChanged([CallerMemberName] string member = "")
    {
        var eventHandler = PropertyChanged;
        if (eventHandler != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(member));
        }
    }
}

As you can see I am trying to implement the local server and dynamic layer in ViewModel.cs like

public async void CreateLocalServiceAndDynamicLayer()
{
    LocalMapService localMapService = new LocalMapService(this.MAPKMap);
    await localMapService.StartAsync();

    ArcGISDynamicMapServiceLayer arcGISDynamicMapServiceLayer = new ArcGISDynamicMapServiceLayer()
    {
        ID = "mpklayer",
        ServiceUri = localMapService.UrlMapService,
    };

    //myModel.Map.Layers.Add(arcGISDynamicMapServiceLayer);
}

but I do not know how to bind this service to the Model ? I tried

myModel.Map.Layers.Add(arcGISDynamicMapServiceLayer);

but as you know the myModel doesn't have any Map object.

Update

using M_PK2.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Esri.ArcGISRuntime.LocalServices;
using Esri.ArcGISRuntime.Controls;
using Esri.ArcGISRuntime.Layers;

namespace M_PK2.ViewModels
{
    class ViewModel : ViewModelBase
    {
        private readonly LocalMapService localMapService;
        private readonly Model myModel;
        private LayerCollection layers;

        public ViewModel()
        {
            myModel = new Model();
            layers = new LayerCollection();
            localMapService = new LocalMapService(myModel.MapPackage);
            starting += onStarting;
            starting(this, EventArgs.Empty);
        }

        private event EventHandler starting = delegate { };
        private async void onStarting(object sender, EventArgs args)
        {
            starting -= onStarting; //optional

            // the following runs on background thread
            await localMapService.StartAsync();

            // returned to the UI thread

            var serviceLayer = new ArcGISDynamicMapServiceLayer()
            {
                ID = "mpklayer",
                ServiceUri = localMapService.UrlMapService,
            };

            Layers.Add(serviceLayer);
            OnPropertyChanged(nameof(Layers)); //Notify UI
        }


        public LayerCollection Layers
        {
            get
            {
                return layers;
            }
        }
    }
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        protected void OnPropertyChanged([CallerMemberName] string member = "")
        {
            PropertyChanged(this, new PropertyChangedEventArgs(member));
        }
    }
}
like image 316
Mona Coder Avatar asked Nov 21 '18 20:11

Mona Coder


1 Answers

Avoid using async void except for event handlers,

Reference Async/Await - Best Practices in Asynchronous Programming

In your case you are mixing UI concerns that belong in view. The view model should expose what the view needs in order to perform its function.

Because of the async nature of the used dependency LocalMapService, you should create an async event handler to manage getting the service URI and notify the UI when that task is completed via a bound property change event.

For example

public class ViewModel : ViewModelBase {
    private readonly LocalMapService localMapService;
    private readonly Model myModel;
    private string serviceUri;

    public ViewModel() {
        myModel = new Model();
        localMapService = new LocalMapService(myModel.MapPackage);
        starting += onStarting;
        starting(this, EventArgs.Empty);
    }

    private event EventHandler starting = delegate { };
    private async void onStarting(object sender, EventArgs args) {
        starting -= onStarting; //optional

        // the following runs on background thread
        await localMapService.StartAsync(); 

        // returned to the UI thread
        ServiceUri = localMapService.UrlMapService; //notifies UI
    }

    public string ServiceUri {
        get { return serviceUri; }
        set {
            serviceUri = value;
            OnPropertyChanged();
        }
    }
}

public class ViewModelBase : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    protected void OnPropertyChanged([CallerMemberName] string member = "") {
        PropertyChanged(this, new PropertyChangedEventArgs(member));
    }
}

That way after the async starting of the service, the UI will get notified of the change.

<!-- Add a MapView Control. -->
<esriControls:MapView x:Name="MapView1">

    <!-- Add a Map. -->
    <esriControls:Map>

        <!-- Add an ArcGISDynamicMapServiceLayer via XAML. -->
        <esriLayers:ArcGISDynamicMapServiceLayer ID="mpklayer" 
          ServiceUri="{Bind ServiceUri}"/>
    </esriControls:Map>
</esriControls:MapView>

If the goal is to be able to manipulate multiple layers then I would suggest binding to the Map.Layers Property to be able to have direct access to the layers collection in the view model.

The view model could end up looking like

public class ViewModel : ViewModelBase {
    private readonly LocalMapService localMapService;
    private readonly Model myModel;
    private LayerCollection layers;

    public ViewModel() {
        myModel = new Model();
        layers = new LayerCollection();
        localMapService = new LocalMapService(myModel.MapPackage);
        starting += onStarting;
        starting(this, EventArgs.Empty);
    }

    private event EventHandler starting = delegate { };
    private async void onStarting(object sender, EventArgs args) {
        starting -= onStarting; //optional

        // the following runs on background thread
        await localMapService.StartAsync(); 

        // returned to the UI thread

        var serviceLayer = new ArcGISDynamicMapServiceLayer() {
            ID = "mpklayer",
            ServiceUri = localMapService.UrlMapService,
        };

        Layers.Add(serviceLayer);
    }

    public LayerCollection Layers {
        get {
            return layers;
        }
    }
}

And the view

<!-- Add a MapView Control. -->
<esriControls:MapView x:Name="MapView1">

    <!-- Add a Map. with layers via binding-->
    <esriControls:Map Layers="{Bind Layers, Mode=OneWay}" />
</esriControls:MapView>

You can now manipulate layers via code as needed

like image 110
Nkosi Avatar answered Oct 24 '22 04:10

Nkosi