Xamarin Cross Platform Picker not binding correctly - c#

I am new to Xamarin Cross Platform apps
I am trying to bind a Picker ItemSource to a List and display one property, with out success! My reference has been from here
Please can some one advise where my error is, my View or Xmal please (or probably both)
The List is a List of StdGrades defined as
namespace FitRestults_Dev1
{
class StdGrade
{
public string Gradelbl
{ get; set; }
public string Grade
{ get; set; }
public static List<StdGrade> Grades()
{
List<StdGrade> GradesList = new List<StdGrade>(){
new StdGrade(){ Gradelbl="10th Gup (White belt)", Grade="G10"},
new StdGrade(){ Gradelbl="9th Gup (Organge belt)", Grade="G9"},
new StdGrade(){ Gradelbl="8th Gup (Organge belt 1 tag)", Grade="G8"},
... };
return GradesList;
}
public List<StdGrade> GradesList => Grades();
public static string GetGrade(string Input)
{
List<StdGrade> GradesList = Grades();
var result = (from r in GradesList where r.Gradelbl == Input select r).First();
return result.Grade;
}
}
For the Content page I have defined a simple view as
namespace FitRestults_Dev1
{
class AddStudentView
{
List<StdGrade> _GradeList;
public List<StdGrade> GradeList
{ get => _GradeList;
set
{
_GradeList = StdGrade.Grades();
}
}
}
}
My content page xmal is
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FitRestults_Dev1.AddStudent"
xmlns:src="clr-namespace:FitRestults_Dev1"
>
<ContentPage.BindingContext>
<src:AddStudentView/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout Padding="10" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Grid>
…
<Picker x:Name="GradePicker" Title="Select a Grade" Grid.Row="2" Grid.Column="1" MinimumWidthRequest="100" FontSize="12"
ItemsSource="GradeList" SelectedIndex="0" ItemDisplayBinding="{Binding Gradelbl}">
</Picker>
</StackLayout>
</ContentPage.Content>
</ContentPage>

You are providing the itemsSource the incorrect way it should be a binding
ItemsSource={Binding GradeList}
Also Stop using Generic.List for binding, MVVM with Xamarin Forms should have ObservableCollections as it inherits from INotifyPropertyChanged and INotifyCollectionChanged

Related

For Binding Property in Xamarin Forms

enter image description here This is the data that is stored in listview
I am trying to show ChampName and ChampImage on the page
Therefore, I used Binding in this code but nothing show up...
Please help me Thank you.
public class UserChamp
{
[JsonProperty("championId")]
public int champID { get; set; }
[JsonProperty("championPoints")]
public int points { get; set; }
public string ChampName { get; set; }
public string ChampImage { get; set; }
}
List<UserChamp> champList;
for (int i = 0; i < userChamps.Length; i++)
{
if (userChamps[i].champID == Convert.ToInt32(championDTO.data.Aatrox.key))
{
UserChamp user = new UserChamp {
ChampName = championDTO.data.Aatrox.name,
ChampImage = "http://ddragon.leagueoflegends.com/cdn/12.23.1/img/champion/Atrox.png"
};
champList.Add(user);
}
if (userChamps[i].champID == Convert.ToInt32(championDTO.data.Ahri.key))
{
UserChamp user = new UserChamp
{
ChampName = championDTO.data.Ahri.name,
ChampImage = "http://ddragon.leagueoflegends.com/cdn/12.23.1/img/champion/Ahri.png"
};
champList.Add(user);
} ....
champLv.ItemsSource = champList; // champLv is listview
// this is xml for showing champion Image and Names.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FinalProject.Champions">
<ContentPage.Content>
<StackLayout>
<ListView x:Name="champLv">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Image Source="{Binding ChampImage}" HeightRequest="100" WidthRequest="100" />
<Label Text="{Binding ChampName}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
private async void Clicked(object sender, ItemTappedEventArgs e)
{
champ = new Champions();
await Navigation.PushAsync(champ, true);
}
I expect to see champion Images and Names.
The listview is saved correctly.
The page I want to show up is champ page so I am using await Navigation.PushAsync(champ, true); in my mainpage so I can go over to it when I click summoner icon

.net Maui binding values multiple levels deep

How can I Pass a Binding from a Page to a View?
I have this Page(Xaml)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:DataBindingTests.Views"
xmlns:model="clr-namespace:DataBindingTests.ViewModels"
x:Class="DataBindingTests.Pages.CoolePage"
Title="CoolePage"
x:DataType="model:CoolesModel">
<VerticalStackLayout>
<Label Text="{Binding YourName}"></Label>
<views:MainView YourName="{Binding YourName}"></views:MainView>
<Button Command="{Binding ChangeNameCommand}"></Button>
</VerticalStackLayout>
</ContentPage>
And its CodeBehind:
using DataBindingTests.ViewModels;
namespace DataBindingTests.Pages;
public partial class CoolePage : ContentPage
{
public CoolePage()
{
this.BindingContext = new CoolesModel();
InitializeComponent();
}
}
If I pass a String into my MainView it works and all events are fired. When I use the binding it doesn't. In this simple test, the app should display two times the same name, but only the Label of the ContentPage has the YourName property printed
<views:MainView YourName="Lars"></views:MainView> <-- Works
<views:MainView YourName="{Binding YourName}"></views:MainView> <-- doesn't work
This is the Xaml of the MainView
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:DataBindingTests.Views"
x:Class="DataBindingTests.Views.MainView">
<VerticalStackLayout>
<Label Text="{Binding YourName}"
VerticalOptions="Center"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ContentView>
This is the CodeBehind of the MainView
namespace DataBindingTests.Views;
public partial class MainView : ContentView
{
public String YourName
{
get
{
String value = (String)GetValue(MainView.YourNameProperty);
return value;
}
set
{
SetValue(MainView.YourNameProperty, value);
}
}
public static readonly BindableProperty YourNameProperty = BindableProperty.Create(nameof(YourName)
, typeof(String)
, typeof(MainView), defaultBindingMode:BindingMode.TwoWay, propertyChanged: OnYourNameChanged);
static void OnYourNameChanged(BindableObject bindable, object oldValue, object newValue)
{
Console.WriteLine(newValue);
}
public MainView()
{
this.BindingContext = this; // Ignore ParentContext
InitializeComponent();
}
}
You can just remove code this.BindingContext = this; from the constructor of MainView.xaml.cs:
public MainView()
{
//this.BindingContext = this;
InitializeComponent();
}
Update:
the above code would only work because the Property in the View and
the Page have the same name.
In this condition, you can modify the code of MainView.xaml as follows:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp929.MainView"
x:Name="TestControlView"
>
<VerticalStackLayout>
<Label Text="{Binding Source={x:Reference TestControlView}, Path=YourName}"
VerticalOptions="Center"
HorizontalOptions="Center" />
</VerticalStackLayout>
</ContentView>
MainView.xaml.cs
public partial class MainView : ContentView
{
public String YourName
{
get
{
String value = (String)GetValue(YourNameProperty);
return value;
}
set
{
SetValue(YourNameProperty, value);
}
}
public static readonly BindableProperty YourNameProperty = BindableProperty.Create(nameof(YourName)
, typeof(String)
, typeof(MainView), defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnYourNameChanged);
static void OnYourNameChanged(BindableObject bindable, object oldValue, object newValue)
{
Console.WriteLine("-----------------> "+newValue);
}
public MainView()
      {
            InitializeComponent();
// this.BindingContext = this;
}
}
CoolesModel.cs
public class CoolesModel
{
// public string YourName { get; set; }
public string Name { get; set; }
public string TestName { get; set; }
public ICommand ChangeNameCommand => new Command(changeMethod);
private void changeMethod()
{
}
public CoolesModel() {
//YourName = "abc";
Name = "abc";
TestName = "test123...";
}
}
MainPage.xaml.cs
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mauiapp929="clr-namespace:MauiApp929"
x:Class="MauiApp929.MainPage">
<ScrollView>
<VerticalStackLayout>
<Label Text="{Binding Name}"></Label>
<mauiapp929:MainView YourName="{Binding TestName}"></mauiapp929:MainView>
<Button Command="{Binding ChangeNameCommand}"></Button>
</VerticalStackLayout>
</ScrollView>
</ContentPage>
In this simple test, the app should display two times the same name, but only the Label of the ContentPage has the YourName property printed
You're overwriting your binding context half way through for some reason, and the context your page binding resolves (the normal way of using it, the parent context) is different than what you actually see on the screen (which is your this.BindingContext = this). And you never set your second context's property.

How to fix the issue with binding generic list to listview in xamarin forms?

I'm new to Xamarin forms. I tried to make a card view using ListView in Xamarin forms. Problem is that I am having issue with binding data from generic list.
The number of rows shown in listview are the same as the number of rows in the list but the property values don't bind to the XAML tags
I've tried it using both approaches by binding the data from code behind and directly to the item source in XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MedicalCenter.Pages.MenuPage">
<ListView x:Name="MyListView"
ItemsSource="{Binding Items}"
ItemTapped="Handle_ItemTapped"
CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding lstHomeMenu}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
public partial class HomePage : ContentPage
{
public HomePage ()
{
InitializeComponent ();
this.BindingContext = new HomeMenuViewModel();
}
}
public class HomeMenuViewModel
{
public IList<HomeMenu> lstHomeMenu { get; set; }
public HomeMenuViewModel()
{
lstHomeMenu = new List<HomeMenu>();
GenerateCardModel();
}
private void GenerateCardModel()
{
lstHomeMenu.Add(new HomeMenu()
{
Title = "Request Appointment",
Icon = "icon_appointment.png",
BackgroundColor = "#479589"
});
lstHomeMenu.Add(new HomeMenu()
{
Title = "Order Prescription",
Icon = "icon_prescription.png",
BackgroundColor ="#4383D9"
});
}
}
public class HomeMenu
{
public string Title { get; set; }
public string Icon { get; set; }
public string BackgroundColor { get; set; }
}
}
When I bind a List to the ListView the data binds properly.
public class HomeMenuViewModel
{
public IList<string> lstHomeMenu { get; set; }
public HomeMenuViewModel()
{
lstHomeMenu = new List<string>();
GenerateCardModel();
}
private void GenerateCardModel()
{
lstHomeMenu = new ObservableCollection<string>
{
"1",
"2",
"3",
"4",
"5",
"6"
};
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MedicalCenter.Pages.MenuPage">
<ListView x:Name="MyListView"
ItemsSource="{Binding Items}"
ItemTapped="Handle_ItemTapped"
CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding .}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
but when I bind the List the binding doesn't work but I get the same number of rows in the ListView as the number of rows in the List.
Edited Question:
List View Result
This is the View I am getting. Seems like the list is binding but the properties inside the object are not binding
UPD:
I still think that your XAML is a problem.
I've recreated a project from the provided code. Please look at the lines 10 and 16 here.
==========
Original answer:
I think that problem is here:
ItemsSource="{Binding Items}"
Your HomeMenuViewModel contains public IList<string> lstHomeMenu { get; set; }, not Items.
Ok. I found the problem. During building the release version when I select
"Sdk and User Assemblies" for "Linking" menu in Android Options properties. after that the list view doesn't bind the data. When I select "Sdk assemblies only" it works fine. Don't know why this is happening.

Databinding is not working between two tabbedpage children

I'm new to Xamarin.Forms development and I'm trying to build a tiny Xamarin app to start.
My app currently has one main TabbedPage that has two ContentPages children. On the ListePage, I have a ListView with an ObservableCollection with OlonaModel is an object with an int Numero and a Text string. I would like the Details page to show details of the selected OlonaModel from the listview of ListePage, but the Details page doesn't seem to update to changes when I select an item from the listview.
Both of the content pages are bound to the same ListPageViewModel. The view model updates when I select an item from the listview, but the changes aren't reflected on the Details page and I'm really confused.
How can I make the Details refresh itself when the SelectedItem of the view model gets set ?
The MainPage :
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage 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"
xmlns:local="clr-namespace:App.Views"
xmlns:viewmodels="clr-namespace:App.ViewModels"
mc:Ignorable="d"
x:Class="App.MainPage">
<TabbedPage.Children>
<local:ListePage/>
<local:Details/>
</TabbedPage.Children>
</TabbedPage>
The ListePage.xaml (ContentPage1 in the post) :
<?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="App.Views.ListePage"
Title="Liste">
<ContentPage.Content>
<StackLayout>
<ListView x:Name="ListeMipoitra" ItemSelected="ListeMipoitra_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Text="{Binding Numero}" Grid.Column="0" FontSize="Medium" FontAttributes="Bold"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
The ListePage.xaml.cs :
namespace App.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ListePage : ContentPage
{
public static ListePageViewModel viewModel;
public ListePage()
{
InitializeComponent();
viewModel = new ListePageViewModel();
this.BindingContext = viewModel;
ListeMipoitra.ItemsSource = viewModel.listeOlonaVM;
}
private void ListeMipoitra_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
viewModel.setSelected((OlonaModel)e.SelectedItem);
}
}
}
The Details.xaml (ContentPage2 in the post) :
<?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="App.Views.Details"
Title="Détails">
<ContentPage.Content>
<StackLayout>
<Label Text="{Binding Text}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
The Details.xaml.cs :
namespace App.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Details : ContentPage
{
public Details()
{
InitializeComponent();
this.BindingContext = ListePage.viewModel.selected;
}
}
}
The ListePageViewModel.cs :
Note: I'm using Fody and PropertyChanged.Fody weaver, so the RaisePropertyChanged() event (should be) is called automatically when a property changes
namespace App.ViewModels
{
public class ListePageViewModel : INotifyPropertyChanged
{
public ObservableCollection<OlonaModel> listeOlonaVM;
public OlonaModel selected { get; set; }
public ListePageViewModel()
{
listeOlonaVM = new ObservableCollection<OlonaModel>();
listeOlonaVM = ListeOlona.liste;
}
public event PropertyChangedEventHandler PropertyChanged;
public void setSelected(OlonaModel olona)
{
selected = olona;
}
}
}
The model of the Olona object :
namespace App.Models
{
public class OlonaModel
{
public int Numero { get; set; }
public string Text { get; set; }
public OlonaModel(int num, string text)
{
this.Numero = num;
this.Text= text;
}
}
}
The ListeOlona.cs where the model of the list is stored:
The InitializeList() method is called at App Startup.
namespace App.ViewModels
{
public static class ListeOlona
{
public static ObservableCollection<OlonaModel> liste = new ObservableCollection<OlonaModel>();
public static void InitializeList()
{
liste.Add(new OlonaModel(1,
"FirstItem"));
liste.Add(new OlonaModel(2,
"Second Item"));
}
}
}
According to your description, there is tabbedpage, two pages, one is Listpage, another is detailpage, you want to select listview item in listpage, then other detail info will display detailpage, am I right?
If yes, I suggest you can do this like this:
TabbedPage:
<TabbedPage
x:Class="App4.TabbedPage2"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App4"
x:Name="tabbedpage2">
<!-- Pages can be added as references or inline -->
<TabbedPage.Children>
<local:ListPage Title="Mian page" BindingContext="{Binding}" />
<local:DetailPage Title="Detail page" BindingContext="{Binding select}" />
</TabbedPage.Children>
public partial class TabbedPage2 : TabbedPage
{
public TabbedPage2 ()
{
InitializeComponent();
this.BindingContext = new ListePageViewModel();
}
}
public class OlonaModel
{
public int Numero { get; set; }
public string Text { get; set; }
}
public class ListePageViewModel : ViewModelBase
{
public ObservableCollection<OlonaModel> listeOlonaVM { get; set; }
private OlonaModel _select;
public OlonaModel select
{
get { return _select; }
set
{
_select = value;
RaisePropertyChanged("select");
}
}
public ListePageViewModel()
{
listeOlonaVM = new ObservableCollection<OlonaModel>()
{
new OlonaModel(){Numero=1,Text="first item"},
new OlonaModel(){Numero=2,Text="second item"},
new OlonaModel(){Numero=3,Text="third item"},
new OlonaModel(){Numero=4,Text="fouth item"}
};
select = listeOlonaVM[0];
}
}
ListPage:
<StackLayout>
<ListView ItemsSource="{Binding listeOlonaVM}" SelectedItem="{Binding select}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Numero}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
DetailPage:
<StackLayout>
<Label
HorizontalOptions="CenterAndExpand"
Text="{Binding Text}"
VerticalOptions="CenterAndExpand" />
</StackLayout>
ViewModelBase implement INotifyPropertyChanged interface.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Xamarin Forms Refresh Layout after Observable Collection changes

