Bind to Xamarin.Forms.Maps.Map from ViewModel

后端 未结 5 1656
失恋的感觉
失恋的感觉 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: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 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();
            PinsSource.CollectionChanged += PinsSourceOnCollectionChanged;
        }
    
        public ObservableCollection PinsSource
        {
            get { return (ObservableCollection)GetValue(PinsSourceProperty); }
            set { SetValue(PinsSourceProperty, value); }
        }
    
        public static readonly BindableProperty PinsSourceProperty = BindableProperty.Create(
                                                         propertyName: "PinsSource",
                                                         returnType: typeof(ObservableCollection),
                                                         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;
    
            if (thisInstance == null ||
                newPinsSource == null)
                return;
    
            UpdatePinsSource(thisInstance, newPinsSource);
        }
        private void PinsSourceOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            UpdatePinsSource(this, sender as IEnumerable);
        }
    
        private static void UpdatePinsSource(Map bindableMap, IEnumerable 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 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!

提交回复
热议问题