Xamarin Forms how to add behaviors to custom control - xamarin.forms

I have created a custom control,which is a ContentView with a Label and an Entry
The xaml of the custom controls looks like this:
<Label Text="{Binding Source={x:Reference ValidationControl}, Path=Caption}"/>
<Entry Text="{Binding Source={x:Reference ValidationControl}, Path=Value, Mode=TwoWay}" />
The code behind of the custom control looks like this:
public static readonly BindableProperty CaptionProperty = BindableProperty.Create(
nameof(Caption), typeof(string), typeof(ValidationEntry), default(string));
public string Caption
{
get => (string)GetValue(CaptionProperty);
set => SetValue(CaptionProperty, value);
}
public static readonly BindableProperty ValueProperty = BindableProperty.Create(
nameof(Value), typeof(string), typeof(ValidationEntry), default(string));
public string Value
{
get => (string)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
I’m using the custom control in the following way
<controls:ValidationEntry Caption=”Name:” Value="{Binding FullName, Mode=TwoWay}" />
My question is how to add behaviors to the custom control?
I would like to add them in the place that I’m using the control. i.e.
<controls:ValidationEntry Caption="Name:"
Value="{Binding FullName, Mode=TwoWay}">
<controls:ValidationEntry.EntryBehaviors>
<behaviors:EntryLengthValidatorBehavior IgnoreSpaces="True"/>
</controls:ValidationEntry.EntryBehaviors>
</controls:ValidationEntry>

You can create a behaviors directly, I add a NumericValidationBehavior in my custom entry to check the data if it is double.If type of the data is not double, the color of text will be set to red.
Here is xaml code.
<StackLayout>
<local:MyEntry local:NumericValidationBehavior.AttachBehavior="true">
</local:MyEntry>
</StackLayout>
Here is NumericValidationBehavior.cs
public static class NumericValidationBehavior
{
public static readonly BindableProperty AttachBehaviorProperty =
BindableProperty.CreateAttached(
"AttachBehavior",
typeof(bool),
typeof(NumericValidationBehavior),
false,
propertyChanged: OnAttachBehaviorChanged);
public static bool GetAttachBehavior(BindableObject view)
{
return (bool)view.GetValue(AttachBehaviorProperty);
}
public static void SetAttachBehavior(BindableObject view, bool value)
{
view.SetValue(AttachBehaviorProperty, value);
}
static void OnAttachBehaviorChanged(BindableObject view, object oldValue, object newValue)
{
var entry = view as Entry;
if (entry == null)
{
return;
}
bool attachBehavior = (bool)newValue;
if (attachBehavior)
{
entry.TextChanged += OnEntryTextChanged;
}
else
{
entry.TextChanged -= OnEntryTextChanged;
}
}
static void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
double result;
bool isValid = double.TryParse(args.NewTextValue, out result);
((Entry)sender).TextColor = isValid ? Color.Default : Color.Red;
}
}
Update
I create a custom view with ContentView
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BeHavDemo.MyView">
<ContentView.Content>
<StackLayout>
<Label Text="xxxx"/>
<Entry Text="eeeee" />
</StackLayout>
</ContentView.Content>
</ContentView>
Then I create a behavior.
public class MyBeha : Behavior<MyView>
{
protected override void OnAttachedTo(BindableObject view)
{
base.OnAttachedTo(view);
var myview=view as MyView;
StackLayout stackLayout = (StackLayout)myview.Content;
Label label = (Label)stackLayout.Children[0];
Entry entry=(Entry) stackLayout.Children[1];
}
}

Related

Updating of property values & bindings in .NET MAUI ContentView