I'm having a though time trying to "refresh" one of the views where I'm using a WrapLayout. Even though I change the items inside the ObservableCollection the page does not show the changes made.
Code below (some obfuscation needed due to confidentiality issues but I think the most important part is all there). Any help would be greatly appreciated.
Thanks.
ItemCardsViewModel.cs
// INotifyPropertyChanged implemented on BaseViewModel
public class ItemCardsViewModel : BaseViewModel
{
public ObservableCollection<ItemViewModel> Items { get; set; }
public ICommand RefreshCardsCommand { get; private set; }
public Action OnItemsChanged { get; internal set; }
public ItemCardsViewModel()
{
(...)
this.RefreshCardsCommand = new Command(RefreshCards);
}
private void RefreshCards(object x)
{
this.Items = new ObservableCollection<ItemViewModel>(
this.Items.Select(x =>
{
x.IsVisible = false;
return x;
}));
OnPropertyChanged(nameof(this.Items));
if (this.OnItemsChanged != null)
OnItemsChanged();
}
(...)
}
ItemCards.xaml.cs
public partial class ItemCards : ContentPage
{
ItemCardsViewModel ViewModel => ((ItemCardsViewModel)this.BindingContext);
public ItemCards()
{
InitializeComponent();
foreach (var item in ViewModel.Items)
{
var cell = new ItemView { BindingContext = item };
CardsLayout.Children.Add(cell);
}
ViewModel.OnItemsChanged += CardsLayout.ForceLayout;
}
}
ItemCards.xaml
<?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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True" (...)>
<ContentPage.Content>
<Grid>
(...)
<ScrollView Orientation="Vertical" Padding="0,5,0,5" Grid.Column="0" Grid.Row="2">
<ScrollView.Content>
<local:WrapLayout x:Name="CardsLayout" Spacing="5" HorizontalOptions="Start" VerticalOptions="Start" />
</ScrollView.Content>
</ScrollView>
</Grid>
</ContentPage.Content>
</ContentPage>
EDIT: Forgot to mention but I'm using Prism so the ViewModel is automatically wired up to the view.
EDIT 2: Just a quick update on this one... The issue persists even if I don't create a new Instance of the ObservableCollection on the RefreshCards method but rather loop through the records and set the IsVisible property one by one. Also tried to add a new ItemViewModel to the collection. Always the same result, no changes are shown on the page.

Categories