0

I'm having problems binding text to a label inside a custom control.

I have made the following control:

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Frida3.Components.TestView">
  <ContentView.Content>
      <StackLayout>
          <Label Text="{Binding Text}" />
      </StackLayout>
  </ContentView.Content>
</ContentView>

With the following code-behind:

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TestView : ContentView
{
    public TestView ()
    {
        InitializeComponent ();
        BindingContext = this;
    }

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

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

When using the control and a binding to set the Text property, nothing is shown. Below is some sample code of showing the results:

<!-- Shows that the LoginText binding contains value -->
<Label Text="{Binding LoginText}" BackgroundColor="BurlyWood"/>
<!-- Nothing shown with same binding -->
<components:TestView Text="{Binding LoginText}" BackgroundColor="Aqua" />
<!-- Works without binding -->
<components:TestView Text="This is showing" BackgroundColor="Yellow" />

And here is the result of that:

Screenshot

4
  • You're setting the binding context here to the code behind file. So if my understanding is correct, it's looking at the CodeBehind for a property LoginText and failing because it doesn't exist. In the xaml, on the middle one, try setting BindingContext='{Binding ViewModel}' or alternatively, get rid of BindingContext assignment in the constructor. Commented Nov 29, 2018 at 0:01
  • @MaxHampton I tried removing the BindingContext assignment in the constructor, but the result was both Aqua and Yellow views were empty. I think that BindingContext is isolated inside that element, and is required for the binding on the Label. Commented Nov 29, 2018 at 0:05
  • Adding bindings for a custom control creates unnecessarily complex code and potentially affects performance. You're much better off using the HandlePropertyChanged event. Commented Nov 29, 2018 at 13:36
  • @Tom I'm not sure I exactly understand what you mean. Can you provide an example? How else to make reusable controls, if not for binding data to it? Commented Nov 30, 2018 at 12:40

2 Answers 2

1

XAML

Let's take the XAML of your custom control as below:

<ContentView 
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="Frida3.Components.TestView">
    <ContentView.Content>
        <StackLayout>
            <Label Text="{Binding Text}" />
        </StackLayout>
    </ContentView.Content>
</ContentView>

Adding bindings to your custom control adds another level of complexity to your app, which may hinder the app's performance if used excessively. You are better off rewriting the Label as:

<Label x:Name="MyLabel"/>

Now, the Label is accessible in the code-behind.

Code-Behind

You've set up the property correctly in your question as below:

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

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

You can remove the BindingContext = this; from the constructor, because you won't be using any bindings.

In your TextProperty, we're going to add an event for the propertyChanged parameter, and define the event:

public static readonly BindableProperty TextProperty =
    BindableProperty.Create(
        propertyName: nameof(Text), 
        returnType: typeof(string), 
        declaringType: typeof(TestView), 
        defaultValue: default(string),
        propertyChanged: HandleTextPropertyChanged); // Property-changed handler!!

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

// Handler for when the Text property changes.
private static void HandleTextPropertyChanged(
    BindableObject bindable, object oldValue, object newValue)
{
    var control = (TestView)bindable;
    if (control != null)
    {
        control.MyLabel.Text = (string)newValue;
    }
}

What's happening here? Essentially, you've told the app

"When the Text property of TestView changes, set the Text property of MyLabel to the new string value".

Your code should now look like:

// BindableProperty for your Text property
public static readonly BindableProperty TextProperty =
    BindableProperty.Create(
        propertyName: nameof(Text), 
        returnType: typeof(string), 
        declaringType: typeof(TestView), 
        defaultValue: default(string),
        propertyChanged: HandleTextPropertyChanged); // Property-changed handler!!

// Text property of you TestView
public string Text
{
    get => (string) GetValue(TextProperty);
    set => SetValue(TextProperty, value);
}

// Constructor
public TestView ()
{
    InitializeComponent ();
}

// Handler for when the Text property changes.
private static void HandleTextPropertyChanged(
    BindableObject bindable, object oldValue, object newValue)
{
    var control = (TestView)bindable;
    if (control != null)
    {
        control.MyLabel.Text = (string)newValue;
    }
}

You can now call your custom view like so:

<components:TestView 
    Text="This is showing" />

<components:TestView 
    Text="{Binding SomeOtherTextProperty}" />

There you have it! No bindings, just good old-fashioned events.

Sign up to request clarification or add additional context in comments.

5 Comments

Aha! It just hit me that you probably meant like this. And then I came here to check if you replied, and you did - and confirmed my thoughts. Thank you for the in-depth answer!
I ran into another issue with this approach. Maybe it's worth posting another question about. But when the Text property has been set through an app-wide style, the Text property is being set on TestView before MyLabel has been instantiated, which results in a null reference exception on the line setting control.MyLabel.Text = (string)newValue; Adding a null check, would probably mean the text property of the label will not end up with the value that I set in my style.
I thought I could cleverly change my component to be completely C#-based, but ran into the same issue. See more about the same issue here: stackoverflow.com/questions/46523431/… Where a suggested workaround is using bindings. I'm confused :D
I'm assuming as you've marked this as the correct answer you're no longer confused?
I guess I was a bit too quick on the correct answer button. In reality I am so far using the solution I posted on my own answer below. If you have a solution to the problems I posted regarding your answer, then I would love to see it.
1

I found a solution. Basically I give my control a name (x:Name="this"), and I add a source to the data binding, like so: Source={x:Reference this}. With these changes, I can remove BindingContext = this;

So the final xaml will be like so:

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Frida3.Components.TestView"
             x:Name="this">
  <ContentView.Content>
      <StackLayout>
          <Label Text="{Binding Path=Text, Source={x:Reference this}}" />
      </StackLayout>
  </ContentView.Content>
</ContentView>

I would like to know if there's a simpler way, so I don't have to add source to every single child that uses a BindableProperty..

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.