I'm attempting to try my hand at creating a card-based UI similar to Android's CardView layout using MAUI ContentView. The exemplary use case for such an element is the display of testing or scoring results, so I attempted to roll in some of the underlying logic directly into the new element - the user would simply have to supply an Attempted and a Possible set of integers, i.e. 92 and 100, from which it would display an "out-of" format of 92/100 as well as a percentage of 92.0%.
The issue is that I've tried a number of ways to do this, and none successfully update the Score and Percentage properties correctly. I realize that it may be an issue with the order (or simultaneity) of the properties being set, but I haven't been able to rectify it using BindableProperty.Create(..., propertyChanged:) or other methods.
ProgressViewCard.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView x:Name="this"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ExampleApp.Controls.ProgressCardView">
<Frame BindingContext="{x:Reference this}" BackgroundColor="{Binding BackgroundColor}" CornerRadius="5" HasShadow="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*" />
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<!-- Configure the button functionality of the ProgressCardView -->
<Button x:Name="InternalButton" Grid.RowSpan="3" Grid.ColumnSpan="2" Opacity="0.0" Clicked="buttonEvent" />
<Label Text="{Binding Title}" HorizontalOptions="Start" VerticalOptions="Center" FontSize="17" TextColor="{Binding HeaderColor}" />
<Label Text="{Binding Score}" Grid.Column="1" Grid.ColumnSpan="2" HorizontalOptions="End" VerticalOptions="Center" FontSize="12" TextColor="{Binding TextColor}" />
<BoxView Grid.Row="1" Grid.ColumnSpan="3" HeightRequest="1" Color="{Binding TextColor}"/>
<ProgressBar x:Name="CardProgressBar" Grid.Row="2" Grid.ColumnSpan="2" />
<Label Text="{Binding Percentage}" Grid.Row="2" Grid.Column="2" HorizontalOptions="End" VerticalOptions="Center" FontSize="12" TextColor="{Binding TextColor}" />
</Grid>
</Frame>
</ContentView>
ProgressViewCard.xaml.cs:
namespace ExampleApp.Controls
{
public partial class ProgressCardView : ContentView
{
private int attempted = 0;
private int possible = 0;
#region BindableProperties
public static readonly BindableProperty TitleProperty = BindableProperty.Create(
nameof(Title),
typeof(string),
typeof(ProgressCardView2),
string.Empty);
public static readonly BindableProperty AttemptedProperty = BindableProperty.Create(
nameof(Attempted),
typeof(int),
typeof(ProgressCardView2),
0);
public static readonly BindableProperty PossibleProperty = BindableProperty.Create(
nameof(Possible),
typeof(int),
typeof(ProgressCardView2),
1);
public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(
nameof(BackgroundColor),
typeof(Color),
typeof(ProgressCardView2),
Color.FromArgb("#FFFFFF"));
public static readonly BindableProperty HeaderColorProperty = BindableProperty.Create(
nameof(HeaderColor),
typeof(Color),
typeof(ProgressCardView2),
Color.FromArgb("#FFFFFF"));
public static readonly BindableProperty TextColorProperty = BindableProperty.Create(
nameof(TextColor),
typeof(Color),
typeof(ProgressCardView2),
Color.FromArgb("#FFFFFF"));
#endregion
#region Getters and Setters
public string Title
{
get => (string) GetValue(ProgressCardView2.TitleProperty);
set => SetValue(ProgressCardView2.TitleProperty, value);
}
public int Attempted
{
get => (int) GetValue(ProgressCardView2.AttemptedProperty);
set => SetValue(ProgressCardView2.AttemptedProperty, value);
}
public int Possible
{
get => (int)GetValue(ProgressCardView2.PossibleProperty);
set => SetValue(ProgressCardView2.PossibleProperty, value);
}
public string Score
{
get { return String.Format("{0}/{1}", this.attempted, this.possible); }
set { this.Score = value; }
}
public string Percentage
{
get { return String.Format("{0:P1}", ((double) this.attempted) / ((double) this.possible)); }
set { this.Score = value; }
}
public Color BackgroundColor
{
get => (Color) GetValue(ProgressCardView2.BackgroundColorProperty);
set => SetValue(ProgressCardView2.BackgroundColorProperty, value);
}
public Color HeaderColor
{
get => (Color) GetValue(ProgressCardView2.HeaderColorProperty);
set => SetValue(ProgressCardView2.HeaderColorProperty, value);
}
public Color TextColor
{
get => (Color) GetValue(ProgressCardView2.TextColorProperty);
set => SetValue(ProgressCardView2.TextColorProperty, value);
}
#endregion
#region Methods and Events
public ProgressCardView2()
{
InitializeComponent();
}
private void buttonEvent(object sender, EventArgs e)
{
}
#endregion
}
}
The usage of the controls is as follows:
<ctrls:ProgressCardView Title="CS 101 Final" Attempted="92" Possible="100" BackgroundColor="#ffffff"
HeaderColor="#e74c3c" TextColor="#7f8c8d" />
<ctrls:ProgressCardView Title="ME 302 Midterm" Attempted="68" Possible="85" BackgroundColor="#ffffff"
HeaderColor="#e74c3c" TextColor="#7f8c8d" />
This is the result in an Android emulator (API 31). How do I modify the above control to obtain the correct behavior?
You have done most of the work. I tried your code and made some changes which worked for me.
First, in Custom control ProgressCardView, delete this first two lines:
private int attempted = 0;
private int possible = 0;
because this has nothing to do with the BindableProperty and you use the following code which will cause the value always be 0. You could set default value in BindableProperty.Create.
public string Score
{
get { return String.Format("{0}/{1}", this.attempted, this.possible); }
}
Second, in BindableProperty, you could define propertyChanged event handler, which could define a callback method when property changed. For more info, you could refer to Detect property changes
And for your case, the change of AttemptedProperty and PossibleProperty would cause the result changed, so we could add propertyChanged to these two BindableProperty. Such like the following:
public static readonly BindableProperty AttemptedProperty = BindableProperty.Create(
nameof(Attempted),
typeof(int),
typeof(ProgressViewCard),
0,
propertyChanged:OnThisPropertyChanged);
public static readonly BindableProperty PossibleProperty = BindableProperty.Create(
nameof(Possible),
typeof(int),
typeof(ProgressViewCard),
1,
propertyChanged:OnThisPropertyChanged);
For convenience, I think these two properties could share the same propertyChanged callback method, that's to count the value.
Then we could implement the OnThisPropertyChanged callback method:
private static void OnThisPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var myCard = bindable as ProgressViewCard;
myCard.OnPropertyChanged(nameof(Score));
myCard.OnPropertyChanged(nameof(Percentage));
}
Here is the complete code for ProgressViewCard.cs which worked for me:
#region BindableProperties
public static readonly BindableProperty TitleProperty = BindableProperty.Create(
nameof(Title),
typeof(string),
typeof(ProgressViewCard),
string.Empty);
public static readonly BindableProperty AttemptedProperty = BindableProperty.Create(
nameof(Attempted),
typeof(int),
typeof(ProgressViewCard),
0,
propertyChanged:OnThisPropertyChanged);
public static readonly BindableProperty PossibleProperty = BindableProperty.Create(
nameof(Possible),
typeof(int),
typeof(ProgressViewCard),
1,
propertyChanged:OnThisPropertyChanged);
private static void OnThisPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var a = bindable as ProgressViewCard;
a.OnPropertyChanged(nameof(Score));
a.OnPropertyChanged(nameof(Percentage));
}
public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create(
nameof(BackgroundColor),
typeof(Color),
typeof(ProgressViewCard),
Color.FromArgb("#FFFFFF"));
public static readonly BindableProperty HeaderColorProperty = BindableProperty.Create(
nameof(HeaderColor),
typeof(Color),
typeof(ProgressViewCard),
Color.FromArgb("#FFFFFF"));
public static readonly BindableProperty TextColorProperty = BindableProperty.Create(
nameof(TextColor),
typeof(Color),
typeof(ProgressViewCard),
Color.FromArgb("#FFFFFF"));
#endregion
#region Getters and Setters
public string Title
{
get => (string)GetValue(ProgressViewCard.TitleProperty);
set => SetValue(ProgressViewCard.TitleProperty, value);
}
public int Attempted
{
get => (int)GetValue(ProgressViewCard.AttemptedProperty);
set => SetValue(ProgressViewCard.AttemptedProperty, value);
}
public int Possible
{
get => (int)GetValue(ProgressViewCard.PossibleProperty);
set => SetValue(ProgressViewCard.PossibleProperty, value);
}
public string Score
{
get { return String.Format("{0}/{1}", this.Attempted, this.Possible); }
set { this.Score = value; }
}
public string Percentage
{
get { return String.Format("{0:P1}", ((double)this.Attempted) / ((double)this.Possible)); }
set { this.Score = value; }
}
public Color BackgroundColor
{
get => (Color)GetValue(ProgressViewCard.BackgroundColorProperty);
set => SetValue(ProgressViewCard.BackgroundColorProperty, value);
}
public Color HeaderColor
{
get => (Color)GetValue(ProgressViewCard.HeaderColorProperty);
set => SetValue(ProgressViewCard.HeaderColorProperty, value);
}
public Color TextColor
{
get => (Color)GetValue(ProgressViewCard.TextColorProperty);
set => SetValue(ProgressViewCard.TextColorProperty, value);
}
#endregion
#region Methods and Events
public ProgressViewCard()
{
InitializeComponent();
}
private void buttonEvent(object sender, EventArgs e)
{
}
#endregion
Hope it works for you.

