Pass-through TextChanged Bindableproperty from a custom Entry control to the actual Entry - events

I have a custom control, which is basically a XML wrapper around another custom control, with its own CustomRenderer.
Custom Control, basically a Wrapper XAML for another custom control
<?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="Test.Controls.TitleAndEntry"
xmlns:renderers="clr-namespace:Test.Renderers">
<StackLayout>
<Label
FontAttributes="Bold"
Style="{StaticResource TitleSemiBoldBlackLabel}"/>
<renderers:EntryBottomLine
x:Name="myEntry"
PlaceholderColor="{StaticResource PrimarySolidGrayColor}"
IsPassword="False"
TextColor="{StaticResource BlackColor}"/>
</StackLayout>
</ContentView>
Custom Control Wrapper Code-Behind
public partial class TitleAndEntry : ContentView
{
public TitleAndEntry()
{
InitializeComponent();
}
// Other BindableProperties & code
public static BindableProperty TextEntryChangedProperty = BindableProperty.Create(
nameof(TextEntryChanged), typeof(EventHandler<TextChangedEventArgs>), typeof(TitleAndEntry), null,
propertyChanged: OnTextEntryChanged);
public EventHandler<TextChangedEventArgs> TextEntryChanged
{
get
{
return (EventHandler<TextChangedEventArgs>)GetValue(TextEntryChangedProperty);
}
set
{
SetValue(TextEntryChangedProperty, value);
}
}
private static void OnTextEntryChanged(BindableObject bindable, object oldVal, object newVal)
{
var titleAndEntry = (TitleAndEntry)bindable;
titleAndEntry.myEntry.TextChanged += (EventHandler<TextChangedEventArgs>)newVal;
}
}
}
XAML on the page where I use the custom control
<ctrl:TitleAndEntry
x:Name="user"
Margin="{StaticResource NormalTopPadding}"
TextLabel="{x:Static local:AppResources.User}"
TextChanged="OnMyTextChanged"/>
Code-behind of that page
private void OnMyTextChanged(object sender, TextChangedEventArgs e)
{
// Call to Viewmodel
}
I get this error:
Xamarin Forms No property, bindable property, or event found for 'TextChanged', or mismatching type between value and property
I tried many suggestions, but I cannot get it to work.

I'll answer it myself:
After going back to the InputView (which is the base class for Entry), I noticed that TextChanged is not a BindableProperty, but an event.
public event EventHandler<TextChangedEventArgs> TextChanged;
So I removed the entire BindableProperty and replaced it with:
// On the regular Text propert, call the event handler from the PropertyChanged param
public static readonly BindableProperty TextProperty =
BindableProperty.Create(
nameof(Text),
typeof(string),
typeof(TitleAndEntry),
null,
BindingMode.TwoWay,
propertyChanged: (bindable, oldValue, newValue) => ((TitleAndEntry)bindable).OnTextChanged((TitleAndEntry)bindable, (string)oldValue, (string)newValue));
public event EventHandler<TextChangedEventArgs> TextChanged;
// Simply Invoke the event with the provided args
private void OnTextChanged(TitleAndEntry bindable, string oldValue, string newValue)
{
this.TextChanged?.Invoke(bindable, new TextChangedEventArgs(oldValue, newValue));
}
Now it works as it should.

Related

Correct Way to Define a Custom Bindable Property of Type ObservableCollection in Xamarin

