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
Related
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>
I'm trying to achieve what I think is probably quite simple but as I'm new to Xamarin & Databinding I think I'm getting in a spin.
I have a very simple ContentPage that just has a Databinding to my viewModel for this page and my ContentView, TotalsTemplate.
<ContentPage.BindingContext>
<vm:DealsTodayViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<template:TotalsTemplate></template:TotalsTemplate>
</StackLayout>
</ContentPage.Content>
My viewmodel has a public property of my class, Totals, which has basic int,string,decimal props.
public class DealsTodayViewModel
{
Public string ViewModelPeriod;
public PeriodTotals Totals;
public DealsTodayViewModel()
{
ViewModelPeriod = "TODAY";
Totals = new PeriodTotals
{
Period = "DAILY",
ClientServices_Deals_Chicago = 1,
ClientServices_Deals_Manchester_Na = 1,
ClientServices_Deals_Manchester_Uk = 1,
ClientServices_Ramp_Chicago = 1.2m,
ClientServices_Ramp_Manchester_Na = 1.3m,
ClientServices_Ramp_Manchester_Uk = 1.4m
};
}
}
Now in my TotalsTemplte ContentView I have a Grid with following inside.
<Label Text="{***Binding ViewModelPeriod***}" FontAttributes="Bold"/>
<Frame OutlineColor="Black" Grid.Row="0" Grid.Column="1">
<Label Text="{Binding ***Totals.Period***}" FontAttributes="Bold"/>
</Frame>
My String property on the DealsTodayViewModel is visible in my ContentView but not the Perod property from inside my Totals property, am I binding incorrectly to this?
From the document, data-binding should binding between properties instead of field:
Data binding is the technique of linking properties of two objects so
that changes in one property are automatically reflected in the other
property. Data binding is an integral part of the Model-View-ViewModel
(MVVM) application architecture.
So the solution is change the fields in your vm to properties:
public class DealsTodayViewModel
{
public string ViewModelPeriod { get; set; }
public PeriodTotals Totals { get; set; }
public DealsTodayViewModel()
{
...
}
}
Refer: field and property
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.
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); }
}
}
I have the following page:
<?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:pages="clr-namespace:XamFormsBle.Pages;assembly=XamFormsBle"
x:Name="ContentPageContainer"
x:Class="XamFormsBle.Pages.ConnectPage">
<!--This does not work-->
<ContentPage.BindingContext>
<pages:ConnectPage/>
</ContentPage.BindingContext>
<StackLayout Orientation="Vertical">
<Button Text="Refresh"
Clicked="RefreshDevicesList"/>
<ListView ItemsSource="{Binding DevicesList}"/>
</StackLayout>
</ContentPage>
And the code behind:
public partial class ConnectPage : ContentPage
{
public ObservableCollection<string> DevicesList { get; set; }
public ConnectPage()
{
InitializeComponent();
DevicesList = new ObservableCollection<string>();
//BindingContext = this;
}
public void RefreshDevicesList(object sender, EventArgs e)
{
DevicesList.Add("device");
}
}
What I am trying to achieve is to bind the ListView to the DevicesList. It works when I uncomment the BindingContext line in the constructor. I want to move that line into the .xaml itself. Researching the matter leads to the ContentPage.BindingContext block in the .xaml, but that crashes the program. There also seems to be the approach of setting the Source inside the binding of the ListView's ItemsSource, but I don't understand the syntax to work it in my case (I'm new to Xamarin.Forms and XAML in general). Is there a way to set the BindingContext inside .xaml?
You're using MVVM wrong. You're trying to set viewmodel to the view itself. Create a seperate class for the viewmodel, and it shouldn't be deriving from a ContentPage as you did.