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

时间:2015-01-22 20:24:01

标签: xaml dictionary mvvm xamarin.forms

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

<maps:Map x:Name="Map">
    ...
</maps:Map>

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

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

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

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

5 个答案:

答案 0 :(得分:5)

如果您不想破坏MVVM模式并且仍然能够从ViewModel访问您的Map对象,那么您可以使用ViewModel中的属性公开Map实例,并从View中绑定它。 / p>

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

ViewModel:

using Xamarin.Forms.Maps;

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

        public Map Map { get; private set; }
    }
}

视图(在此示例中,我使用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>

我没有说明如何,但上面的代码剪辑只能在ViewModel是您视图的BindingContext时才能生效。

答案 1 :(得分:4)

我有两个选项对我有用,可以帮到你。

  1. 您可以向Xamarin.Forms.Maps Map添加静态ViewModel属性,并在View实例化期间设置绑定上下文后设置此静态属性,如下所示:

    public MapsPage()
        {
            InitializeComponent();
            BindingContext = new MapViewModel();
            MapViewModel.Map = MyMap;
        }
    
  2. 这将允许您在ViewModel中访问您的地图。

    1. 您可以在绑定过程中将地图从视图传递到ViewModel,例如:

      <maps:Map
          x:Name="MyMap"
          IsShowingUser="true"
          MapType="Hybrid" />
      <StackLayout Orientation="Horizontal" HorizontalOptions="Center">
          <Button x:Name="HybridButton" Command="{Binding MapToHybridViewChangeCommand}" 
                  CommandParameter="{x:Reference MyMap}"
                  Text="Hybrid" HorizontalOptions="Center" VerticalOptions="Center" Margin="5"/>`
      
    2. 从ViewModel的命令中获取Map。

答案 2 :(得分:2)

关于创建一个新控件,说BindableMap,该控件从Map继承并执行内部Map原始的绑定更新。实现非常简单,我已经包括了2个基本需求。 Pins属性和当前MapSpan。显然,您可以向此控件添加自己的特殊需求。之后,您要做的就是将类型ObservableCollection<Pin>的属性添加到ViewModel并将其绑定到XAML中BindableMap的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);
    }
}

注意:

  • 为简单起见,我省略了using语句和名称空间声明。
  • 为了在将成员添加到可绑定的Pins属性时更新原始的PinsSource属性,我将PinsSource声明为ObservableCollection<Pin>并订阅了{ {1}}事件。显然,如果您只想更改绑定属性的整个值,则可以将其定义为CollectionChanged

关于此问题的2个最先答案的我的最后决定:

尽管将View控件作为ViewModel属性可以使我们免于在后面的代码中编写业务逻辑,但是仍然感到有些hacky。我认为,MVVM的VM部分(至少是其中的关键点)的全部要点是,它与V完全分离并分离。而上述答案中提供的解决方案实际上是这样的:

  

将View控件插入ViewModel的心脏。

我认为,不仅破坏了MVVM模式,而且破坏了它的心脏!

答案 3 :(得分:1)

我认为Pins不是Map上的可绑定属性,您可能希望在Xamarin的Uservoice或此处的四分之一处提交功能请求:http://forums.xamarin.com/discussion/31273/

答案 4 :(得分:0)

这不是理想的,但您可以在后面的代码中侦听属性更改事件,然后从那里应用更改。它有点手册,但它是可行的。

((ViewModels.YourViewModel)BindingContext).PropertyChanged += yourPropertyChanged;

然后定义“yourPropertyChanged”方法

private void yourPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if(e.PropertyName == "YourPropertyName")
    {
        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
        });
    }
}