I need to react to the content of an ObservableCollection's property changing. I define the property like this:
public static readonly BindableProperty MyCollectionProperty = BindableProperty.Create(
nameof(MyContentView.MyCollection),
typeof(ObservableCollection<object>),
typeof(MyContentView),
propertyChanged: OnMyCollectionPropertyChanged);
public ObservableCollection<object> MyCollection
{
get => (ObservableCollection<object>)GetValue(MyContentView.MyCollectionProperty);
set => SetValue(MyContentView.MyCollectionProperty, value);
}
private static void OnMyCollectionPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
// Do Something
}
The method OnMyCollectionPropertyChanged is called when the whole bound collection object changes (i.e. a completely new collection) but of course not when the collection's content changes.
What is the correct way to react to the events of the INotifyCollectionChanged interface implemented by ObservableCollection? Do I need to manually sign up for them when the collection object is assigned or is there a syntax of BindableProperty.Create() which allows to do so directly, something similar to the propertyChanged method?
I make a code sample of Custom BindableProperty for Type ObservableCollection for your reference.
ContentView:
<ContentView.Content>
<StackLayout>
<ListView x:Name="listview1" ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentView.Content>
public partial class View1 : ContentView
{
public ObservableCollection<object> Items
{
get { return (ObservableCollection<object>)GetValue(ItemsProperty); }
set
{
SetValue(ItemsProperty, value);
}
}
public static BindableProperty ItemsProperty = BindableProperty.Create("ItemsSource",
typeof(ObservableCollection<object>),
typeof(View1),
null,
BindingMode.TwoWay,
propertyChanged: (bindable, oldValue, newValue) => OnItemsSourceChanged(bindable, oldValue, newValue));
private static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (View1)bindable;
control.listview1.ItemsSource = (ObservableCollection<object>)newValue;
}
public View1()
{
InitializeComponent();
}
}
Useage:
Xaml:
<ContentPage.Content>
<local:View1 Items="{Binding models}" />
</ContentPage.Content>
Code behind:
public partial class Page17 : ContentPage
{
public ObservableCollection<object> models { get; set; }
public Page17()
{
InitializeComponent();
models = new ObservableCollection<object>()
{
new modela(){Name="A" },
new modela(){Name="B" }
};
this.BindingContext = this;
}
}
public class modela
{
public string Name { get; set; }
}

Add a touch-event listener to a Xamarin custom control using Bindable Property

I developed a simple two-lines custom control to host a Name-Value pair and display it with reusable logicl.
I could set up the link between the two properties and the XAML using two BindableProperty that set the value of the two labels.
This is my custom control XAML:
<?xml version="1.0" encoding="UTF-8"?>
<StackLayout
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SystemOne.ui.GenericItem"
Orientation="Vertical">
<Label x:Name="TitleLabel" />
<Label x:Name="ContentLabel"/>
</StackLayout>
This is one of the Properties & related BindableProperty in the code behind:
public string TextContent { get; set; }
public static readonly BindableProperty TextContentProperty = BindableProperty.Create(
propertyName: "TextContent",
returnType: typeof(string),
declaringType: typeof(GenericItem),
defaultValue: "",
propertyChanged: TextContentPropertyChanged);
private static void TextContentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
GenericItem GenericItem = (GenericItem)bindable;
GenericItem.ContentLabel.Text = newValue.ToString();
}
This allows to use the GenericItem custom control in any Page in this way (definig proper namespace):
<ui:GenericItem x:Name="MyGenItem" TextContent="{Binding MyViewModel.MyContentText}" />
The GenericItem custom-control takes the value for the its Lable 'ContentLabel' from the binding defined for the TextContent property.
Now I woluld like to develope something that allows a usage with this pseudo-XAML:
<ui:GenericItem x:Name="MyGenItem" TextContent="{Binding MyViewModel.MyContentText}" Clicked="{Binding MyViewModel.SomeProperty}"/>
or even not binded:
<ui:GenericItem x:Name="MyGenItem" TextContent="{Binding MyViewModel.MyContentText}" Clicked="MyGenericItem_Tapped"/>
where 'MyGenericItem_Tapped' is an Event handler method defined in code-behind of the page the is creating the 'MyGenItem' instnce of the GeneriItem control.
I could not find a way!
I use the ContentView to make the custom control like below:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App17.GenericItem">
<ContentView.Content>
<StackLayout Orientation="Vertical">
<Label x:Name="TitleLabel" />
<Label x:Name="ContentLabel" />
</StackLayout>
</ContentView.Content>
</ContentView>
And then use the GestureRecognizers directly instead of touch-event listener which using Bindable Property.
<ui:GenericItem x:Name="MyGenItem" TextContent="{Binding MyContentText}">
<ui:GenericItem.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"></TapGestureRecognizer>
</ui:GenericItem.GestureRecognizers>
</ui:GenericItem>
If you want to use the binding for the GestureRecognizers, you could use ICommand.
Xaml:
<ui:GenericItem x:Name="MyGenItem" TextContent="{Binding MyContentText}">
<ui:GenericItem.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapGestureRecognizer_Command}"></TapGestureRecognizer>
</ui:GenericItem.GestureRecognizers>
</ui:GenericItem>
Code behind:
public partial class Page14 : ContentPage
{
public Page14()
{
InitializeComponent();
this.BindingContext = new MyViewModel();
}
}
public class MyViewModel
{
public Command TapGestureRecognizer_Command { get; set; }
public string MyContentText { get; set; }
public MyViewModel()
{
MyContentText = "Hello";
TapGestureRecognizer_Command = new Command(TappedCommand);
}
private void TappedCommand(object obj)
{
Console.WriteLine("TappedCommand");
}
}

