XAML 绑定到Xamarin.窗体.Map.来自ViewModel的Map

sbdsn5lh  于 2022-12-07  发布在  其他
关注(0)|答案(6)|浏览(129)

我正在开发一个Xamarin.Forms应用程序,它使用一个显示Map的页面。XAML是:

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

我知道可以从页面的代码隐藏访问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/页面访问--要么像上面的代码那样直接使用它,要么通过数据绑定到它的属性。
有人知道怎么做吗?

5ssjco0h

5ssjco0h1#

如果您不想破坏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时才能工作。

jecbmhm3

jecbmhm32#

创建一个新的控件,比如说BindableMap,它继承自Map并执行绑定更新,而原始的Map内部缺少这些。Pins属性和当前的MapSpan。显然,您可以将自己的特殊需要添加到此控件中。之后您所要做的就是将ObservableCollection<Pin>类型的属性添加到ViewModel中,并将其绑定到XAML中BindableMap的PinsSource属性。
下面是绑定Map:

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

关于这个问题的前两个答案,我的最后一句话是:
尽管将View控件作为ViewModel属性使我们不必在代码后面编写业务逻辑,但它仍然让人感觉有点笨拙。在我看来,MVVM的VM部分的全部要点(至少是其中的一个关键点)是它与V完全分离和解耦。而上述答案中提供的解决方案实际上是这样的:
将View控件插入ViewModel的中心。
我认为这样做,你不仅打破了MVVM的模式,而且你打破了它的心!

9ceoxa92

9ceoxa923#

我有两个选择,对我有效,对你也有帮助。
1.您可以将静态Xamarin.Forms.Maps Map属性添加到您的ViewModel,并在设置绑定上下文后,在视图示例化期间设置此静态属性,如下所示:

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

这将允许您在ViewModel中访问Map。
1.您可以在绑定期间将Map从视图传递到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的命令中获取Map。

egmofgnx

egmofgnx4#

是的,Map.Pins是不可绑定的,但是有ItemsSource,它很容易使用。

<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,则可以在构造函数中设置该区域。

wrrgggsh

wrrgggsh5#

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

dgsult0t

dgsult0t6#

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

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

相关问题