从ViewModel绑定到Xamarin.Forms.Maps.Map

Tho*_*ler 8 xaml dictionary mvvm xamarin.forms

我正在使用显示地图的页面处理Xamarin.Forms应用程序.XAML是:

<maps:Map x:Name="Map">
    ...
</maps:Map>
Run Code Online (Sandbox Code Playgroud)

我知道可以从页面的代码隐藏中访问地图,如下所示:

var position = new Position(37.79762, -122.40181);
Map.MoveToRegion(new MapSpan(position, 0.01, 0.01));
Map.Pins.Add(new Pin
{
    Label = "Xamarin",
    Position = position
});
Run Code Online (Sandbox Code Playgroud)

但是因为这段代码会打破应用程序的MVVM架构,所以我更喜欢Map从我的ViewModel 访问该对象,而不是直接从View /页面访问 - 直接使用它,就像在上面的代码中一样,或者通过数据绑定到它的属性.

有人知道如何做到这一点吗?

Pej*_*man 9

如何创建一个新的 Control 说它BindableMap继承自 Map 并执行原始Map内部缺少的绑定更新。实现非常简单,我已经包含了 2 个基本需求;该Pins属性和当前MapSpan。显然,您可以将自己的特殊需求添加到该控件中。之后您要做的就是将 type 属性添加ObservableCollection<Pin>到您的 ViewModel 并将其绑定到您BindableMap在 XAML 中的 PinsSource 属性。

这是 BindableMap:

public class BindableMap : Map
{

    public BindableMap()
    {
        PinsSource = new ObservableCollection<Pin>();
        PinsSource.CollectionChanged += PinsSourceOnCollectionChanged;
    }

    public ObservableCollection<Pin> PinsSource
    {
        get { return (ObservableCollection<Pin>)GetValue(PinsSourceProperty); }
        set { SetValue(PinsSourceProperty, value); }
    }

    public static readonly BindableProperty PinsSourceProperty = BindableProperty.Create(
                                                     propertyName: "PinsSource",
                                                     returnType: typeof(ObservableCollection<Pin>),
                                                     declaringType: typeof(BindableMap),
                                                     defaultValue: null,
                                                     defaultBindingMode: BindingMode.TwoWay,
                                                     validateValue: null,
                                                     propertyChanged: PinsSourcePropertyChanged);


    public MapSpan MapSpan
    {
        get { return (MapSpan)GetValue(MapSpanProperty); }
        set { SetValue(MapSpanProperty, value); }
    }

    public static readonly BindableProperty MapSpanProperty = BindableProperty.Create(
                                                     propertyName: "MapSpan",
                                                     returnType: typeof(MapSpan),
                                                     declaringType: typeof(BindableMap),
                                                     defaultValue: null,
                                                     defaultBindingMode: BindingMode.TwoWay,
                                                     validateValue: null,
                                                     propertyChanged: MapSpanPropertyChanged);

    private static void MapSpanPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var thisInstance = bindable as BindableMap;
        var newMapSpan = newValue as MapSpan;

        thisInstance?.MoveToRegion(newMapSpan);
    }
    private static void PinsSourcePropertyChanged(BindableObject bindable, object oldvalue, object newValue)
    {
        var thisInstance = bindable as BindableMap;
        var newPinsSource = newValue as ObservableCollection<Pin>;

        if (thisInstance == null ||
            newPinsSource == null)
            return;

        UpdatePinsSource(thisInstance, newPinsSource);
    }
    private void PinsSourceOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        UpdatePinsSource(this, sender as IEnumerable<Pin>);
    }

    private static void UpdatePinsSource(Map bindableMap, IEnumerable<Pin> newSource)
    {
        bindableMap.Pins.Clear();
        foreach (var pin in newSource)
            bindableMap.Pins.Add(pin);
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 为简单起见,我省略了 using 语句和命名空间声明。
  • 为了在我们Pins向可绑定PinsSource属性添加成员时更新我们的原始属性,我声明了PinsSourceasObservableCollection<Pin>并订阅了它的CollectionChanged事件。显然,IList如果您打算只更改绑定属性的整个值,则可以将其定义为 an 。

关于这个问题的第 2 个答案,我的最后一句话是:

尽管将 View 控件作为 ViewModel 属性可以免除我们在后面的代码中编写业务逻辑的麻烦,但仍然感觉有点 hacky。在我看来,MVVM 的 VM 部分的重点(嗯,至少是其中的一个关键点)是它与 V 完全分离和解耦。 而上述答案中提供的解决方案实际上是这样的:

将视图控件插入到您的 ViewModel 的核心。

我认为这样,不仅你打破了 MVVM 模式,而且你也伤了它的心!


小智 8

如果您不想破坏MVVM模式并且仍然能够从ViewModel访问Map对象,则可以使用ViewModel的属性公开Map实例,并从View绑定到该实例。

您的代码的结构应如下文所述。

ViewModel:

using Xamarin.Forms.Maps;

namespace YourApp.ViewModels
{
    public class MapViewModel
    {
        public MapViewModel()
        {
            Map = new Map();
        }

        public Map Map { get; private set; }
    }
}
Run Code Online (Sandbox Code Playgroud)

视图(在此示例中,我使用a,ContentPage但是您可以使用任何您喜欢的视图):

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="YourApp.Views.MapView">
    <ContentPage.Content>
                <!--The map-->
                <ContentView Content="{Binding Map}"/>
    </ContentPage.Content>
</ContentPage>
Run Code Online (Sandbox Code Playgroud)

我没有显示如何操作,但是上面的代码片段只有在ViewModel是BindingContext视图的视图时才有效。

  • 这应该是公认的答案。对于那些不了解它的人-通过绑定到属性,我们仍然受益于XAML根据类型对事物进行解释的方式。比其他任何解决方案都更好的解决方案。 (2认同)
  • 完美的解决方案。谢谢@Aquablue (2认同)

Pra*_*dda 1

我不认为Pins是 上的可绑定属性Map,您可能需要在 Xamarin 的Uservoice或此处的论坛上提交功能请求: http: //forums.xamarin.com/discussion/31273/