How do you bind a View Model to the View in XAML in Xamarin?

I've got a very basic view.
<ContentPage x:Class="ThetaRex.InvestmentManager.Merlin.Views.ScenarioSelectionPage"
Title="{Binding Title}"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns="http://xamarin.com/schemas/2014/forms">
<StackLayout>
<ListView ItemsSource="{Binding Items}"/>
</StackLayout>
<ContentPage/>
The code behind is also very simple:
namespace ThetaRex.InvestmentManager.Merlin.Views
{
using System.ComponentModel;
using ThetaRex.InvestmentManager.Merlin.ViewModels;
using Xamarin.Forms;
public partial class ScenarioSelectionPage : ContentPage
{
public ScenarioSelectionPage()
{
InitializeComponent();
this.BindingContext = this.ViewModel = new ScenarioSelectionViewModel();
}
public ScenarioSelectionViewModel ViewModel { get; set; }
protected override void OnAppearing()
{
base.OnAppearing();
ViewModel.LoadItemsCommand.Execute(null);
}
}
}
Coming from a pure MVVM environment in WPF and UWP, I want to bind the view to the viewmodel in XAML, not using the this.Binding = ViewModel in the code behind. I've tried:
<ContentPage x:Class="ThetaRex.InvestmentManager.Merlin.Views.ScenarioSelectionPage"
xmlns:controls="clr-namespace:ThetaRex.InvestmentManager.Merlin.Controls"
BindingContext="{Binding ViewModel}"
Title="{Binding Title}"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns="http://xamarin.com/schemas/2014/forms">
But it didn't work. How do I bind to the ViewModel from XAML?
Note: I know that I can create a view model from scratch in XAML, but it doesn't use the same instance that the code behind in the view uses, so that's not an option.
If I understood what you want, the solution is build a ViewModelLocator like this:
ViewModelLocalizator Class
public static class ViewModelLocalizator
{
public static readonly BindableProperty AutoWireViewModelProperty =
BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocalizator), default(bool), propertyChanged: OnAutoWireViewModelChanged);
public static bool GetAutoWireViewModel(BindableObject bindable)
{
return (bool)bindable.GetValue(AutoWireViewModelProperty);
}
public static void SetAutoWireViewModel(BindableObject bindable, bool value)
{
bindable.SetValue(AutoWireViewModelProperty, value);
}
/// <summary>
/// VERIFY THE VIEW NAME AND ASSOCIATE IT WITH THE VIEW MODEL OF THE SAME NAME. REPLACING THE 'View' suffix WITH THE 'ViewModel'
/// </summary>
private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
if (!(bindable is Element view))
{
return;
}
var viewType = view.GetType();
var viewModelName = viewType.FullName.Replace(".Views.", ".ViewModels.").Replace("Page", "ViewModel");
var viewModelType = Type.GetType(viewModelName);
if (viewModelType == null) { return; }
var vmInstance = Activator.CreateInstance(viewModelType);
if (vmInstance != null)
{
view.BindingContext = vmInstance;
}
}
}
Using It on your View
<ContentPage x:Class="YourProject.Views.YourTestPage"
Title="{Binding Title}"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:viewModelBase="clr-namespace:YourProject.ViewModels;assembly=YouProject"
viewModelBase:ViewModelLocalizator.AutoWireViewModel="true"
>
<StackLayout>
<ListView ItemsSource="{Binding Items}"/>
</StackLayout>
<ContentPage/>

Xamarin Forms binding - intercepting a bound value and changing it

