Binding to a custom control

I'm having difficulty binding my ViewModel to a custom control. On the same View, binding to a regular control works but the custom control is blank.

Here's the custom control:

public class CustomEntry : StackLayout
{
    protected Entry entry;

    public CustomEntry ()
    {
        entry = new Entry ();

        Children.Add (entry);
    }

    public static readonly BindableProperty TextProperty=BindableProperty.Create<CustomEntry, string>( p => p.Text, "" );

    public String Text {
        get { return entry.Text; }
        set { entry.Text = value; }
    }
}

And the view:

<?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="TestMVVM.TestPage"
    xmlns:mv="clr-namespace:TestMVVM"
    Padding="10, 20">
    <ContentPage.BindingContext>
        <mv:TestViewModel />
    </ContentPage.BindingContext>
    <StackLayout>
        <Entry Text="{Binding TestValue}" />
        <mv:CustomEntry Text="{Binding TestValue}" />
    </StackLayout>
</ContentPage>

And the viewmodel:

public class TestViewModel : INotifyPropertyChanged
{
    double testValue;

    public event PropertyChangedEventHandler PropertyChanged;

    public TestViewModel ()
    {
        var random = new Random ();
        testValue = random.NextDouble ();
    }

    public double TestValue 
    {
        set {
            if (testValue != value) {
                testValue = value;
                OnPropertyChanged ("TestValue");
            }
        }
        get {
            return testValue;
        }
    }

    protected virtual void OnPropertyChanged (string propertyName) {
        if (PropertyChanged != null) {
            PropertyChanged (this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

I put the full code up on GitHub: [https://github.com/andybarilla/BindableCustomTest]

Best Answers

Answers

  • GeorgeCookGeorgeCook PEUniversity ✭✭✭

    @mhutch or anyone else in xamarin, I think this is indicative that you guys need to better document how xaml bindings work. I'll raise a bug about it.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    I talked to someone ahat Xamarin this week about improving some of their Xamarin.Forms documentation. They have some ideas, and they're working on it.

  • AndyBarillaAndyBarilla USMember
    edited April 2015

    I did see see that other conversation but hadn't realized that it was the same problem. I was following the documentation here: [http://developer.xamarin.com/guides/cross-platform/xamarin-forms/Xaml-for-Xamarin-forms/data_bindings_to_MVVM/ ] instead.

    I changed my code and that does work for one way binding but not two way. But I'll play around with that myself before posting a follow up question. I'll also keep my GitHub repo out there reference once I update it.

  • AndyBarillaAndyBarilla USMember

    I implemented the changes from the above referenced thread and made some additional changes to get the two way binding working. The GitHub repo I linked above has the full working example.

    public class CustomEntry : StackLayout
    {
        protected Entry entry;
    
        public CustomEntry ()
        {
            entry = new Entry ();
            entry.PropertyChanged += EntryPropertyChanged;
            Children.Add (entry);
        }
    
        public static readonly BindableProperty TextProperty =
            BindableProperty.Create<CustomEntry, string>(
                p => p.Text,
                "",
                BindingMode.TwoWay,
                null,
                new BindableProperty.BindingPropertyChangedDelegate<string> (HandleTextChanged));
    
        public string Text {
            get { return (string)GetValue(TextProperty); }
            set { 
                SetValue (TextProperty, value);
                entry.Text = value;
            }
        }
    
        static void HandleTextChanged (BindableObject bindable, object oldValue, object newValue) {
            ((CustomEntry)bindable).Text = newValue.ToString();
        }
    
        void EntryPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e) {
            if (e.PropertyName == "Text") {
                Text = ((Entry)sender).Text;
                OnPropertyChanged ("Text");
            }
        }
    }
    
  • adamkempadamkemp USInsider, Developer Group Leader mod

    There are a few mistakes in that code. First, the setter for the Text property shouldn't do anything extra because it's not going to get called in every instance. It's not supposed to. So the line in there that sets the Entry text should not be there.

    The HandleTextChanged method should also not call the setter. That is potentially recursive. It's probably safe because the value won't change when you set it again, but it's doing extra work that it shouldn't do. That method should instead just have the line that I said to delete from the setter. That is, this method is where you should poke the new value into the entry. That's the only thing you should do in this method.

    Lastly, the property changed handler doesn't need to call OnPropertyChanged. SetValue does that for you. You don't want to raise the event twice for the same value.

  • AndyBarillaAndyBarilla USMember

    I tried changing the code as per your suggestions but it caused them to not work. I tried various combinations but I was only able to remove the call to OnPropertyChanged and still have it work.

  • AndyBarillaAndyBarilla USMember

    Ok. I messed up the line in HandleTextChanged.

    All good now. Thanks for the help.

Sign In or Register to comment.