Forum Xamarin.Forms
We are excited to announce that the Xamarin Forums are moving to the new Microsoft Q&A experience. Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

We encourage you to head over to Microsoft Q&A for .NET for posting new questions and get involved today.

Cannot create a user control with two way binding to view model property names

My objective is to create a user control containing a basic Entry control that will update a specified property in my view model.
I have been unable to bind the user control to the property name in my view model unless the view model property name is exactly the same as the property in the user control. This defeats the purpose of a reusable two way reusable control.
I suspect I'm just missing a key concept.

My expectation is that I should be able to add one or more of my UserEditors to a page and bind it to various string properties in the view model, and the property the UserEditor gets/sets should be the property I specify in the {Binding } for the UserEditor.

I have created a simple PCL project to test with. I would be very grateful if someone can get me on the correct track with this. I have exhausted all other resources to come to grips with this. I can provide a zip file of the VS2015 solution, however its pretty big (65MB) with all the XAM stuff.

Here are all the pieces:
UserEditor.xaml:

    <?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="UserControl.UserEditor">
      <ContentView.Content>
            <StackLayout Orientation="Vertical" Margin="0,0,0,10" >
                <Entry x:Name="TextEntry" 
                       Text="{Binding Text}"
                       HorizontalOptions="Fill"/>
            </StackLayout>
        </ContentView.Content>
    </ContentView>

