Внедрение MVVM с локальным сервером ArcGIS Runtime

Я пытаюсь настроить локальный сервер ESRI для отображения .mpk. У меня есть модель, как

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;
            }
        }
    }
}

в ViewModel.cs классе у меня есть

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));
        }
    }
}

Как видите, я пытаюсь реализовать локальный сервер и динамический слой в ViewModel.cs, например

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);
}

но я не знаю как привязать этот сервис к Model ? Я попытался

myModel.Map.Layers.Add(arcGISDynamicMapServiceLayer);

но, как вы знаете, у myModel нет объекта Map.

Обновить

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));
        }
    }
}

person Mona Coder    schedule 21.11.2018    source источник
comment
Глядя на пример кода и документы говорит нам, что ArcGISDynamicMapServiceLayer является компонентом пользовательского интерфейса и принадлежит представлению. Модель представления должна предоставлять только ID и ServiceUri.   -  person Funk    schedule 05.12.2018
comment
Спасибо, Фанк, но это больше касается загрузки ArcGISDynamicMapServiceLayer, а не самого сервисного уровня! проблема заключается в том, где заполнить это устройство в модели или модели представления, не нарушая роли MVVM.   -  person Mona Coder    schedule 06.12.2018


Ответы (2)


Избегайте использования async void за исключением обработчиков событий,

Справочник по Async/Await: лучшие практики асинхронного программирования

В вашем случае вы смешиваете проблемы пользовательского интерфейса, которые относятся к виду. Модель представления должна раскрывать то, что необходимо представлению для выполнения своей функции.

Из-за асинхронного характера используемой зависимости LocalMapService вам следует создать обработчик асинхронного события, чтобы управлять получением URI службы и уведомлять пользовательский интерфейс о завершении этой задачи с помощью связанного события изменения свойства.

Например

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));
    }
}

Таким образом, после асинхронного запуска службы пользовательский интерфейс получит уведомление об изменении.

<!-- 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>

Если цель состоит в том, чтобы иметь возможность манипулировать несколькими слоями, я бы предложил привязку к Свойство Map.Layers, чтобы иметь прямой доступ к коллекции слоев в модели представления.

Модель представления может выглядеть как

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;
        }
    }
}

И вид

<!-- 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>

Теперь вы можете манипулировать слоями с помощью кода по мере необходимости.

person Nkosi    schedule 07.12.2018
comment
Спасибо, Нкоси, мне нравится твоя идея с LayerCollection, но похоже, что это ничего не добавляет на карту! похоже, что async void onStarting() вообще не срабатывает, потому что, когда я пытаюсь его отладить, ничего не происходит при установке точки останова - person Mona Coder; 07.12.2018
comment
Я обновил представление до <esriControls:Map Layers="{Binding Source={StaticResource VM}, Path=Layers}"/>, и теперь оно работает. - person Mona Coder; 08.12.2018
comment
Спасибо, но еще один вопрос, что именно делает starting += onStarting; starting(this, EventArgs.Empty);? - person Mona Coder; 08.12.2018
comment
starting += onStarting; подписывает событие на обработчик событий. starting(this, EventArgs.Empty); на самом деле вызывает событие. - person Nkosi; 08.12.2018
comment
@MonaCoder все это, как я указал в ответе, позволяет вам правильно вызывать асинхронный API LocalMapService - person Nkosi; 08.12.2018

У меня нет SDK, чтобы попробовать, но следующий код должен работать:

Модель просмотра:

private readonly LocalMapService localMapService;

// initialize localMapService instance in the constructor 

public string UrlMapService
{
    get { return localMapService.UrlMapService; }
}

XAML:

<!-- A Map ControlView to display various GIS layers. -->
<esriControls:MapView x:Name="MapView1" Width="448" Height="480" VerticalAlignment="Top" Margin="2,2,2,2">

    <!-- A Map. -->
    <esriControls:Map  x:Name="Map1" >

        <!-- Add an ArcGISDynamicMapServiceLayer via Xaml. Set the ID and ImageFormat properties. -->
        <esriLayers:ArcGISDynamicMapServiceLayer ID="serviceLayer" ImageFormat="PNG24" 
                ServiceUri="{Binding UrlMapService, Mode=OneWay}"/>
    </esriControls:Map>
</esriControls:MapView>
person Dipen Shah    schedule 05.12.2018
comment
Спасибо, Дипен, я могу видеть `localMapService.UrlMapService;` в режиме отладки, но во время выполнения все еще получаю пустую белую страницу на карте. Нет ошибок - person Mona Coder; 06.12.2018
comment
@MonaCoder обязательно позвоните localMapService.StartAsync(). Вы можете вызвать его вручную на виртуальной машине в коде позади r, чтобы связать команду с событием загрузки окна. - person Dipen Shah; 06.12.2018