Xamarin Forms Binding Issue

Dave Lyons 1 Reputation point
2021-08-27T08:26:19.863+00:00

Hey,

Currently trying to write an app in Xamarin Forms. I decided to create some custom controls so I could extend functionality of some such as the SwitchCell doesn't allow you to change the Label colour where as the EntryCell does. All renders totally fine. The issue I'm having is with the data binding. It seems that even if I set the binding explicitly it defaults back to binding to the control itself. The issue I'm getting is that the 'page/view' I'm creating (in the sample below the register view' I'm binding the view model to the tableview (this works), but then as there is custom binding over the top this is where I have issues. The issue is that controls recognise the model (I've hooked up reflection to look at the models data annotations for the validation) and validate correctly however never put the value into the property on the model. Here is the code (yes I know I'm not doing MVVM, found it very cumbersome for doing any UI changes so I do this hybrid).

Register.xaml

 <ContentPage.Content> 
        <controls:StackLayout Spacing="0" Padding="0"> 
            <TableView x:Name="tvView" Intent="Settings" BackgroundColor="WhiteSmoke"> 
                <TableRoot> 
                    <TableSection Title="Authentication"> 
                        <controls:EntryCell Label="Email Address" Placeholder="Email Address" Validate="true" Keyboard="Email" HorizontalTextAlignment="End" BindingProperty="EmailAddress" /> 
                        <controls:EntryCell Label="Password" Placeholder="Password" Validate="true" IsPassword="True" HorizontalTextAlignment="End" BindingProperty="Password" /> 
                        <controls:EntryCell Label="Confirm Password" Placeholder="Confirm Password" Validate="true" IsPassword="True" HorizontalTextAlignment="End" BindingProperty="ConfirmPassword" /> 
                    </TableSection> 
                </TableRoot> 
            </TableView> 
        </controls:StackLayout> 
    </ContentPage.Content> 

Register.xaml.cs

[XamlCompilation(XamlCompilationOptions.Compile)] 
    public partial class Register : ContentPage 
    { 
        private RegisterUserDTO _model; 

        public Register() 
        {    
            InitializeComponent(); 

            _model = new RegisterUserDTO(); 
            tvView.BindingContext = _model; 

        } 
    } 

EntryCell.xaml

<ViewCell xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
          xmlns:controls="clr-namespace:SaS.Mobile.Controls;assembly=SaS.Mobile.Controls" 
          x:Class="SaS.Mobile.Controls.EntryCell" 
          x:Name="SaSEntryCell"> 
    <controls:StackLayout Orientation="Horizontal" Margin="20,5,10,5"> 
        <controls:Label x:Name="lblName"  
                        Text="{Binding  Label, Source={x:Reference SaSEntryCell}}" 
                        TextColor="{Binding TextColor, Source={x:Reference SaSEntryCell}}" 
                        VerticalOptions="CenterAndExpand" 
                        HorizontalOptions="Start" /> 

        <controls:Entry x:Name="txtEntry" HorizontalOptions="FillAndExpand" 
                        VerticalOptions="CenterAndExpand" 
                        BorderStyle="None" 
                        HorizontalTextAlignment="End" 
                        Text="{Binding Text, Source={x:Reference SaSEntryCell}}" 
                        Placeholder="{Binding Placeholder, Source={x:Reference SaSEntryCell}}" 
                        Keyboard="{Binding Keyboard, Source={x:Reference SaSEntryCell}}" /> 
    </controls:StackLayout> 
</ViewCell> 

EntryCell.xaml.cs

 public partial class EntryCell : ViewCell 
    { 
        public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(EntryCell), null, BindingMode.TwoWay); 

        public static readonly BindableProperty LabelProperty = BindableProperty.Create("Label", typeof(string), typeof(EntryCell), null, BindingMode.TwoWay); 

        public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create("Placeholder", typeof(string), typeof(EntryCell), null); 

        public static readonly BindableProperty LabelColorProperty = BindableProperty.Create("LabelColor", typeof(Color), typeof(EntryCell), Color.Default, BindingMode.TwoWay); 

        public static readonly BindableProperty KeyboardProperty = BindableProperty.Create("Keyboard", typeof(Keyboard), typeof(EntryCell), Keyboard.Default); 

        public static readonly BindableProperty HorizontalTextAlignmentProperty = BindableProperty.Create("HorizontalTextAlignment", typeof(TextAlignment), typeof(EntryCell), TextAlignment.Center); 

        public static readonly BindableProperty VerticalTextAlignmentProperty = BindableProperty.Create("VerticalTextAlignment", typeof(TextAlignment), typeof(EntryCell), TextAlignment.Center); 

        public static readonly BindableProperty ValidationTypeProperty = BindableProperty.Create("Validate", typeof(bool), typeof(EntryCell), false); 

        public static readonly BindableProperty IsPasswordProperty = BindableProperty.Create("IsPassword", typeof(bool), typeof(EntryCell), false); 

        private string _bindingProperty; 

        public TextAlignment HorizontalTextAlignment 
        { 
            get => (TextAlignment)GetValue(HorizontalTextAlignmentProperty); 
            set => SetValue(HorizontalTextAlignmentProperty, value); 
        } 

        public TextAlignment VerticalTextAlignment 
        { 
            get => (TextAlignment)GetValue(VerticalTextAlignmentProperty); 
            set => SetValue(VerticalTextAlignmentProperty, value); 
        } 

        public Keyboard Keyboard 
        { 
            get => (Keyboard)GetValue(KeyboardProperty); 
            set => SetValue(KeyboardProperty, value); 
        } 

        public string Label 
        { 
            get => (string)GetValue(LabelProperty); 
            set => SetValue(LabelProperty, value); 
        } 

        public Color LabelColor 
        { 
            get => (Color)GetValue(LabelColorProperty); 
            set => SetValue(LabelColorProperty, value); 
        } 

        public string Placeholder 
        { 
            get => (string)GetValue(PlaceholderProperty); 
            set => SetValue(PlaceholderProperty, value); 
        } 

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

        public bool Validate 
        { 
            get => (bool)GetValue(ValidationTypeProperty); 
            set => SetValue(ValidationTypeProperty, value); 
        } 

        public bool IsPassword 
        { 
            get => (bool)GetValue(IsPasswordProperty); 
            set => SetValue(IsPasswordProperty, value); 
        } 

        public string BindingProperty 
        { 
            get => _bindingProperty; 
            set 
            { 
                _bindingProperty = value; 
                this.SetBinding(Xamarin.Forms.EntryCell.TextProperty, value, BindingMode.TwoWay); 
            } 
        } 

        public EntryCell() 
        { 
            InitializeComponent(); 
        } 

        protected override void OnBindingContextChanged () 
        { 
            base.OnBindingContextChanged (); 

            if (BindingContext != null) 
            { 
                lblName.Text = Label; 
                lblName.TextColor = LabelColor; 

                txtEntry.Text = Text; 
                txtEntry.Placeholder = Placeholder; 
                txtEntry.Keyboard = Keyboard; 
            } 
        } 

        protected override void OnPropertyChanged(string propertyName = null) 
        { 
            switch (propertyName) 
            { 
                case nameof(Text): 
                    if (Validate) 
                    { 
                        LabelColor = IsValid() ? Color.Default : Color.Red; 
                    } 
                    break; 
            } 

            base.OnPropertyChanged(propertyName);         
        } 

        private bool IsValid() 
        { 
            var valid = true; 

            var theProperty = BindingContext.GetType().GetProperty(_bindingProperty); 
            var theAttributes = theProperty.GetCustomAttributes().Where(a => a.GetType().IsClass && !a.GetType().IsAbstract && a.GetType().IsSubclassOf(typeof(ValidationAttribute))); 
            var propertyValue = theProperty.GetValue(BindingContext, null)?.ToString() ?? string.Empty;; 

            foreach (var theAttribute in theAttributes) 
            { 
                var validationAttribute = (ValidationAttribute)theAttribute; 

                switch (validationAttribute.ToString()) 
                { 
                    case "System.ComponentModel.DataAnnotations.CompareAttribute": 
                        var compareAttribute = (CompareAttribute)validationAttribute; 
                        var otherProperty = BindingContext.GetType().GetProperty(compareAttribute.OtherProperty); 
                        var otherValue = otherProperty.GetValue(BindingContext, null)?.ToString() ?? string.Empty; 

                        if (propertyValue != otherValue) 
                            valid = false; 

                        break; 
                    default: 
                        if (!validationAttribute.IsValid(propertyValue)) 
                        { 
                            valid = false; 
                        } 
                        break; 
                } 
            } 

            return valid; 
        } 
    } 
Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,366 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. JarvanZhang 23,961 Reputation points
    2021-08-30T05:43:30.19+00:00

    Hello @Dave Lyons ,​

    Welcome to our Microsoft Q&A platform!

    To set data binding for the tableView from the model class, it's unnecessary to add these properties such as 'Text, Placeholder, Keyboard' in the custom cell class. You could pass the value via the model parameters and binding. Here is the sample code, you could refer to:

       <!--Page.xaml-->  
       <TableView x:Name="tvView" Intent="Settings" BackgroundColor="WhiteSmoke" HasUnevenRows="True">  
           <TableRoot>  
               <TableSection Title="Authentication">  
                   <local:CustomCell   
                       BindingContext="{Binding Item_1}"   
                       Validate="true"   
                       HorizontalTextAlignment="End"   
                       BindingProperty="EmailAddress" >  
                   </local:CustomCell>  
         
                   <local:CustomCell   
                       BindingContext="{Binding Item_2}"   
                       Validate="true"   
                       IsPassword="True"   
                       HorizontalTextAlignment="End"   
                       BindingProperty="Password">  
                   </local:CustomCell>  
               </TableSection>  
           </TableRoot>  
       </TableView>  
         
       <!--CustomCell.xaml-->  
       <ViewCell xmlns="http://xamarin.com/schemas/2014/forms"   
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
                    x:Name="customCell"  
                    x:Class="TestApplication_6.CustomCell">  
           <StackLayout Orientation="Horizontal" Margin="20,5,10,5">  
               <Label   
                   Text="{Binding TheLabel}"   
                   TextColor="{Binding TextColor}"   
                   VerticalOptions="CenterAndExpand"   
                   HorizontalOptions="Start" />  
         
               <Entry HorizontalOptions="FillAndExpand"   
                   VerticalOptions="CenterAndExpand"   
                   HorizontalTextAlignment="End"   
                   Text="{Binding TheText}"   
                   Placeholder="{Binding ThePlaceholder}"   
                   Keyboard="{Binding TheKeyboard}" />  
           </StackLayout>  
       </ViewCell>  
    

    Page.xaml.cs & Model class

       public partial class Page2 : ContentPage  
       {  
           public TableViewItem Item_1 { get; set; }  
           public TableViewItem Item_2 { get; set; }  
         
           public Page2()  
           {  
               InitializeComponent();  
         
               Item_1 = new TableViewItem  
               {  
                   TheLabel = "Email Address.......",  
                   ThePlaceholder = "Email Address",  
                   TheKeyboard = Keyboard.Email  
               };  
         
               Item_2 = new TableViewItem  
               {  
                   TheLabel = "Password",  
                   ThePlaceholder = "Password",  
               };  
               BindingContext = this;  
           }  
       }  
         
       public class TableViewItem  
       {  
           public string TheLabel { get; set; }  
           public string TheText { get; set; }  
           public string ThePlaceholder { get; set; }  
           public Keyboard TheKeyboard { get; set; }  
       }  
    

    From your posted code, it seems that the tableView displays the same template in each cell. Do you want to display the same template in the tableView? TableView aims to display the data or choices where there are rows that don't share the same template. ListView and CollectionView will a better choice to display the same template.

    Best Regards,

    Jarvan Zhang


    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.