UserEditor.xaml.cs

    using System;
    using System.Linq;

    using Xamarin.Forms;

    namespace UserControl
    {
        public partial class UserEditor : ContentView
        {
            //Bindable property for Text
            public static readonly BindableProperty TextProperty =
                BindableProperty.Create("Text",
                    typeof(string),
                    typeof(UserEditor),
                    string.Empty,
                    BindingMode.TwoWay,
                    null,
                    OnTextPropertyChanged,
                    null,
                    null,
                    null);

            public static void OnTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
            {
                var control = bindable as UserEditor;
                if (control == null) return;
                control.TextEntry.Text = newValue.ToString();
            }

            public UserEditor()
            {
                InitializeComponent();
            }

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

MainPageViewModel.cs

using System;
using System.ComponentModel;
using System.Linq;

namespace UserControl
{
    class MainPageViewModel : INotifyPropertyChanged

    {
        public event PropertyChangedEventHandler PropertyChanged;

        public MainPageViewModel()
        {

        }

        protected void OnPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        string textProperyInVM = "Initial value";
        public string TextPropertyInVm
        {
            get
            {
                return textProperyInVM;
            }
            set
            {
                textProperyInVM = value;
                OnPropertyChanged("TextPropertyInVm");

            }
        }
    }
}

MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:UserControl"
             x:Class="UserControl.MainPage">

    <!--//Page binding context is set to the view model in the page contructor.-->
    <StackLayout Orientation="Vertical">
    <Label Text="Binding viewmodel properties to user control"  />
        <!--//Label is bound to ViewModel field named TextPropertyInVm-->
        <Label Text="{Binding TextPropertyInVm}" ></Label>

        <!--//UserEditor custom control is bound to ViewModel field named TextPropertyInVm
        //User editor will not see the view model property.
        //User editor ALWAYS wants to work with a property named "Text"
        //User editor neither reads nor updates the view model property specified in the Binding -->
        <!--
        Text changes in the user control cause this error:
        [0:] Binding: 'Text' property not found on 'UserControl.MainPageViewModel', target property: 'Xamarin.Forms.Entry.Text'
        -->
        <local:UserEditor Text="{Binding TextPropertyInVM, Mode=TwoWay}"></local:UserEditor>
    </StackLayout>
</ContentPage>

MainPage.xaml.cs

using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace UserControl
{
    public partial class MainPage : ContentPage
    {
        private MainPageViewModel vm;
        public MainPage()
        {
            InitializeComponent();
            //instantiate and bind this page to a new view model.
            vm = new MainPageViewModel();
            this.BindingContext = vm;
        }
    }
}

Best Answer

Answers

  • TimeSliceTimeSlice CAMember

    Just want to correct a casing typo in MainPage.xaml with respect to the name of the property in the view model. It doesn't resolve the problem, but is a necessary correction
    The binding should be:

  • BurtBurt USMember ✭✭

    I have been struggling with this too. Eventually it came down to a better understanding of BindingContexts (plural). After I got it working for my custom control, I split off a base class that also clarifies the point about BindingContexts. You may find it useful too.

    As you can see I have some remaining questions about PropertyChanged, but this works for me. If anyone can clarify those questions for me, I would appreciate.

    using System.ComponentModel;
    using Xamarin.Forms;
    
    namespace Common.Controls
    {
        public abstract class CustomContentView : ContentView, INotifyPropertyChanged
        {
            protected override void OnBindingContextChanged()
            {
                base.OnBindingContextChanged();
    
                // Set the inner BindingContext of the controls to this, while the outer BindingContext of this is set externally.
                Content.BindingContext = this;
            }
    
            public new event PropertyChangedEventHandler PropertyChanged;
    
            // This is needed  for intermediate value changes.
            // An initial binding usually works without, even without being a BindableProperty.
            // TODO This seems superfluous for a BindableProperty.
            protected void RaisePropertyChanged(string propertyName)
            {
                // TODO This does not work for the inherited PropertyChanged.
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    
  • EdgarGonzalezEdgarGonzalez DOMember ✭✭

    You are a life savior, thank you!

  • BurtBurt USMember ✭✭

    I am back to square 2 myself. I hope someone can help out.

    I am struggling with a binding problem, in fact it's already an old problem.
    While searching on the web, I found my own post above, to my surprise.

    In essence the problem is that the binding only works ONE WAY. I should point out that there are 2 binding: one in the usercontrol, one in the enveloping view. When I set a value in the usercontrol, the viewmodel gets updated through the 2 bindings. When I try that the other way around, nothing happens. All bindings are defined twoway.

    More details.

    I have a user control ClearableEntry, based on the CustomContentView above.

    ClearableEntry.xaml

    <Entry Text="{Binding Text, Mode=TwoWay}" />
    

    ClearableEntry.xaml.cs

    public static readonly BindableProperty TextProperty =
        BindableProperty.Create(nameof(Text), typeof(string), typeof(ClearableEntry));
    
    public string Text
    {
        get => (string)GetValue(TextProperty);
        set
        {
            SetValue(TextProperty, value);
            RaisePropertyChanged(nameof(Text));
        }
    }
    

    ClearableEntry is used in a view.
    Note that I have also added a simple Entry for comparison.
    Both use the same binding on the viewmodel.

    <Entry Text="{Binding TextFilterValue, Mode=TwoWay}"/>
    <controls:ClearableEntry Text="{Binding TextFilterValue, Mode=TwoWay}" />
    

    Which is bound to a FilterItemsViewModel with:

        public static readonly BindableProperty TextFilterValueProperty =
            BindableProperty.Create(nameof(TextFilterValue), 
            typeof(string), typeof(FilterItemsViewModel<TItem, TMasterFilterItem, TDetailFilterItem>));
    
        public virtual string TextFilterValue
        {
            get => (string)GetValue(TextFilterValueProperty);
            set
            {
                SetValue(TextFilterValueProperty, value);
                RaisePropertyChanged(nameof(TextFilterValue));
            }
        }
    

    So, when I set a value in either of the 2 controls, the property TextFilterValue is updated.
    When I set TextFilterValue in the viewmodel, only the simple Entry is updated, while the Text property in ClearableEntry is never reached.

    I have read quite some threads about this kind of problem, without ever finding a solution.

    So, the usual question: am I missing something, or is this a bug in Xamarin?

    Any help is appreciated.

  • JoaoRoqueJoaoRoque USMember
    edited October 1

    @Burt I had the same problem. Searched a lot until I found out there is a property on BindableProperty.Create that specifies the default binding mode and it defaults to one way. So either set it to two way in the xaml that calls the user control or specify it's value like this:

    public static readonly BindableProperty EntryTextProperty = BindableProperty.Create(nameof(EntryText), typeof(string), typeof(UserControl), string.Empty, defaultBindingMode: BindingMode.TwoWay);

  • BurtBurt USMember ✭✭

    Thanks @JoaoRoque.

    Just by chance I noticed your reaction today already and I was very hopeful to try it out, as it seemed a plausible explanation.
    Unfortunately it didn't help, yet. I'll keep this in mind to look if there is something else along the line it may apply to.

    I am also checking if it has something to do with an attached behaviour.

Sign In or Register to comment.