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