使用 ArcGIS Runtime 本地服务器实现 MVVM

Implementing MVVM with ArcGIS Runtime local server

我正在尝试设置一个 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.csClass我有

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 没有任何地图对象。

更新

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

我没有可供试用的 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>

避免使用 async void 除了事件处理程序

引用Async/Await - Best Practices in Asynchronous Programming

在您的情况下,您正在混合 UI 属于视图的问题。视图模型应该公开视图执行其功能所需的内容。

由于使用的依赖项 LocalMapService 的异步性质,您应该创建一个异步事件处理程序来管理获取服务 URI 并在该任务完成时通过绑定 UI 通知 UI =41=]更改事件。

例如

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

这样在服务异步启动后,UI 将收到更改通知。

<!-- 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 Property 以便能够直接访问视图模型中的图层集合。

视图模型最终可能看起来像

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>

您现在可以根据需要通过代码操作图层