Swipe Down To Close Popup not working with CarouselView in Xamarin

I am displaying a CarouselView inside a PopupPage (using Rg.Plugins.Popup). It has 2 problems that I don't understand yet:
1. I am trying to do Swipe Down To Close Popup feature. Everything works fine if my UI is just like this:
PopupOne.xaml
<popup:PopupPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:popup="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
xmlns:animations="clr-namespace:Rg.Plugins.Popup.Animations;assembly=Rg.Plugins.Popup"
.....
x:Class="xxx.Views.PopupImgProd">
<ContentView>
<ContentView.Behaviors>
<behavior:SwipeDownToClosePopupPage CloseAction="SwipeDownToClosePopupPage_CloseAction"
ClosingEdge="0"
ClosingTimeInMs="400"/>
</ContentView.Behaviors>
<StackLayout HorizontalOptions="FillAndExpand" Padding="0" BackgroundColor="#fff">
<AbsoluteLayout HorizontalOptions="FillAndExpand">
<Frame>
....
</Frame>
<Frame>
....
</Frame>
</AbsoluteLayout>
<Label Text="sss"/>
</StackLayout>
</ContentView>
</popup:PopupPage>
SwipeDownToClosePopupPage.cs
public class SwipeDownToClosePopupPage : Behavior<View>
{
private PanGestureRecognizer PanGestureRecognizer { get; set; }
private DateTimeOffset? StartPanDownTime { get; set; }
private DateTimeOffset? EndPanDownTime { get; set; }
private double TotalY { get; set; }
private bool ReachedEdge { get; set; }
/// <summary>
/// Close action, depends on your navigation mode
/// </summary>
public event Action CloseAction;
public static readonly BindableProperty ClosingEdgeProperty = BindableProperty.Create(propertyName: nameof(ClosingEdge)
, returnType: typeof(Double)
, declaringType: typeof(SwipeDownToClosePopupPage)
, defaultValue: Convert.ToDouble(100)
, defaultBindingMode: BindingMode.TwoWay
, propertyChanged: ClosingEdgePropertyChanged);
/// <summary>
/// The height from the bottom that will trigger close page
/// </summary>
public Double ClosingEdge
{
get { return (Double)GetValue(ClosingEdgeProperty); }
set { SetValue(ClosingEdgeProperty, Convert.ToDouble(value)); }
}
private static void ClosingEdgePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (SwipeDownToClosePopupPage)bindable;
if (newValue != null)
{
control.ClosingEdge = Convert.ToDouble(newValue);
}
}
public static readonly BindableProperty ClosingTimeInMsProperty = BindableProperty.Create(propertyName: nameof(ClosingTimeInMs)
, returnType: typeof(Int64)
, declaringType: typeof(SwipeDownToClosePopupPage)
, defaultValue: Convert.ToInt64(500)
, defaultBindingMode: BindingMode.TwoWay
, propertyChanged: ClosingTimeInMsPropertyChanged);
/// <summary>
/// Scroll time less than this value will trigger close page
/// </summary>
public Int64 ClosingTimeInMs
{
get { return (Int64)GetValue(ClosingTimeInMsProperty); }
set { SetValue(ClosingTimeInMsProperty, Convert.ToInt64(value)); }
}
private static void ClosingTimeInMsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (SwipeDownToClosePopupPage)bindable;
if (newValue != null)
{
control.ClosingTimeInMs = Convert.ToInt64(newValue);
}
}
public SwipeDownToClosePopupPage()
{
this.PanGestureRecognizer = new PanGestureRecognizer();
}
protected override void OnAttachedTo(View v)
{
PanGestureRecognizer.PanUpdated += Pan_PanUpdated;
v.GestureRecognizers.Add(this.PanGestureRecognizer);
base.OnAttachedTo(v);
}
protected override void OnDetachingFrom(View v)
{
PanGestureRecognizer.PanUpdated -= Pan_PanUpdated;
v.GestureRecognizers.Remove(this.PanGestureRecognizer);
base.OnDetachingFrom(v);
}
private void Pan_PanUpdated(object sender, PanUpdatedEventArgs e)
{
View v = sender as View;
switch (e.StatusType)
{
case GestureStatus.Started:
StartPanDownTime = DateTime.Now;
break;
case GestureStatus.Running:
TotalY = e.TotalY;
if (TotalY > 0)
{
if (Device.RuntimePlatform == Device.Android)
{
v.TranslateTo(0, TotalY + v.TranslationY, 20, Easing.Linear);
//Too close to edge?
ReachedEdge = TotalY + v.TranslationY > v.Height - ClosingEdge;
}
else
{
v.TranslateTo(0, TotalY, 20, Easing.Linear);
//Too close to edge?
ReachedEdge = TotalY > v.Height - ClosingEdge;
}
}
break;
case GestureStatus.Completed:
EndPanDownTime = DateTimeOffset.Now;
if (TotalY > 0
|| ReachedEdge)
//Swipe too fast
CloseAction?.Invoke();
else
{
v.TranslateTo(0, 0, 20, Easing.Linear);
}
break;
}
if (e.StatusType == GestureStatus.Completed || e.StatusType == GestureStatus.Canceled)
{
StartPanDownTime = null;
EndPanDownTime = null;
}
}
}
----------> The above it works great. When I SwipeDown it closes the Popup. Very good
However, now what I display is: CarouselView.
The problem occurs here: When I SwipeDown it doesn't close the Popup? Even though I have wrapped the event for it as it was at the beginning.
PopupOne.xaml
<popup:PopupPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:popup="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup"
xmlns:animations="clr-namespace:Rg.Plugins.Popup.Animations;assembly=Rg.Plugins.Popup"
.....
x:Class="TopSell.Views.PopupImgProd">
<ContentView>
<ContentView.Behaviors>
<behavior:SwipeDownToClosePopupPage CloseAction="SwipeDownToClosePopupPage_CloseAction"
ClosingEdge="0"
ClosingTimeInMs="400"/>
</ContentView.Behaviors>
<StackLayout HorizontalOptions="FillAndExpand" Padding="0" BackgroundColor="#fff">
<AbsoluteLayout HorizontalOptions="FillAndExpand">
<Frame>
....
</Frame>
<Frame>
....
</Frame>
</AbsoluteLayout>
<CarouselView x:Name="_stdata" ItemsSource="{Binding imglistProd}">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="0" Margin="0" VerticalOptions="FillAndExpand" x:DataType="model:ProductImages">
<Image Aspect="AspectFill" VerticalOptions="CenterAndExpand" Source="{Binding Images}"/>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</StackLayout>
</ContentView>
</popup:PopupPage>
PopupOne.xaml.cs
private void SwipeDownToClosePopupPage_CloseAction()
{
PopupNavigation.Instance.PopAllAsync();
}
---> Is there a conflict between the left and right movement of CarouselView and SwipeDown
2. The displayed image of the CarouselView seems to cut off the top and bottom of the image (Image is cropped when I put it in CarouselView )
Looking forward to everyone's help. Thank you

