Reputation: 1788
I have a strange problem with bindings on a custom control. I created a custom toolbar:
public partial class TopToolbar
{
public static readonly BindableProperty BackCommandProperty =
BindableProperty.Create(nameof(BackCommand), typeof(ICommand), typeof(TopToolbar), propertyChanged: BackCommandChanged);
public ICommand BackCommand
{
get => (ICommand) GetValue(BackCommandProperty);
set => SetValue(BackCommandProperty, value);
}
public TopToolbar()
{
InitializeComponent();
}
// for debug purposes only
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
Debug.WriteLine(BindingContext);
}
// for debug purposes only
private static void BackCommandChanged(BindableObject bindable, object oldvalue, object newvalue)
{
Debug.WriteLine($"old: {oldvalue}, new: {newvalue}");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Core.Controls.TopToolbar"
x:Name="TopToolbarView"
BindingContext="{x:Reference TopToolbarView}"
Orientation="Vertical">
<StackLayout Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
<Image Source="{StaticResource Image.Toolbar.LeftArrow}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BackCommand}" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
</StackLayout>
I use it on a page in this way:
<pages:ContentPage.Content>
<StackLayout BackgroundColor="{StaticResource LightGrayColor}"
Spacing="0"
Padding="0">
<controls:TopToolbar Title="Master Data" BackCommand="{Binding MyBackCommand}" />
BindingContext
of the page is a view model:
public class MyCustomersPageModel
{
public RelayCommand MyBackCommand { get; set; }
public MyCustomersPageModel()
{
MyBackCommand = // command creation;
}
}
From the debugging I know that BindingContext
of a control is set (OnBindingContextChanged
called) properly to itself (TopToolbar
object) twice - first time when there's no child views and second time after they are added. I've checked that BindingContext
is correctly propagated in all child controls.
Unfortunately the BackCommand
is not bind at all. The setter of the TopToolbar.BackCommand
is not called even once.
Interestingly when I replace setting the BindingContext
on a control to setting the Souce
directly in bindings everything works fine:
<?xml version="1.0" encoding="UTF-8"?>
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Core.Controls.TopToolbar"
x:Name="TopToolbarView"
Orientation="Vertical">
<StackLayout Orientation="Horizontal"
HorizontalOptions="FillAndExpand"
<Image Source="{StaticResource Image.Toolbar.LeftArrow}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference TopToolbarView}, Path=BackCommand}" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
</StackLayout>
Any clue what I do wrong?
Upvotes: 0
Views: 1086
Reputation: 13591
It is working as expected. I would recommend using Source
.
In first case, when you set BindingContext
on TopToolbar
to itself, then I would imagine this would be the sequence of events:
Custom control is constructed, BindingContext
is assigned to self through reference.
Page instance is created, control is added to it.
When page's BindingContext
is set, through power of property inheritance, all it's child controls' BindingContext
is updated.
At this point your custom control's BindingContext
is still referencing itself as value-propagation doesn't override manually set context.
Therefore, binding <controls:TopToolbar BackCommand="{Binding MyBackCommand}"
fails, as this binding will try to look for MyBackCommand
on it's binding-context which is TopToolbar
.
But, in second case, when you specify binding Source
as TopToolbar
on Tapped
command, then this should be the sequence of events:
Custom control is constructed, BindingContext
is null.
Page instance is created, control is added to it.
When page's BindingContext
is set, through power of property inheritance, all it's child controls' BindingContext
is updated, including your custom control.
At this point your custom control's BindingContext
is now referencing MyCustomersPageModel
. So binding in <controls:TopToolbar BackCommand="{Binding MyBackCommand}"
is appropriately set.
Now the Tapped
binding doesn't care about BindingContext
as it's source is explicitly specified, which is parent control TopToolbar
- whose BackCommand
in turn is bound to the view model's command. Hence, the view-model command is now bound to gesture-recognizer's Tapped
command. And it works!
Upvotes: 3