question

DaveLyons-8454 avatar image
0 Votes"
DaveLyons-8454 asked JarvanZhang-MSFT commented

Xamarin Forms Binding Issue

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; 
         } 
     } 


dotnet-xamarin
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

1 Answer

JarvanZhang-MSFT avatar image
0 Votes"
JarvanZhang-MSFT answered JarvanZhang-MSFT commented

Hello @DaveLyons-8454 ,​

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.


· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi, @DaveLyons-8454
May I know if you have got any chance to check my answer? I am glad to help if you have any other questions.

0 Votes 0 ·