How to access the BindingContext of custom control in Xamarin.Forms

I have a CollectionView with ItemsSource set to ObservableCollection of type Employee.
The ItemTemplate of the CollectionView is a CustomControl that has 1 BindableProperty of Type Employee
MainPage.xaml:
<CollectionView ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee}">
<CollectionView.ItemTemplate>
<DataTemplate>
<controls:CustomControl Employee="{Binding .}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
The CustomControl has an image (checked image to indicate selection).
CustomControl.xaml:
<Frame HasShadow="True"
BackgroundColor="Blue">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}" />
<Image Source="check.png" />
</StackLayout>
</Frame>
CustomControl.xaml.cs:
public partial class CustomControl : ContentView
{
public CustomControl()
{
InitializeComponent();
}
public static BindableProperty EmployeeProperty = BindableProperty.Create(
propertyName: nameof(Employee),
returnType: typeof(Employee),
declaringType: typeof(CustomControl),
defaultValue: default(Employee),
defaultBindingMode: BindingMode.OneWay);
public Employee Employee
{
get
{
return (Employee)GetValue(EmployeeProperty);
}
set
{
SetValue(EmployeeProperty, value);
}
}
}
Model (Employee):
public class Employee: INotifyPropertyChanged
{
private int name;
public int Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
private int isSelected;
public int IsSelected
{
get
{
return isSelected;
}
set
{
isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
#region PropertyChanged
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I am trying to create simple animation (FadeIn/FadeOut) for the checked image in the CustomControl so when an item is selected the image will fade in, and when unselected it will fade out. I could use IsVisible and set it to true/false but that's ugly.
My idea was to listen to PropertyChanged event of the Employee (which supposed to be the context of my CustomControl), and when the property IsSelected is modified, I will start the animation to show/hide the image. something like this
public CustomControl()
{
InitializeComponent();
(this.BindingContext as Employee).PropertyChanged += CustomControl_PropertyChanged;
}
private void CustomControl_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Employee.IsSelected))
{
//do animation to show/hide image
}
}
But couldn't access the Context of my CustomControl!
When I declare the binding in MainPage.xaml I am passing a single Emplyee objet as BindingContext (that dot, right?):
<controls:CustomControl Employee="{Binding .}" />
but after the CustomControl is initializd, the BindingContext is still null!
public CustomControl()
{
InitializeComponent();
var context = this.BindingContext; //this is null
}
How can I observe the changes on the IsSelected property of the Employee object from my CustomControl?
In your custom control override the OnBindingContextChanged method, inside of that method you should be able to access the binding context that is set for your view.
Ex:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
var context = this.BindingContext as Employee
}

