问题
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?
回答1:
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,
BindingContextis assigned to self through reference.Page instance is created, control is added to it.
When page's
BindingContextis set, through power of property inheritance, all it's child controls'BindingContextis updated.At this point your custom control's
BindingContextis 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 forMyBackCommandon it's binding-context which isTopToolbar.
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,
BindingContextis null.Page instance is created, control is added to it.
When page's
BindingContextis set, through power of property inheritance, all it's child controls'BindingContextis updated, including your custom control.At this point your custom control's
BindingContextis now referencingMyCustomersPageModel. So binding in<controls:TopToolbar BackCommand="{Binding MyBackCommand}"is appropriately set.Now the
Tappedbinding doesn't care aboutBindingContextas it's source is explicitly specified, which is parent controlTopToolbar- whoseBackCommandin turn is bound to the view model's command. Hence, the view-model command is now bound to gesture-recognizer'sTappedcommand. And it works!
来源:https://stackoverflow.com/questions/46113142/binding-issue-in-custom-xamarin-forms-control