从 ViewModel 绑定到 Xamarin.Forms.Maps.Map
Posted
技术标签:
【中文标题】从 ViewModel 绑定到 Xamarin.Forms.Maps.Map【英文标题】:Bind to Xamarin.Forms.Maps.Map from ViewModel 【发布时间】: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
对象,而不是直接从视图/页面访问 - 可以像上面的代码一样直接使用它,也可以通过数据绑定到它的属性。
有人知道如何做到这一点吗?
【问题讨论】:
【参考方案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<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 语句和命名空间声明。 为了在我们将成员添加到可绑定的PinsSource
属性时更新我们的原始Pins
属性,我将PinsSource
声明为ObservableCollection<Pin>
并订阅了它的CollectionChanged
事件。显然,如果您打算只更改绑定属性的整个值,则可以将其定义为 IList
。
关于这个问题的 2 个第一个答案,我的最后一句话:
虽然将 View 控件作为 ViewModel 属性可以让我们免于在代码中编写业务逻辑,但它仍然感觉有点 hacky。在我看来,MVVM 的 VM 部分(嗯,至少是关键点)的全部意义在于它与 V 完全分离并解耦。而上述答案中提供的解决方案实际上是这样的:
将 View Control 插入 ViewModel 的核心。
我认为这样,不仅你打破了 MVVM 模式,而且你也伤了它的心!
【讨论】:
我认为您的回答在这里最有意义。我实现了这一点,但在 SetValue(PinsSourceProperty, value); 中没有得到任何值。我在视图中使用INotifyPropertyChanged
,你的IList<Pin>
属性是否在其NotifyPropertyChanged()
部分调用NotifyPropertyChanged()
?
@Pejman 我知道这篇文章很久以前了,但是如何使用自定义弹窗实现自定义引脚?
@Stefan0309 好吧,基本上每当您想要自定义 Xamarin.Forms 组件超出控件提供的属性时,您有 2 个选择:1) 寻找满足您需求的第三方控件或 2)为控件创建一个CustomRenderer
。如果你不熟悉CustomRenderer
,你可以开始here。
我和@MickeyDhami 有同样的问题。有人可以帮我吗?【参考方案3】:
我有两个选项对我有用并且可以帮助你。
您可以将静态Xamarin.Forms.Maps Map
属性添加到您的ViewModel
并在设置绑定上下文后设置此静态属性,在您的视图实例化期间,如下所示:
public MapsPage()
InitializeComponent();
BindingContext = new MapViewModel();
MapViewModel.Map = MyMap;
这将允许您在 ViewModel 中访问您的地图。
您可以在绑定期间将您的地图从您的视图传递给 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】:我不认为 Pins
是 Map
上的可绑定属性,您可能希望在 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
);
【讨论】:
以上是关于从 ViewModel 绑定到 Xamarin.Forms.Maps.Map的主要内容,如果未能解决你的问题,请参考以下文章
从 ViewModel 绑定到 Xamarin.Forms.Maps.Map