Could not bind to nested BindableProperty

I have created bindable property called Text in TargetClass.cs. That Text property is nested bindable property.
TargetClass.cs :
public class TargetClass : BindableObject
{
public static readonly BindableProperty TextProperty =
BindableProperty.Create("Text", typeof(string), typeof(TargetClass), "Default", BindingMode.TwoWay, null,
OnTextChanged);
private static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
}
Then I have created MyView.cs
public class MyView : ContentView
{
private TargetClass target;
Label label;
public TargetClass Target
{
get
{
return target;
}
set
{
target = value;
label.Text = target.Text;
}
}
public MyView()
{
label = new Label();
label.FontSize = 50;
Content = label;
}
}
ViewModel.cs :
public class ViewModel : INotifyPropertyChanged
{
private string m_text = "New Value";
public string TextValue
{
get { return m_text; }
set
{
m_text = value;
OnPropertyChanged("TextValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainPage.xaml :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:BindingDemo"
x:Class="BindingDemo.MainPage">
<ContentPage.BindingContext>
<local:ViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:TargetClass x:Key="target" Text="{Binding TextValue}"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<local:MyView Target="{StaticResource target}"/>
</StackLayout>
It is working when I give some string value to Text property like,
<ContentPage.BindingContext>
<local:ViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:TargetClass x:Key="target" Text="Hello World"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<local:MyView Target="{StaticResource target}"/>
</StackLayout>
in xaml.
But it doesnt work in MVVM binding When I bind the Text property like,
<ContentPage.BindingContext>
<local:ViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<local:TargetClass x:Key="target" Text="{Binding TextValue}"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<local:MyView Target="{StaticResource target}"/>
</StackLayout>
There is no problem with MVVM binding(ViewModel.cs) because it works well with another bindable property.
But it works if the Text property is added in MyView.cs and used like MyView.Text. It doesn't work only when it is added in TargetClass.cs and used like MyView.TargetClass.Text.
Is it possible to use the nested property in data binding in Xamarin Forms?
I don't think objects in the resource dictionary get a BindingContext assigned to them.
So you should modify it from code-behind to assign a context to any resource of type BindableObject
in your page.axml.cs:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (this.Resources != null)
{
foreach (var resource in this.Resources.Values.OfType<BindableObject>())
{
resource.BindingContext = this.BindingContext;
}
}
}

Steps to create a behavior (how create a behavior)

i need to create a behavior from a view, i am using a class, also namespace to call to behavior but it's not working for me, i do not know what is my wrong, because i am doing all of step to create a behavior.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Layouts.Commands.BasketView"
xmlns:local="clr-namespace:Layouts;assembly=Layouts"
Title="Cart">
<StackLayout Padding="10,60,10,0">
<Label Text="Red when the number isn't valid" FontSize="Small" />
<Entry Placeholder="Enter a System.Double"
local:NumericValidationBehavior.AttachBehavior="true" />
</StackLayout>
</ContentPage>
in this line i get the error local:NumericValidationBehavior.AttachBehavior="true" />
i have my behavior, but it cannot be instanciate in the view (see code up), there are other step to be instanciate?
namespace AttachedNumericValidationBehavior
{
public static class NumericValidationBehavior
{
public static readonly BindableProperty AttachBehaviorProperty
= BindableProperty.CreateAttached("AttachBehavior", typeof(bool), typeof(NumericValidationBehavior), false, propertyChanged: OnAttachBehaviorChanged);
public static bool GetAttachBehavior(BindableObject view)
{
return (bool)view.GetValue(AttachBehaviorProperty);
}
public static void SetAttachBehavior(BindableObject view, bool value)
{
view.SetValue(AttachBehaviorProperty, value);
}
static void OnAttachBehaviorChanged(BindableObject view, object oldValue, object newValue)
{
var entry = view as Entry;
if (entry == null)
{
return;
}
bool attachBehavior = (bool)newValue;
if (attachBehavior)
{
entry.TextChanged += OnEntryTextChanged;
}
else
{
entry.TextChanged -= OnEntryTextChanged;
}
}
static void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
double result;
bool isValid = double.TryParse(args.NewTextValue, out result);
((Entry)sender).TextColor = isValid ? Color.Default : Color.Red;
}
}
}
the error is: "AttachBehavior is not in type NumericValidationBehavior"
You class NumericValidationBehavior belongs to namespace AttachedNumericValidationBehavior, so when you want to use NumericValidationBehavior in xaml, the namespace local should be:
xmlns:local="clr-namespace:AttachedNumericValidationBehavior;"
And use the namespace:
<StackLayout Padding="10,60,10,0">
<Label Text="Red when the number isn't valid" FontSize="Small" />
<Entry Placeholder="Enter a System.Double"
local:NumericValidationBehavior.AttachBehavior="true" />
</StackLayout>
For more details, see document: Declaring Namespaces for Types

Resources