I\'m working on a Xamarin.Forms app using a page that displays a map. The XAML is:
...
<
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:
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!