Why is my simple data binding not working? - xaml

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.

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>

Databinding of Listview in custom contentview

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

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

ListView selected Item binding not work

I have a listview in uwp , and a view model that declare pataient_List and selected_patient in this. my listview show itemsource but I don't know why my listview do not show selected item.
<ListView ItemsSource="{Binding pataient_List}"
SelectedItem="{Binding selected_patient, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding name_to_show_menu, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
viewmodel is
public class patient_view_model : notify_property_changed_base
{
public patient_view_model(patient patient_param)
{
pataient_List = new ObservableCollection<patient>();
load_patient(); // this function put patients in pataient_List
selected_patient = patient_param;
}
public patient selected_patient
{
get { return _selected_patient; }
set
{
if (_selected_patient != value)
{
_selected_patient = value;
RasiePropertyChanged();
}
}
}
public ObservableCollection<patient> pataient_List { set; get; }
One cause could be that the selected item must be one of the objects in the pataient_List.
Another cause is perhaps because you're setting the selected_patient in the constructor of the view model which is definitely before you bind the view model to the view. So, why not trying to set the selected_patient after you bind the view model to the view.
Forget about the ItemTemplate in the ListView.
<ListView ItemsSource="{Binding pataient_List}"
SelectedItem="{Binding selected_patient, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Foreground="Black">
</ListView>
.NET doesn't have a clue about how you want your data to be displayed, so it just calls the ToString() method on each object and uses that to represent the item. Override the ToString() method in the patient object to display what you need. Here is the code:
public class patient
{
public string name_to_show_menu;
public override string ToString()
{
return this.name_to_show_menu;
}
}
I solve issue with this one answer.
public override bool Equals(object obj)
{
if (this.name_to_show_menu == (obj as patient).name_to_show_menu)
return true;
else
return false;
}

XAML ListView in ListView

Is it possible to make a ListViewin a ListView and if yes is it even possible to bind data in both of them? I already tried it but it wont work the first List is shown as i want but the second is not shown.
The Code Looks kind of like this:
<ListView ItemSource="{Binding Item}" x:Name="ListA">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Day}"/>
<ListView x:Name="ListB" ItemSoruce="{Binding Item}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Time}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C#
listA.ItemsSource = timebooking.TimeBookingDataPeriod.TimeBookingData;
Hope sombody can help me :)
First of all, you should either go for a binding ItemSource={Binding Item} or setting the ItemSource in code listA.ItemsSource = ... as one overwrites the other. So I'll ignore the fact that you're trying to set it in code and go with the binding (which means you have a datacontext set on your view).
<ListView ItemSource="{Binding WeekDays}" x:Name="ListA">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Day}"/>
<ListView x:Name="ListB" ItemSource="{Binding TimesOfDay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Time}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I've changed your first ItemSource to WeekDays and the second ItemSource to TimesOfDay for clarity. This to explain the way how binding and datacontexts work.
The outer ListView (ListA) inherits its DataContext from the outer items, most likely your page / usercontrol.
In your ListViewItem, the datacontext is changed to a single item in the WeekDays collection. => every property bound under this tree has to be in a WeekDay object.
The inner ListView (ListB) inherits its DataContext from the ListViewItem.
In the ListViewItem, the DataContext is changed to a single item in the TimesOfDay collection.
Every ListView is a new level of objects. So for my sample to work, you need following class structure, not that I'll simplify the code and just use properties (while you should correctly implement INotifyPropertyChanged for UI updates).
public class SampleViewModel : INotifyPropertyChanged
{
public ObservableCollection<WeekDay> WeekDays { get; set; } // first listview
}
public class WeekDay
{
public string Day { get; set; }
public ObservableCollection<TimeOfDay> TimesOfDay { get; set; } // second listview
}
public class TimeOfDay
{
public string Time { get; set; }
}
If you want to use 2 properties from the page's viewmodel, you'll have to use an element binding in ListB. But this is only for scenarios were the items in the inner listview are not correlated to the ones in the outer listview.
{Binding ElementName=ListA, Path=DataContext.ItemsForB}
ItemsSource not ItemSource
Here is an example:
public class ClassB
{
public string Name { get; set; }
public ObservableCollection<ClassA>classAList { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ClassB"/> class.
/// </summary>
public ClassB()
{
Name = String.Empty;
classAList = new ObservableCollection<ClassA>();
}
}
public class ClassA
{
public string Caption { get; set; }
}
Create list:
ObservableCollection<ClassB> list = new ObservableCollection<ClassB>();
Init list:
ClassB item1 = new ClassB() { Name = "listAItem1" };
item1.classAList.Add(new ClassA { Caption = "listBItem1" });
item1.classAList.Add(new ClassA { Caption = "listBItem2" });
item1.classAList.Add(new ClassA { Caption = "listBItem3" });
item1.classAList.Add(new ClassA { Caption = "listBItem4" });
item1.classAList.Add(new ClassA { Caption = "listBItem5" });
list.Add(item1);
ClassB item2 = new ClassB() { Name = "listAItem1" };
item2.classAList.Add(new ClassA { Caption = "listBItem1" });
item2.classAList.Add(new ClassA { Caption = "listBItem2" });
item2.classAList.Add(new ClassA { Caption = "listBItem3" });
item2.classAList.Add(new ClassA { Caption = "listBItem4" });
item2.classAList.Add(new ClassA { Caption = "listBItem5" });
list.Add(item1);
this.DataContext = list;
and XAML:
<ListView ItemsSource="{Binding}" x:Name="ListA">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="Day"/>
<ListView x:Name="ListB" ItemsSource="{Binding classAList}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Caption}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
It seems you want to create a grouped list, in this case is better to use gridview with groups:
Good step by step tutorial for grouped gridview