Bind to Xamarin.Forms.Maps.Map from ViewModel

后端 未结 5 1592
失恋的感觉
失恋的感觉 2020-12-29 12:49

I\'m working on a Xamarin.Forms app using a page that displays a map. The XAML is:


    ...

<
相关标签:
5条回答
  • 2020-12-29 13:17

    I have two options which worked for me and which could help you.

    1. You could either add a static Xamarin.Forms.Maps Map property to your ViewModel and set this static property after setting the binding context, during the instantiation of your View, as show below:

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

    This will permit you to access your Map in your ViewModel.

    1. You could pass your Map from your view to the ViewModel during binding, for example:

      <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"/>`
      

    And get the Map behind from the ViewModel's Command.

    0 讨论(0)
  • 2020-12-29 13:18

    What about creating a new Control say BindableMap which inherits from Map and performs the binding updates which the original Map lacks internally. The implementation is pretty straightforward and I have included 2 basic needs; the Pins property and the current MapSpan. Obviously, you can add your own special needs to this control. All you have to do afterward is to add a property of type ObservableCollection<Pin> to your ViewModel and bind it to the PinsSource property of your BindableMap in XAML.

    Here is the 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);
        }
    }
    

    Notes:

    • I have omitted the using statements and namespace declaration for the sake of simplicity.
    • In order for our original Pins property to be updated as we add members to our bindable PinsSource property, I declared the PinsSource as ObservableCollection<Pin> and subscribed to its CollectionChanged event. Obviously, you can define it as an IList if you intend to only change the whole value of your bound property.

    My final word regarding the 2 first answers to this question:

    Although having a View control as a ViewModel property exempts us from writing business logic in code behind, but it still feels kind of hacky. In my opinion, the whole point of (well, at least a key point in) the VM part of the MVVM is that it is totally separate and decoupled from the V. Whereas the solution provided in the above-mentioned answers is actually this:

    Insert a View Control into the heart of your ViewModel.

    I think this way, not only you break the MVVM pattern but also you break its heart!

    0 讨论(0)
  • 2020-12-29 13:19

    If you don't want to break the MVVM pattern and still be able to access your Map object from the ViewModel then you can expose the Map instance with a property from your ViewModel and bind to it from your View.

    Your code should be structured like described here below.

    The ViewModel:

    using Xamarin.Forms.Maps;
    
    namespace YourApp.ViewModels
    {
        public class MapViewModel
        {
            public MapViewModel()
            {
                Map = new Map();
            }
    
            public Map Map { get; private set; }
        }
    }
    

    The View (in this example I'm using a ContentPage but you can use whatever you like):

    <?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>
    

    I didn't show how, but the above code snipped can only work when the ViewModel is the BindingContext of your view.

    0 讨论(0)
  • 2020-12-29 13:27

    I don't think Pins is a bindable property on Map, you may want to file feature request at Xamarin's Uservoice or the fourm here: http://forums.xamarin.com/discussion/31273/

    0 讨论(0)
  • 2020-12-29 13:30

    It is not ideal, but you could listen for the property changed event in the code behind and then apply the change from there. Its a bit manual, but it is doable.

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

    And then define the "yourPropertyChanged" method

    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
            });
        }
    }
    
    0 讨论(0)
提交回复
热议问题