Property binding w/ custom control using MVVM design - 'Sequence contains no matching element'

SpencerEvansSpencerEvans USMember

I'm getting an error 'System.InvalidOperationException - Sequence contains no matching element' when trying to property bind a custom control to my view model.

My page:

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="HelloWorld.MyPage"
    xmlns:local="clr-namespace:HelloWorld;"
    BackgroundColor="Gray">

    <ContentPage.BindingContext>
        <local:MyViewModel />
    </ContentPage.BindingContext>

    <ContentPage.Content>
        <StackLayout VerticalOptions="Center" BackgroundColor="White" >
            <Entry Text="{Binding Name}" />
            <local:MyCustomControl Text="{Binding Email}" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

My custom control:

public class MyCustomControl : StackLayout
{
    protected Entry entry;

    public MyCustomControl ()
    {
        Orientation = StackOrientation.Horizontal;

        entry = new Entry () {
            HorizontalOptions = LayoutOptions.FillAndExpand
        };
        entry.PropertyChanged += EntryPropertyChanged;

        Children.Add (entry);
    }

    public static readonly BindableProperty textProperty = BindableProperty.Create ("Text", typeof(string), typeof(MyCustomControl), "", BindingMode.TwoWay, null, handleTextChanged);
    public string Text {
        get { return (string)GetValue (textProperty); }
        set { SetValue (textProperty, value); }
    }

    private static void handleTextChanged (BindableObject bindable, object oldValue, object newValue)
    {
        ((MyCustomControl)bindable).entry.Text = newValue.ToString();
    }

    private void EntryPropertyChanged (object sender, PropertyChangedEventArgs e) {
        if (e.PropertyName == Entry.TextProperty.PropertyName) {
            Text = ((Entry)sender).Text;
        }
    }
}

And my view model:

public class MyViewModel : INotifyPropertyChanged
{
    private string name;
    public string Name {
        get { return name; }
        set { name = value; OnPropertyChanged ("Name"); }
    }
    private string email;
    public string Email {
        get { return email; }
        set { email = value; OnPropertyChanged ("Email"); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged (string propertyName) {
        Debug.WriteLine ("property \'" + propertyName + "\' changed!");

        if (PropertyChanged != null) {
            PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
        }
    }
}

I've tried implementing a few solutions from similar posts (notably, those by @GeorgeCook & @adamkemp here and here) without success. Notably, I followed this one exactly, but still no dice. Am I missing something?

Full code here on github.

Best Answer

Answers

  • SpencerEvansSpencerEvans USMember

    Thanks for your quick reply, @adamkemp.

    You were right - changing the name to TextProperty fixed the error "Sequence contains no matching element".

    After that, I got a NullPointerException at ((MyCustomControl)bindable).entry.Text = newValue.ToString(); in MyCustomControl. It appears that because I wasn't giving name and email (in MyViewModel) default values, they were being set to null. In turn, handleTextChanged was being passed a newValue of null.

    I set name="" and email="" as empty strings at the time of declaration, which fixed that problem.

    Thanks for your help!

    (I'll commit the fixes for anyone who would like to see the solution.)

  • adamkempadamkemp mod USInsider, Developer Group Leader mod

    Your bindable property should probably handle null values being set, though. I suggest you make that work without changing the view model.

  • SpencerEvansSpencerEvans USMember

    Ah, yeah I forgot to mention I did that too.

    MyCustomControl control = bindable as MyCustomControl;
    if (control != null) {
        if (newValue != null)
            control.entry.Text = newValue.ToString();
    }
    

    Thanks again!

  • shyjumadathilshyjumadathil ✭✭ USUniversity ✭✭
    edited September 2016

    @adamkemp , Getting an infinite loop when I tried the same

    The following line of code is calling the handleTextChanged method again. (xamarin forms 2.2)

    Text = ((Entry)sender).Text;

    Please help

  • adamkempadamkemp mod USInsider, Developer Group Leader mod

    Two-way bindings are tricky. You should only push the value back to the control if the value is different from what the control already has.

Sign In or Register to comment.