Understanding of data binding in Xamarin forms - xamarin

I am confused with how Xamarin binding works.
OneWay
Indicates that the binding should only propagate changes from source
(usually the View Model) to target (the BindableObject). This is the
default mode for most BindableProperty values.
So by default, if the values are set in the view model it will be reflected in the xaml pages.
But in the Xamarin default template, below is the code to insert a new item. Page doesn't have any two way binding mode set in the markup.
<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"
x:Class="Christianity.Mobile.Views.NewItemPage"
Title="New Item">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Cancel" Clicked="Cancel_Clicked" />
<ToolbarItem Text="Save" Clicked="Save_Clicked" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout Spacing="20" Padding="15">
<Label Text="Text" FontSize="Medium" />
<Entry Text="{Binding Item.Text}" d:Text="Item name" FontSize="Small" />
<Label Text="Description" FontSize="Medium" />
<Editor Text="{Binding Item.Description}" d:Text="Item description" FontSize="Small" Margin="0" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
Here I can see that default values of item are populated when a new page is created and also, edited name and description are available while saving the item.
My question - is two way binding implemented by default without having any binding mode set?
public partial class NewItemPage : ContentPage
{
public Item Item { get; set; }
public NewItemPage()
{
InitializeComponent();
Item = new Item
{
Text = "Item name",
Description = "This is an item description."
};
BindingContext = this;
}
async void Save_Clicked(object sender, EventArgs e)
{
MessagingCenter.Send(this, "AddItem", Item);
await Navigation.PopModalAsync();
}
async void Cancel_Clicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
}
}
UPDATE
Here is my code to load data asynchronously
<?xml version="1.0" encoding="utf-8" ?>
<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"
x:Class="Christianity.Mobile.Views.ItemDetailPage"
Title="{Binding Title}">
<StackLayout Spacing="20" Padding="15">
<Label Text="Text:" FontSize="Medium" />
<Label Text="{Binding Item.Title}" d:Text="Item title" FontSize="Small"/>
<Label Text="Description:" FontSize="Medium" />
<Label Text="{Binding Item.Description}" d:Text="Item description" FontSize="Small"/>
</StackLayout>
</ContentPage>
public class ItemDetailViewModel : BaseViewModel
{
public ItemListItemDTO SelectedItem { get; set; }
public ItemDTO Item { get; set; }
public ICommand LoadItemCommand;
public ItemDetailViewModel(IPageService pageService, ItemListItemDTO selectedItem)
{
SelectedItem = selectedItem;
LoadItemCommand = new Command(async () => await LoadItem());
}
public async Task LoadItem()
{
IsBusy = true;
try
{
// Both are not working
Item = await ItemsDataStore.GetItemAsync(SelectedItem.Id);
//await Device.InvokeOnMainThreadAsync(async () =>
//{
// Item = await ItemsDataStore.GetItemAsync(SelectedItem.Id);
//});
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
}

According to Xamarin.Forms Binding Mode, you can see that most bindable properties have a default binding mode of OneWay but the following properties have a default binding mode of TwoWay:
Date property of DatePicker
Text property of Editor, Entry, SearchBar, and EntryCell
IsRefreshing property of ListView
SelectedItem property of MultiPage
SelectedIndex and SelectedItem properties of Picker
Value property of Slider and Stepper
IsToggled property of Switch
On property of SwitchCell
Time property of TimePicker
These particular properties are defined as TwoWay for a very good reason:
When data bindings are used with the Model-View-ViewModel (MVVM) application architecture, the ViewModel class is the data-binding source, and the View, which consists of views such as Slider, are data-binding targets. MVVM bindings resemble the Reverse Binding sample more than the bindings in the previous samples. It is very likely that you want each view on the page to be initialized with the value of the corresponding property in the ViewModel, but changes in the view should also affect the ViewModel property.
The properties with default binding modes of TwoWay are those properties most likely to be used in MVVM scenarios.
Update:
For example, you get data using Web Api, then loading into ItemDTO Item, please comfirm that you have implement INotifyPropertyChanged interface for ItemDTO class to notify data changed.
public class ItemDTO:ViewModelBase
{
private string _Text;
public string Text
{
get { return _Text; }
set
{
_Text = value;
RaisePropertyChanged("Text");
}
}
private string _Description;
public string Description
{
get
{ return _Description; }
set
{
_Description = value;
RaisePropertyChanged("Description");
}
}
}
The ViewModelBase is the class that implement INotifyPropertyChanged.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Related

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

Updated list does not change on UI when PropertyChanged is called in Xamarin.Forms

I have a bindable StackLayout bound to a List in the ViewModel. When a button is pressed I am adding an element to the list and then invoking PropertyChanged with the name of the list.
I don't understand why the UI does not get updated in this case. I know that I should use an ObservableCollection, and I know how to do it, but I am curious about why the UI does not change if I am invoking PropertyChanged myself.
This is the ViewModel and Model class:
public class Element
{
public string Value { get; set; }
}
public class MainViewModel : INotifyPropertyChanged
{
public List<Element> Elements { get; set; }
public ICommand AddElementCommand { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
AddElementCommand = new Command(AddElement);
Elements = new List<Element>();
Elements.Add(new Element { Value = "test1" });
}
void AddElement()
{
Elements.Add(new Element { Value = "testAgain" });
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Elements"));
}
}
And this is the view:
<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"
x:Class="TestList.MainPage"
x:Name="page">
<StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Elements"
FontSize="Large"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center"/>
<Button Command="{Binding AddElementCommand}"
Text="+" FontSize="Title"
VerticalOptions="Center"/>
</StackLayout>
<StackLayout BindableLayout.ItemsSource="{Binding Elements}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Entry Text="{Binding Value, Mode=TwoWay}" HorizontalOptions="FillAndExpand"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</ContentPage>
If we check the source code of ObservableCollection , we will see that it had implemented INotifyCollectionChanged and INotifyPropertyChanged in default while List didn't .
public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
public class List<T> : ICollection<T>, IEnumerable<T>, IEnumerable, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection, IList
If a list will contain the same items for their whole lifetime, but the individual objects within that list will change, then it's enough for just the objects to raise change notifications (typically through INotifyPropertyChanged) and List is sufficient. But if the list will contain different objects from time to time, or if the order changes, then you should use ObservableCollection.
That is why we always suggest users to choose ObservableCollection instead of List .

Binding ViewModel field outside of Item Source

Hi I have an ItemSource which binds a list of contact
<ListView x:Name="contactsListView" ItemsSource="{Binding contacts}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Image Source="{Binding Should be to my View Model instead of Contacts}"></Image>
<Label Text="{Binding FullName}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
the Full Name binding works fine. My problem is the Image Source is not included in the Contact model so I need to retrieve that from my view Model How can I do it?
VIEW MODEL
public class ContactsViewModel : INotifyPropertyChanged
{
public ObservableCollection<Contact> contacts { get; set; }
private string _contactImage;
public string ContactImage
{
get => _contactImage; set
{
_contactImage = value;
OnPropertyChanged("ContactImage");
}
}
public ContactsViewModel(List<Contact> _contacts)
{
contacts = new ObservableCollection<Contact>(_contacts);
ContactImage = "arrow.png";
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
CODE BEHIND VIEW
public partial class ContactListPage : TabbedPage
{
public ContactsViewModel vm;
public ContactListPage (List<Contact> _contacts)
{
vm = new ContactsViewModel(_contacts);
BindingContext = vm;
InitializeComponent();
}
}
This is because the scope within a ListView item is different than a level higher. To overcome this, create a reference to a parent control. I see you already named your ListView, so we can use that.
Do it like this: <Image Source="{Binding Path=BindingContext.ContactImage, Source={x:Reference contactsListView}}"></Image>

Xamarin forms custom view dropdown is not working

I’m trying to make a custom drop-down in xamarin forms with some bindable properties. I’m having a label and a list view below the label using relative layout so that listview will always below the label and its IsVisible property will be toggled.
I have created a custom view as below:
Dropdown.xaml
<?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="CustomViewXam.CustomViews.Dropdown">
<StackLayout>
<RelativeLayout>
<Label x:Name="selectedLabel" TextColor="Red" Text="xcx"
BackgroundColor="Silver" FontSize="15"
HeightRequest="50"
RelativeLayout.WidthConstraint =
"{ConstraintExpression
Type=RelativeToParent,
Property=Width,
Factor=0.5,
Constant=0}"/>
<ListView x:Name="listView" BackgroundColor="Black"
RelativeLayout.WidthConstraint =
"{ConstraintExpression
Type=RelativeToView,
ElementName=selectedLabel,
Property=Width,
Factor=1,
Constant=0}"
RelativeLayout.YConstraint=
"{ConstraintExpression
Type=RelativeToView,
ElementName=selectedLabel,
Property=Height,
Factor=1,
Constant=0}"
>
</ListView>
</RelativeLayout>
</StackLayout>
</ContentView>
Dropdown.xaml.cs
public partial class Dropdown : ContentView
{
public Dropdown()
{
InitializeComponent();
BindingContext = this;
}
public string TitleText
{
get { return base.GetValue(TitleTextProperty).ToString(); }
set { base.SetValue(TitleTextProperty, value); }
}
private static BindableProperty TitleTextProperty = BindableProperty.Create(
propertyName: "TitleText",
returnType: typeof(string),
declaringType: typeof(string),
defaultValue: "",
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: TitleTextPropertyChanged);
private static void TitleTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (Dropdown)bindable;
control.selectedLabel.Text = newValue.ToString();
}
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create<Dropdown, IEnumerable<object>>(p => p.ItemsSource,
null, BindingMode.OneWay, null, (bindable, oldValue, newValue) => { ((Dropdown)bindable).LoadItems(newValue); });
public IEnumerable<object> ItemsSource
{
get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public void LoadItems(IEnumerable<object> tiles)
{
try
{
var list = tiles;
}
catch (Exception e)
{ // can throw exceptions if binding upon disposal
}
}
}
And I'm using this custom view as below in my xaml page
<local:Dropdown TitleText="dssdasd" ItemsSource="{Binding TitleList}" />
Where TitleList is an ObservableCollection in ViewModel
private ObservableCollection<string> _titleList;
public ObservableCollection<string> TitleList
{
get
{
return _titleList;
}
set
{
if (_titleList!= value)
{
_titleList= value;
NotifyPropertyChanged("TitleList");
}
}
}
Problem:
The Text is visible on the UI and text is coming properly, but the listview below is empty and data is not coming. LoadItems method in Custom dropdown is not getting called even TitleList list is updated. Can anyone please guide me whats wrong i'm doing in the above code.
Note: I'm having BindingContext set to my view model in my view.
Note: I'm having BindingContext set to my view model in my view.
Do you mean you use the code in your Dropdown's construction method to set it?
public Dropdown()
{
InitializeComponent();
// Try to remove the code below
BindingContext = this;
}
I recommend you to set the BindingContext in your page xaml and remove this code in the view:
public MainPage()
{
InitializeComponent();
MyViewModel viewModel = new MyViewModel();
BindingContext = viewModel;
}
In this way the LoadItems() will fire at the first time we initialize the Dropdown's ItemsSource. But when you want to update the list, LoadItems() will not be called. because the instance of the list hasn't been changed. You can try to modify the method like:
public void LoadItems(IEnumerable<object> tiles)
{
try
{
var list = tiles;
listView.ItemsSource = tiles;
}
catch (Exception e)
{ // can throw exceptions if binding upon disposal
}
}
Moreover if you want to see the listView's data on the view, you should set its ItemTemplate for instance:
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Label Text="{Binding}"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>

Xamrin Button is not working for mmvm-light Relay Command

In my Xamrin forms code i have configured MVVM light
RelayCommand is not hitting even though I have set the property in xaml page for the command.
XAML
<?xml version="1.0" encoding="utf-8"?><ContentPage BackgroundColor="White" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ContractorActionSolution.CSA.CSAContentPages.Demo">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<OnPlatform.iOS>0,20,0,0</OnPlatform.iOS>
</OnPlatform>
</ContentPage.Padding>
<ContentPage.Content>
<StackLayout Padding="10" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<Image x:Name="imgLogo" Source="sisystems_logo.jpg" HorizontalOptions="FillAndExpand" VerticalOptions="Start"/>
<StackLayout VerticalOptions="CenterAndExpand" Padding="20" HorizontalOptions="FillAndExpand">
<Label x:Name="lblmsg" TextColor="#F25B63" Text="Error Message"/>
<Entry x:Name="txtUserName" Placeholder="Email"/>
<Entry x:Name="txtPassword" Placeholder="Password" IsPassword="True"/>
<Button Command="{Binding IncrementCommand}" Text="Continue"/>
<Label Text="Can't Login ?" TextColor="#F25B63" HorizontalOptions="Center"/>
</StackLayout>
</StackLayout>
</ContentPage.Content>
Code
public partial class Demo : ContentPage
{
DemoViewModel _myViewModel;
public Demo ()
{
InitializeComponent ();
}
protected override void OnAppearing()
{
base.OnAppearing();
BindingContext = App.Locator.DemoVM;
_myViewModel = BindingContext as DemoViewModel;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_myViewModel.CleanUP();
}
}
Model
public class DemoViewModel : BaseViewModel
{
private string _name;
public string Name
{
get { return _name; }
set { Set(() => Name, ref _name, value); }
}
private RelayCommand _incrementCommand;
/// <summary>
/// Gets the IncrementCommand.
/// </summary>
public RelayCommand IncrementCommand
{
get
{
return _incrementCommand ?? (_incrementCommand = new RelayCommand(
() =>
{
}));
}
}
public void CleanUP()
{
Name = string.Empty;
}
}
I have also tried using RelayCommand and RelayAsyncCommand both are not working.
Property bindings are working fine,
I can add binding for entry with a string property, but not able to add RelayCommand with a button.
The problem might be the fact that the handler is empty and optimization actually omits it after compilation. Try to add anything inside, like Debug.WriteLine("Test"); to see if the breakpoint is hit then.

Resources