I have a simple composed custom control that displays text set to a bound ControlText property. In the example below, you can see when the button is clicked the control is updated.
How can I change the code so that the label shown by the control takes whatever is sent to it and converts it to all uppercase?
So instead of showing...
Count=5
it would show...
COUNT=5
In this simple example an IValueConverter can be leveraged to accomplish this, but I want to see a different implementation for a much more complex example I need to implement. I am seeking a solution that intercepts the value being set in the code behind, converts it, and sets it to the ControlText property of the custom control.
SimpleControl.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SimpleControl : ContentView
{
public SimpleControl ()
{
InitializeComponent ();
}
public static readonly BindableProperty ControlTextProperty = BindableProperty.Create(
propertyName: nameof(ControlText),
returnType: typeof(string),
declaringType: typeof(SimpleControl),
defaultBindingMode: BindingMode.TwoWay,
defaultValue: "Hello World");
public string ControlText
{
get { return (string)base.GetValue(ControlTextProperty); }
set { base.SetValue(ControlTextProperty, value); }
}
}
Also, I would expect at runtime this breakpoint to be hit, but the code never stops on it. I am setting the property from the SimplePageModel, so I find it strange this is never hit. Can someone explain that to me as well?
SimpleControl.xaml
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App7.SimpleControl"
x:Name="this">
<ContentView.Content>
<StackLayout Margin="100">
<Label Text="{Binding Source={x:Reference this}, Path=ControlText}" />
</StackLayout>
</ContentView.Content>
</ContentView>
SimplePage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App7"
x:Class="App7.SimplePage">
<ContentPage.Content>
<StackLayout>
<local:SimpleControl ControlText="{Binding ControlText}" />
<Button Text="Update Control"
Command="{Binding UpdateControl}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
SimplePageModel.cs (leveraging FreshMVVM)
public class SimplePageModel : FreshBasePageModel
{
public SimplePageModel() { }
private int _index;
public string ControlText { get; set; }
public Command UpdateControl
{
get
{
return new Command((t) =>
{
ControlText = $"Count = {++_index}";
});
}
}
public override void Init(object initData)
{
ControlText = $"Count = 0";
base.Init(initData);
}
}
You can also use triggers for this purpose, since its not too clear what is the idea in the background, I am just suggesting that it can be helpful:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/triggers
Direct answer to the question : change the definition of your property from
public static readonly BindableProperty ControlTextProperty = BindableProperty.Create(
propertyName: nameof(ControlText),
returnType: typeof(string),
declaringType: typeof(SimpleControl),
defaultBindingMode: BindingMode.TwoWay,
defaultValue: "Hello World");
to
public static readonly BindableProperty ControlTextProperty = BindableProperty.Create(
propertyName: nameof(ControlText),
returnType: typeof(string),
declaringType: typeof(SimpleControl),
defaultBindingMode: BindingMode.TwoWay,
defaultValue: "Hello World",
coerceValue: (bindable, value) =>
{
if (value!=null)
return ((string) value).ToUpper();
return value;
});

How can I catch when a switch is Toggled in a XAML template I created?

I have this template:
<?xml version="1.0" encoding="utf-8"?>
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
          xmlns:local="clr-namespace:Test;assembly=Test"
          x:Class="Test.Templates.SwitchTemplate"
              x:Name="this" >
<Switch IsToggled="{Binding IsToggled, Source={x:Reference this}}" />
</StackLayout>
My CS back end looks like this:
        
public partial class SwitchTemplate : StackLayout
{
public SwitchTemplate()
    {
     InitializeComponent();
    }
       
    public static readonly BindableProperty IsToggledProperty =
           BindableProperty.Create(
                nameof(IsToggled),
                typeof(bool),
                typeof(SwitchTemplate),
                default(bool));
        
    public bool IsToggled
    {
     get { return (bool)GetValue(IsToggledProperty); }
        set { SetValue(IsToggledProperty, value); }
    }
}
What I would like to do is to have a method called in the back-end CS of the XAML where the template is used when the toggled state changes.
Can someone give me some suggestions on how I could code the XAML template, its back end CS and the CS of the XAML where the template is used so that I can do some actions when the toggle state changes?
Try adding this (propertyChanged) to your Create
public static readonly BindableProperty IsToggledProperty =
BindableProperty.Create(
nameof(IsToggled),
typeof(bool),
typeof(SwitchTemplate),
default(bool),
propertyChanged: PropertyChanged);
and add this method:
private static void PropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (SwitchTemplate)bindable;
//Do something :)
}

Resources