bit
bit

Reputation: 4487

Data binding fails for a custom Xamarin forms control but works for a normal one

I have a simple custom control, which I use in a ContentPage (actually a LoginPage) in my Xamarin forms app. The custom control has a property called EntryText which I am trying to bind to the LoginViewModel's Email property.

If I bind Email with the custom control's EntryText, it does not seem to work. However, if I bind Email to a normal control like Entry's Text, it does work correctly. What am I doing wrong here?

My custom control:

public class FlakeEntry2 : StackLayout
{
    public Entry Entry;
    public Label ErrorLabel;

    public FlakeEntry2()
    {
        Entry = new Entry { };
        Children.Add(Entry);

        ErrorLabel = new Label
        {
            FontAttributes = FontAttributes.Italic,
            TextColor = Color.Red,
        };
        Children.Add(ErrorLabel);
    }

    #region Text property which I am trying to bind

    public static readonly BindableProperty EntryTextProperty = BindableProperty.Create(
                                                   propertyName: nameof(EntryText),
                                                   returnType: typeof(string),
                                                   declaringType: typeof(FlakeEntry2),
                                                   defaultValue: null,
                                                   defaultBindingMode: BindingMode.TwoWay,
                                                   propertyChanged: EntryTextPropertyChanged);

    public string EntryText
    {
        get { return GetValue(EntryTextProperty)?.ToString(); }
        set { SetValue(EntryTextProperty, value); }
    }

    private static void EntryTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var control = (FlakeEntry2)bindable;
        control.Entry.Text = newValue?.ToString();
    }

    #endregion
}

The View (LoginPage) where I try to bind looks like this:

<StackLayout Orientation="Vertical" Spacing="10">
    <custom:FlakeEntry2 x:Name="Email" EntryText="{Binding Email}" /> <!--This does not work-->
    <Entry Text="{Binding Email}"/> <!--This works-->
</StackLayout>

Finally the ViewModel is pretty much standard and implements INotifyPropertyChanged. The Email property of which looks like:

private string _email;
public string Email
{
    get { return _email; }
    set
    {
        _email = value;
        NotifyPropertyChanged();
    }
}

I have tried adding a XAML based custom control (current one is just C# based). I have tried explicitly adding BindingContext={Binding} to the custom control used in the LoginPage. I have read numerous blogs but to no avail. Can someone please point me in the right direction?

Upvotes: 0

Views: 1156

Answers (1)

Diego Rafael Souza
Diego Rafael Souza

Reputation: 5313

I've heard there are some things a bit different between the WPF and XF bindings and even its XAML syntaxes but I don't know much about WPF.

Whatever.

How about to use cascade bindings and take control of your component avoiding exposing its inner views?

I always use this approach and works pretty fine for me, see if it fit's to you too:

public class FlakeEntry2 : StackLayout
{
    private Entry Entry;
    private Label ErrorLabel;

    public FlakeEntry2()
    {
        Entry = new Entry { };

        ErrorLabel = new Label
        {
            FontAttributes = FontAttributes.Italic,
            TextColor = Color.Red,
        };

        this.Entry.SetBinding(Entry.TextProperty, new Binding(nameof(EntryText), source: this));
        // You'll need to do the same to label's and other properties you need expose, but you get rid of the 'OnChanged' methods

        Children.Add(Entry);
        Children.Add(ErrorLabel);
    }

    #region Text property which I am trying to bind

    public static readonly BindableProperty EntryTextProperty = BindableProperty.Create(
                                                   propertyName: nameof(EntryText),
                                                   returnType: typeof(string),
                                                   declaringType: typeof(FlakeEntry2),
                                                   defaultValue: null,
                                                   defaultBindingMode: BindingMode.TwoWay);
    public string EntryText
    {
        get { return GetValue(EntryTextProperty)?.ToString(); }
        set { SetValue(EntryTextProperty, value); }
    }

    #endregion
}

Nothing on your XAML nor VM should be changed.

Upvotes: 4

Related Questions