Databinding of Listview in custom contentview - xaml

I am trying to create a contentview which has a listview xamarinforms.
I want to set a property of the contentview that is then used to bind the data to the listview.
Unfortunately I am not able to populate it.
I broke down the example to get a poc. The desired result should be a contentpage with all the names.
Any helb appreciated. Thx in advance!
The example consists of:
Contentpage:
Adds the contentview.
It also has a bindingcontext to a viewmodel - CompanyVM.
Sets Property PersonList of contentview to PersonVM.Personlist. (Unsure if correct)
Contentview.XAML:
XAML of contentview
Bindings for listview (unsure if correct)
Contentview.cs
XAML code-behind
Property Settings for contentview
CompanyVM:
Viewmodel used
Company & Person & Mockup
Simple classed to work with
Example
ContentMainPage
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:cv="clr-namespace:ContentViewExample.XAML"
xmlns:vm="clr-namespace:ContentViewExample.ViewModel"
xmlns:local="clr-namespace:ContentViewExample"
x:Class="ContentViewExample.MainPage"
x:Name="mainpage">
<ContentPage.BindingContext>
<vm:CompanyVM/>
</ContentPage.BindingContext>
<StackLayout>
<cv:personlistCV Company="{x:Reference mainpage }"/>
<!--Is this correct?-->
</StackLayout>
</ContentPage>
```
Contentview.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="ContentViewExample.XAML.personlistCV"
x:Name="cvPersons">
<ContentView.Content>
<StackLayout>
<ListView x:Name="lstPerson"
ItemsSource="{Binding Company.Persons}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Path=Name}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentView.Content>
</ContentView>
Contentview.cs
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace ContentViewExample.XAML
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class personlistCV : ContentView
{
public personlistCV ()
{
InitializeComponent ();
}
//CompanyProperty
public static readonly BindableProperty CompanyProperty =
BindableProperty.Create(
"Company",
typeof(CompanyVM),
typeof(personlistCV),
null);
public personlistCV Company
{
set { SetValue(CompanyProperty, value); }
get { return (personlistCV)GetValue(CompanyProperty); }
}
}
}
CompanyVM
namespace ContentViewExample.ViewModel
{
public class CompanyVM: ViewModelBase
{
ObservableCollection<Person> persons;
string companyname;
public CompanyVM()
{
companyname = "Test Company";
persons = new ObservableCollection<Person>();
foreach (Person item in MockData.GetPeople())
persons.Add(item);
}
public string Company
{
set { SetProperty(ref companyname, value); }
get { return companyname; }
}
public ObservableCollection<Person> Persons
{
set { SetProperty(ref persons, value); }
get { return persons; }
}
}
}
Company & Person
public class Company
{
public string Name { get; set; }
}
public class Person
{
public string Name{get;set;}
}
public static class MockData
{
public static List<Person> GetPeople()
{
List<Person> tmp = new List<Person>
{
new Person
{
Name="Ted"
},
new Person
{
Name="Jennifer"
},
new Person
{
Name="Andy"
},
new Person
{
Name="Oscar"
}
};
return tmp;
}
}

You have tried to bind personlistCV.Company the following way
<StackLayout>
<cv:personlistCV Company="{x:Reference mainpage }"/>
<!--Is this correct?-->
</StackLayout>
I see several issues here:
Bindings are set with the XAML extension Binding
The Company is set to the mainpage, which is of Type MainPage.
It should rather be set to mainpage.BindingContext (this is a CompanyCV object)
Furthermore the personlistCV.Company is of type personlistCV, which does not really make sense. The field should be of type CompanyVM, since we'd like to bind the viewmodel (and personlistCV does not even have a Persons (bindable) property).
Having said that, the following code should work:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:cv="clr-namespace:ContentViewExample.XAML"
xmlns:vm="clr-namespace:ContentViewExample.ViewModel"
xmlns:local="clr-namespace:ContentViewExample"
x:Class="ContentViewExample.MainPage"
x:Name="mainpage">
<ContentPage.BindingContext>
<vm:CompanyVM/>
</ContentPage.BindingContext>
<StackLayout>
<cv:personlistCV Company="{Binding Path=BindingContext, Source={x:Reference mainpage}}"/> <!-- Bind the element to `mainpage.BindingContext` -->
</StackLayout>
</ContentPage>
Maybe
<cv:personlistCV Company="{Binding .}"/>
could work, too, since . usually binds to the BindingContext of the view and the BindingContext of the page is passed down to the views (unless another BindingContext is set for the views explicitly).
And for the companyCV
public partial class personlistCV : ContentView
{
public personlistCV ()
{
InitializeComponent ();
}
//CompanyProperty
public static readonly BindableProperty CompanyProperty =
BindableProperty.Create(
"Company",
typeof(CompanyVM),
typeof(personlistCV),
null);
public CompanyVM Company
{
set { SetValue(CompanyProperty, value); }
get { return (personlistCV)GetValue(CompanyProperty); }
}
}

Related

Xamarin Froms Entry Custom Control does not work

I'm trying to make a custom control to use it in multiple places.
the thing is it works fine with the label but when it comes to entry it won't even run and it gives me
No property, BindableProperty, or event found for "EntryText"
here is my custom control Xaml:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
<ContentView.Content>
<StackLayout>
<Label x:Name="MyLabel" />
<Entry x:Name="MyEntry" />
</StackLayout>
</ContentView.Content>
</ContentView>
and its code behind
public partial class MyCustomControl : ContentView
{
public static readonly BindableProperty LabelTextProperty = BindableProperty.Create(
nameof(LabelText),
typeof(string),
typeof(MyCustomControl),
propertyChanged: LabelTextPropertyChanged);
private static void LabelTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (MyCustomControl)bindable;
control.MyLabel.Text = newValue?.ToString();
Debug.WriteLine("LabelTextPropertyChanged: " + control.MyEntry);
Debug.WriteLine("LabelTextPropertyChanged: new value" + newValue);
}
public string LabelText
{
get => base.GetValue(LabelTextProperty)?.ToString();
set
{
base.SetValue(LabelTextProperty, value);
}
}
public static BindableProperty EntryBindableProperty = BindableProperty.Create(
nameof(EntryText),
typeof(string),
typeof(MyCustomControl),
propertyChanged:EntryBindablePropertyChanged
);
private static void EntryBindablePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (MyCustomControl)bindable;
Debug.WriteLine("EntryBindablePropertyChanged: " + control.MyEntry);
Debug.WriteLine("EntryBindablePropertyChanged: new value" + newValue);
}
public string EntryText
{
get => base.GetValue(EntryBindableProperty)?.ToString();
set
{
base.SetValue(EntryBindableProperty, value);
}
}
public MyCustomControl()
{
InitializeComponent();
}
}
and its Usage
<StackLayout>
<local:MyCustomControl
LabelText="{Binding BindingLabel}"
EntryText="{Binding BindingEntry}"/>
</StackLayout>
NOTE:
I tried to remove the
</ContentView.Content>
from my xaml because i've seen some example like that,
and also i've tried to set binding in the code behind constructor
public MyCustomControl()
{
InitializeComponent();
MyEntry.SetBinding(Entry.TextProperty, new Binding(nameof(EntryText) , source: this));
}
but neither did work for the entry .
so how can i resolve this and does it make any additional setting if i want to bind the value to a view model.
thanks in advance.
Update:
#Jessie Zhang -MSFT
thanks for your help I really appreciate it,
however I discovered a bug in MyCustomControl code => EntryTextProperty
is that i have to declare the defaultBindingMode to be able to get the data passed to a ViewModel Property.
so i changed the BindableProperty code to:
public static BindableProperty EntryTextProperty = BindableProperty.Create(
nameof(EntryText),
typeof(string),
typeof(MyCustomControl),
defaultBindingMode: BindingMode.OneWayToSource,
propertyChanged:EntryBindablePropertyChanged
);
You didn't bind the Entry.Text and EntryText property in MyCustomControl.xaml:
Please refer the following code:
<?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="CardViewDemo.Controls.MyCustomControl"
x:Name="this"
>
<ContentView.Content>
<StackLayout BindingContext="{x:Reference this}">
<Label x:Name="MyLabel" Text="{Binding LabelText}" />
<Entry x:Name="MyEntry" Text="{ Binding EntryText}" />
</StackLayout>
</ContentView.Content>
</ContentView>
And I used the following code to did a test, it works properly.
<local:MyCustomControl
LabelText="test1"
EntryText="test2"/>
Update:
From document Create a property,we know that:
The naming convention for bindable properties is that the bindable
property identifier must match the property name specified in the
Create method, with "Property" appended to it.
So, you can change your code EntryBindableProperty to EntryTextProperty, just as follows:
public static readonly BindableProperty EntryTextProperty = BindableProperty.Create(nameof(EntryText), typeof(string),typeof(MyCustomControl),string.Empty, BindingMode.TwoWay, null, EntryBindablePropertyChanged);
public string EntryText
{
get => base.GetValue(EntryTextProperty)?.ToString();
set
{
base.SetValue(EntryTextProperty, value);
}
}
And the xaml code is:
<?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="CardViewDemo.Controls.MyCustomControl"
x:Name="template"
>
<ContentView.Content>
<StackLayout >
<Label x:Name="MyLabel" Text="{Binding Source={x:Reference template},Path=LabelText}"/>
<Entry x:Name="MyEntry" Text="{Binding EntryText, Source={x:Reference template}}" >
</Entry>
</StackLayout>
</ContentView.Content>
</ContentView>

Why is my simple data binding not working?

For the life of me I can't understand why this simple data binding isn't working at all.
First I tried it with the combo box (picker) items, and even though the string list "categories_names" gets populated with values in the code behind, they don't show up in the view.
Then I tried a simple text on a label, which didn't work either.
The XAML file:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="budget.Pages.CreateItemBase"
BindingContext="budget.Pages.CreateItemBase">
<ContentPage.Content>
<StackLayout>
<Label
Text="{Binding Thing}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
/>
<Picker
x:Name ="IB_Category"
ItemsSource="{Binding categories_names}"
Title="Select a Category"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
The code behind:
public partial class CreateItemBase : ContentPage
{
private List<Category> Categories;
private string thing;
public string Thing
{
get { return thing; }
set { thing = value; }
}
public IList<string> categories_names
{
get { return Categories.Select(x => x.Name).ToList(); }
set { Categories = new List<Category>(); }
}
public CreateItemBase ()
{
InitializeComponent ();
var z = BindingContext; // To check if the BindingContext is right during debug
Thing = "whatever";
SQLiteConnection DBCon = new SQLiteConnection(App.DBLocation);
DBCon.CreateTable<Category>(); // Creates the table if it doesn't exist yet
Categories = DBCon.Table<Category>().ToList();
DBCon.Close();
}
}
Any idea on what might be wrong? Much appreciated.

Add multiple bindingcontexts to xaml file

I try to realize my first MVVM-Project.
First I created model called "person.cs".
Then I created a modelview "AddPerson.cs", which should dynamically creates the data, which is stored in person.cs.
In my view (completely created with xaml) I have a button which should call a method "CreatePerson()" from my "AddPerson.cs". I like to bind the method.
Additionally I have created a label which should be bound to the class "person.cs" for example to the public string "Name".
How can I set the BindingContext of the Button to the "AddPerson.cs"-class and the BindingContext of the Label to the "person.cs"-class?
Yes this is possible.
Most of the Elements inherit BindablObject. Each BindableObjaect has a BindingContext Property.
See: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-binding-basics
MainViewModel
The Viewmodel for your entire page, that holds every sub-viewmodel.
public class MainViewModel
{
public AddPersonViewModel AddPersonViewModel { get; }
public PersonViewModel PersonViewModel { get; }
public MainViewModel()
{
// the passed action is just a fake action to simulate adding a person
AddPersonViewModel = new AddPersonViewModel(value => PersonViewModel.Name = value);
PersonViewModel = new PersonViewModel();
}
}
AddPersonViewModel
Contains your add logic.
public class AddPersonViewModel : INotifyPropertyChanged
{
public AddPersonViewModel(Action<string> onAction)
{
AddPerson = new Command(() =>
{
onAction(NewName); // call your update logic
NewName = ""; // reset name
});
}
public Command AddPerson { get; }
private string _name;
public string NewName
{
get => _name;
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewName)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
PersonViewModel
Contains your "new" Person.
public class PersonViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
MainPage
Create and set your MainViewModel.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MainViewModel();
}
}
MainPage.xaml
Here we bind the BindingContext of Entry and Button to the AddPersonViewModel property of our ContentPage's BindingContext which is the MainViewModel. And then we bind the Text of the Label and the Command of the Button to NewName and AddPerson properties of the local BindingContext, which is AddPersonViewModel
Same for Label.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App5"
x:Class="App5.MainPage">
<StackLayout>
<Entry BindingContext="{Binding AddPersonViewModel}" Text="{Binding NewName}"
HorizontalOptions="FillAndExpand" />
<Button BindingContext="{Binding AddPersonViewModel}" Text="Click me!" Command="{Binding AddPerson}"
HorizontalOptions="Center" />
<Label Text="Added Person:" FontAttributes="Bold"
HorizontalOptions="Center"/>
<Label BindingContext="{Binding PersonViewModel}" Text="{Binding Name}"
HorizontalOptions="Center"/>
</StackLayout>
</ContentPage>
The example is very hacky, but I think you get the point. The key is the already mentioned property BindingContext
You are missing some essential concepts which result in your requests being strange.
You don't data bind to the class definition, but to the instance of the class. As one ViewModel is a class it may contain instances of other classes that you data bind to, and everything except that is in 99% of cases a wrong thing to do and your example is not one of those 1% of cases.
So basically your ViewModel should be something like:
public class PersonViewModel
{
public Person Person {get; set}
public ICommand AddPersonCommand {get; set}
}
Your BindingContext is then an instance of PersonViewModel and then on Label you bind to Person while on the button you would bind to AddPersonCommand.

Xamarin forms (Cross-Platform) : Multiple type of cells in ListView

I am new to Xamarin. I have a requirement where I have to implement a ListView or say tableView that have multiple different type-size cells.
And I also have to add Header for a particular section of cells, and some of my custom cells have a horizontal scroll in it.
I have done this thing in iOS native UITableView before, but don't know how this done in Xamarin cross platform, can anyone help me out this?
You are looking for DataTemplateSelector, which is very well documented in the official Xamarin.Forms documentation.
The basics are that you create your own DataTemplateSelector class:
public class MyDataTemplateSelector : DataTemplateSelector
{
}
In that class you override OnSelectTemplate:
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
}
By checking the type of the item argument, you should be able to figure out which template to return.
So lets say you have a ViewModel for Dog and one for Cat and want to show a different DataTemplate for each of those. You would do something like:
public class DogCatTemplateSelector : DataTemplateSelector
{
public DataTemplate DogTemplate { get; set; }
public DataTemplate CatTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
if (item is DogViewModel)
return DogTemplate;
return CatTemplate;
}
}
Then you can consume this in your XAML:
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="dogTemplate">
<ViewCell>
... <---- define your look of dog template here
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="catTemplate">
<ViewCell>
... <---- define your look of cat template here
</ViewCell>
</DataTemplate>
<local:DogCatTemplateSelector x:Key="dogCatTemplateSelector"
DogTemplate="{StaticResource dogTemplate}"
CatTemplate="{StaticResource catTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
Then simply set the ItemTemplate to your dogCatTemplateSelector instance you've defined in the resources on your ListView:
<ListView ItemsSource="{Binding DogsCatsCollection}" ItemTemplate="{StaticResource dogCatTemplateSelector}" />
Your ViewModel would then look something like:
public class Animal : INotifyPropertyChanged
{
}
public class DogViewModel : Animal
{
}
public class CatViewModel : Animal
{
}
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<Animal> DogsCatsCollection { get; }
= new ObservableCollection<Animal>();
}
Then you just populate DogsCatsCollection with instances of dogs and cats.

Xamarin Forms - Changes to ObservableCollection are not updating the View

I have a ViewModel that, when a Command is executed, gets items from a service and adds those items to an ObservableCollection.
The ObservableCollection is bound to a ListView.
My problem is that adding items to the ObservableCollection is not automatically updating the View - the ListView stays empty. If I call OnPropertyChanged(nameof(ItemList)) then the ListView is updated - but shouldn't the ObservableCollection be taking care of raising this?
I am using the latest Xamarin.Forms (2.3.4.247). This issue only affects Android - if I run on iOS it works correctly.
The ViewModel that implements INotifyPropertyChanged:
public class ListViewModel : ViewModelBase
{
private readonly IService m_Service;
public ListViewModel(IService service)
{
m_Service = service;
ItemList = new ObservableCollection<Item>();
RefreshCommand = new Command(async () => await RefreshData());
}
public ICommand RefreshCommand {get;set;}
public ObservableCollection<Item> ItemList { get; private set; }
private async Task RefreshData()
{
ItemList.Clear();
var newItems = await m_Service.GetItems();
if(newItems != null) {
foreach(var n in newItems)
{
ItemList.Add(new ItemViewModel() { Title = n.Title });
}
}
}
The Item ViewModel (that also implements INotifyPropertyChanged):
public class ItemViewModel : ViewModelBase
{
private string m_JobTitle;
public ItemViewModel()
{
}
public string Title
{
get { return m_Title; }
set
{
m_Title = value;
OnPropertyChanged(nameof(Title));
}
}
}
The 'List' View:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ui="clr-namespace:Test.Mobile.UI;assembly=Test.Mobile.UI"
x:Class="Test.Mobile.UI.MyListView">
<ContentView.Content>
<StackLayout Orientation="Vertical">
<ListView ItemsSource="{Binding ItemList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ui:ItemView></ui:ItemView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentView.Content>
</ContentView>
The 'Item' View:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:enums="clr-namespace:Test.Mobile.Models.Enums;assembly=Test.Mobile.Models"
x:Class="Test.Mobile.UI.JobItemView">
<ContentView.Content>
<Label Text={Binding Title}/>
</ContentView.Content>
</ContentView>
What version of Xamarin Forms are you using?
Update your project to the latest version
If you still having the issue, after adding the all elements try this:
ItemList = new ObservableCollection(newItems);
If that does not work, maybe could be an issue in your ViewModelBase.
For Handle PropertyChanged I use this nice package
https://github.com/Fody/PropertyChanged