Data binding and passing object to custom control.

TK33TK33 USMember

Let say I created a custom control to replace

<ContentView BackgroundColor="{Binding Blah.Type}">
    <Label Text="{Binding Blah.Name}" TextColor="{Binding Blah.Color}" />
</ContentView>

by

<local:CustomLabel Text="{Binding Person.Name}" Type="{StaticResource inverse}" />

So my custom label xaml would look like:

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" x:Class="Blah.CustomLabel" 
    BackgroundColor="{Binding Type}">
    <Label x:Name="Label2" Text="{Binding Text}" TextColor="{Binding TextColor}" />
</ContentView>

and my custom label class would look like:

public partial class CustomLabel : ContentView {

        public CustomLabel()
        {
            InitializeComponent();
            BindingContext = this;
        }

        public static readonly BindableProperty TextProperty =
            BindableProperty.Create("Text", typeof(string), 
                typeof(CustomLabel), string.Empty);

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set{ SetValue(TextProperty, value); }
        }

        public static readonly BindableProperty TextColorProperty =
            BindableProperty.Create("TextColor", typeof(Color), typeof(CustomLabel), Color.FromHex("#fff"));

        public Color TextColor
        {
            get { return (Color)GetValue(TextColorProperty); }
            set{ SetValue(TextColorProperty, value); }
        }

        public static readonly BindableProperty TypeProperty =
           BindableProperty.Create("Type", typeof(Color), typeof(CustomLabel), Color.FromHex("#999"));

        public Color Type
        {
            get { return (Color)GetValue(TypeProperty); }
            set { SetValue(TypeProperty, value); }
        }
    }

I set BindingContext = this to force the ContentView BindingContext to be this view so the binding work.
I can add this custom control to my view by

<local:CustomLabel Text="Something!" TextColor="Red" />

This work just fine, my problem is when passing or attach a bindable object to the custom control. Ex:

<local:CustomLabel Text="{Binding Person.Name}" TextColor="Red" />

The TextProperty always null.

Best Answer

Answers

  • TK33TK33 USMember

    Any help on this?

  • TK33TK33 USMember

    @adamkemp said:
    Your CustomLabel can only have one BindingContext at a time. When you set the BindingContext in your constructor to this then it will work as long as someone else doesn't end up setting it to something else later. But if you're setting bindings in the XAML that includes the custom control then you must have a BindingContext set for that to work, and that is overriding the one you set. In other words, the problem is that the ContentView in CustomLabel.xaml is the same object as the <local:CustomLabel ... /> in your other XAML, which is the same object as the CustomLabel itself. There's only one BindingContext property involved, and it can't have two values.

    There are a few ways to resolve this. One is to introduce another wrapper control in your XAML:

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentView xmlns="http://xamarin.com/schemas/2014/forms" x:Class="Blah.CustomLabel">
        <Grid x:Name="_content" BackgroundColor="{Binding Type}">
            <Label x:Name="Label2" Text="{Binding Text}" TextColor="{Binding TextColor}" />
        </Grid>
    </ContentView>
    

    Then set the binding context of that to this in your constructor:

        public CustomLabel()
        {
            InitializeComponent();
            _content.BindingContext = this;
        }
    

    Now you have two binding contexts: one for your CustomLabel itself (set to your view model) and one for your custom control's content, and this works because they are set on two different objects.

    Another way to handle this is to use the extra argument in the BindableProperty.Create method to supply a handler for when the value changes, and use that to explicitly set the values in the view. There is an example of that approach here. This approach is more verbose, but it is more efficient (skips several layers of indirection), and I think less confusing than the multiple binding contexts approach.

    Personally I've never been a fan of setting the BindingContext to this. That's not how the MVVM pattern is supposed to work. You're supposed to bind to a view model, not to a view. I think people tend to just confuse themselves when they mix the two approaches because you can't really tell by looking at a {Binding} whether that connects to a view model or something else.

    @adamkemp Thanks for you help. Creating a view model for the control and using propertyChanged event to set the values work perfectly. Thanks again!

Sign In or Register to comment.