【问题标题】:Bind to Xamarin.Forms.Maps.Map from ViewModel从 ViewModel 绑定到 Xamarin.Forms.Maps.Map
【发布时间】:2015-03-21 18:58:30
【问题描述】:

我正在使用显示地图的页面开发 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 对象,而不是直接从视图/页面访问 - 可以像上面的代码一样直接使用它,也可以通过数据绑定到它的属性。

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

【问题讨论】:

    标签: xaml dictionary mvvm xamarin.forms


    【解决方案1】:

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

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

    视图模型:

    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 时才能工作。

    【讨论】:

    • 这应该是公认的答案。对于那些不明白的人——通过绑定到一个属性,我们仍然受益于 XAML 基于类型来表示事物的方式。比其他任何解决方案都好得多。
    • 完美解决方案。谢谢@Aquablue
    【解决方案2】:

    创建一个新的控件怎么样?比如说BindableMap,它继承自 Map 并执行原始Map 内部缺乏的绑定更新。实现非常简单,我已经包含了 2 个基本需求; Pins 属性和当前 MapSpan。显然,您可以在此控件中添加自己的特殊需求。之后您所要做的就是将 ObservableCollection&lt;Pin&gt; 类型的属性添加到您的 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 语句和命名空间声明。
    • 为了在我们将成员添加到可绑定的PinsSource 属性时更新我们的原始Pins 属性,我将PinsSource 声明为ObservableCollection&lt;Pin&gt; 并订阅了它的CollectionChanged 事件。显然,如果您打算只更改绑定属性的整个值,则可以将其定义为 IList

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

    虽然将 View 控件作为 ViewModel 属性可以让我们免于在代码中编写业务逻辑,但它仍然感觉有点 hacky。在我看来,MVVM 的 VM 部分(嗯,至少是关键点)的全部意义在于它与 V 完全分离并解耦。而上述答案中提供的解决方案实际上是这样的:

    将 View Control 插入 ViewModel 的核心。

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

    【讨论】:

    • 我认为您的回答在这里最有意义。我实现了这一点,但在 SetValue(PinsSourceProperty, value); 中没有得到任何值。我在视图中使用 并且 mappins 是在 VM 内设置的 IList 。我做错了吗?
    • @MickeyDhami 你的虚拟机是否实现了INotifyPropertyChanged,你的IList&lt;Pin&gt; 属性是否在其NotifyPropertyChanged() 部分调用NotifyPropertyChanged()
    • @Pejman 我知道这篇文章很久以前了,但是如何使用自定义弹窗实现自定义引脚?
    • @Stefan0309 好吧,基本上每当您想要自定义 Xamarin.Forms 组件超出控件提供的属性时,您有 2 个选择:1) 寻找满足您需求的第三方控件或 2)为控件创建一个CustomRenderer。如果你不熟悉CustomRenderer,你可以开始here
    • 我和@MickeyDhami 有同样的问题。有人可以帮我吗?
    【解决方案3】:

    我有两个选项对我有用并且可以帮助你。

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

      public MapsPage()
          {
              InitializeComponent();
              BindingContext = new MapViewModel();
              MapViewModel.Map = MyMap;
          }
      

    这将允许您在 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"/>`
      

    并从 ViewModel 的命令中获取地图。

    【讨论】:

      【解决方案4】:

      我不认为 PinsMap 上的可绑定属性,您可能希望在 Xamarin 的 Uservoice 或此处的四个地址处提交功能请求:http://forums.xamarin.com/discussion/31273/

      【讨论】:

        【解决方案5】:

        是的,Map.Pins 是不可绑定的,但是有ItemsSource,而是easy to use

           <maps:Map ItemsSource="{Binding Locations}">
                <maps:Map.ItemTemplate>
                    <DataTemplate>
                        <maps:Pin Position="{Binding Position}"
                                  Label="{Binding Name}"
                                  Address="{Binding Subtitle}" />
        

        因此,仅对于引脚,MVVM 可以在没有任何自定义控件的情况下完成。 但Map.MoveToRegion()(和Map.VisibleRegion 阅读)仍然开放。应该有办法绑定它们。为什么不在一个读/写属性中? (答案:因为无限循环。)

        注意:如果你在启动时只需要Map.MoveToRegion一次,可以在构造函数中设置区域。

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-12-18
            • 2021-10-20
            • 1970-01-01
            相关资源
            最近更新 更多