Xamarin forms list view items source not updating - c#

I am having a problem with my xamarin forms list view. I have created a list view that has an items source that is binded to a List of a custom class. In the app.xaml.cs, I have the exact same list. In another form, I am updating the list that resides in app.xaml.cs. When the first form with the list view is brought back up, on appearing I set the local list that is binded to the list in app.xaml.cs. But, the items in the list view UI dont update. Can someone please help me with this?
MainPage.xaml (with list view):
`
<?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="TrackExpensesApp.MainPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="+"
Clicked="OnAddExpenseClicked" />
</ContentPage.ToolbarItems>
<ListView x:Name="ExpenseEntriesListView"
ItemsSource="{Binding ExpenseEntries}"
SelectionMode="Single"
ItemSelected="ExpenseEntriesItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Padding="10"
RowDefinitions="Auto, *"
ColumnDefinitions="Auto, *">
<Label Grid.ColumnSpan="2"
Text="{Binding Name}"
VerticalOptions="End" />
<Label Grid.Row="1"
Text="{Binding DateCreated}"
VerticalOptions="End" />
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Category}"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
`
MainPage.xaml.cs (with list view):
`
public partial class MainPage : ContentPage
{
public IList<ExpenseEntry> ExpenseEntries { get; set; }
public MainPage()
{
InitializeComponent();
}
async void OnAddExpenseClicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new ExpensesEntryPage());
}
protected override void OnAppearing()
{
ExpenseEntries = (Application.Current as TrackExpensesApp.App).ExpenseEntries;
BindingContext = this;
}
async void ExpenseEntriesItemSelected(object sender, SelectedItemChangedEventArgs e)
{
ExpenseEntry selectedItem = e.SelectedItem as ExpenseEntry;
await Navigation.PushAsync(new ExpensePage());
}
}
`
App.xaml.cs:
`
public partial class App : Application
{
public IList<ExpenseEntry> ExpenseEntries { get; set; }
public IList<string> Categories { get; set; }
public App()
{
InitializeComponent();
// Load in all expenses before loading in the main page
ExpenseEntries = new List<ExpenseEntry>();
Categories = new List<string>();
Categories.Add("Monthly");
Categories.Add("Vacation");
Categories.Add("Other");
MainPage = new NavigationPage(new MainPage());
BindingContext = this;
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
}
`
I would truly appreciate it if someone would get back to me as soon as possible. Thank you!

We need to call OnPropertyChanged in setter method to notify that a change happened on a property ,so that the UI would change after then.
MainPage
Change your code as below
private IList<string> expenseEntries;
public IList<string> ExpenseEntries {
get
{
return expenseEntries;
}
set
{
expenseEntries = value;
OnPropertyChanged(); //add this line
}
}

Related

MAUI: ListView Binding With Custom ViewCell

I use FreshMvvm to develop and run MAUI project on Windows.
But I have some binding issues with ListView and my custom template.
The following is my code:
Model:
public class BaseModel
{
public string Code{ get; set; }
}
public class NameModel: BaseModel
{
public string Name{ get; set; }
}
ViewModel:
public class MainPageModel : FreshBasePageModel
{
private readonly IApiService _apiService;
private List<NameModel> _nameModelList;
public List<NameModel> NameModelList
{
get => _nameModelList;
private set
{
_nameModelList= value;
RaisePropertyChanged(nameof(NameModelList));
}
}
public MainPageModel(IApiService apiService)
{
_apiService = apiService;
}
protected override void ViewIsAppearing(object sender, EventArgs e)
{
base.ViewIsAppearing(sender, e);
Task.Run(() => GetNameData());
}
private async Task GetNameData()
{
var result = await _apiService.GetNameData();
NameModelList= result.GetRange(1, 10);
}
}
I create a list and use an api service to get a name model list data.
If api service gets the data, NameModelList will be updated.
NameModelList is the property which will be bind on Listview.ItemsSource
MainPage.xmal:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyNamespace.ViewCells.CustomListViewCell"
x:Class="MyNamespace.Pages.MainPage"
BackgroundColor="{DynamicResource SecondaryColor}">
<Grid RowSpacing="25"
RowDefinitions="Auto"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ListView
x:Name="MyListView"
ItemsSource="{Binding NameModelList}"
Grid.Row="0"
WidthRequest="800"
HeightRequest="800"
BackgroundColor="Gray"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<local:MyCustomViewCell/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage>
Custom ViewCell (.xml):
<ViewCell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyNamespace.ViewCells.CustomListViewCell.MyCustomViewCell">
<Grid RowSpacing="100" WidthRequest="100" HeightRequest="100">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*" />
</Grid.ColumnDefinitions>
<StackLayout
GridLayout.Row="0"
GridLayout.Column="0">
<Label
Text="{Binding Code}"
FontSize="30"/>
<Label
Text="{Binding Name}"
FontSize="30"/>
</StackLayout>
</Grid>
</ViewCell>
Custom ViewCell (.cs)
public partial class MyCustomViewCell: ViewCell
{
public static readonly BindableProperty CodeProperty =
BindableProperty.Create("Code", typeof(string), typeof(MyCustomViewCell), "");
public string Code
{
get { return (string)GetValue(CodeProperty); }
set { SetValue(CodeProperty, value); }
}
public static readonly BindableProperty NameProperty =
BindableProperty.Create("Name", typeof(string), typeof(MyCustomViewCell), "");
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
I define a custom ViewCell files and put this ViewCell in the Listview of MainPage.
Now my question is my Listview can't show data successfully.
I'm sure that NameModelList has value and its count is more than 1.
But I can see nothing.
The output log has no error, and the breakpoints in MyCustomViewCell.cs are never triggered.
So I think I have some binding issues, but I can't find it out.
To get to the bottom of this I took your code and put it in a project so I could have a little play with it. You can find the repo here. Not to be rude here or anything, but might be a good idea for a next question to do that yourself, that will help speed things up :)
Anyway, the problem is much more subtle. Because you're using XAML for your layout, you'll have to call InitializeComponent in the constructor. So adding this to your MyCustomViewCell made it work:
public MyCustomViewCell()
{
InitializeComponent();
}

New elements added to ObservableCollection from external Page are not persisted

I have two pages. On the first I want to see every elements from ObservableCollection and the second I want to add new element to ObservableCollection. When I give breakpoint I see that the ObservableCollection has new element, but when I back to the first page new element dosen't exist. What should I do?
There is code
public ListViewFlascardViewModel()
{
GoToAddFlashcard = new Command(goToAddFlashcard);
Fiszka = new ObservableCollection<Flashcard>();
Fiszka.Add(new Flashcard {Name = "hello" });
}
public Command GoToAddFlashcard { get; }
Flashcard flashcard = new Flashcard();
async void goToAddFlashcard()
{
await Shell.Current.GoToAsync(nameof(View.NoweFiszki));;
}
public ObservableCollection<Flashcard> Fiszka { get; set; }
}
And there is second page:
class NoweFiszkiViewModel
{
public Command SaveFlashcard { get; }
public NoweFiszkiViewModel()
{
SaveFlashcard = new Command(save);
}
ListViewFlascardViewModel list = new ListViewFlascardViewModel();
private async void save()
{
list.Fiszka.Add(new Flashcard { Name = "Bye" });
await Shell.Current.GoToAsync("..");
}
}
I try a lot of things, but nothing help. I am new in C# and I will be appreciated for every help.
I add whole code.
There is view
ListViewFlashcard
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MVVM.ViewModel"
xmlns:model="clr-namespace:MVVM.Model"
x:Class="MVVM.View.ListViewFlashcard"
x:DataType="viewmodels:ListViewFlascardViewModel"
>
<ContentPage.BindingContext>
<viewmodels:ListViewFlascardViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<Button
Command="{Binding GoToAddFlashcard}"
Text="Click Me"/>
<ListView
ItemsSource="{Binding Fiszka}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Flashcard">
<TextCell Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
NoweFiszki
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MVVM.ViewModel"
x:Class="MVVM.View.NoweFiszki">
<ContentPage.BindingContext>
<viewmodels:NoweFiszkiViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<Entry Text="{Binding Text, Mode=TwoWay}"/>
<Button Text="Save"
Command="{Binding SaveFlashcard}"/>
</StackLayout>
</ContentPage.Content>
And Model
Flashcard
public class Flashcard
{
public string Name { get; set; }
}
And AppShell
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MVVM.AppShell"
xmlns:local="clr-namespace:MVVM.View"
>
<FlyoutItem Title="Add Flashcard" Icon="icon_about.png">
<ShellContent ContentTemplate="{DataTemplate local:ListViewFlashcard}"/>
</FlyoutItem>
public partial class AppShell : Xamarin.Forms.Shell
{
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute(nameof(NoweFiszki), typeof(NoweFiszki));
}
}
It is everything what I wrote.
A couple of things have to be corrected on your code:
In NoweFiszkiViewModel you are creating a new instance of ListViewFlascardViewModel every time, and adding elements on that new instance, which does not affect the instance to which ListViewFlashcard is actually bound.
To fix this, you will have to create a public static ListViewFlascardViewModel in ListViewFlashcard.xaml.cs and set the binding context to it, as follows
ListViewFlashcard.xaml.cs
public static ListViewFlascardViewModel vm { get; set; }
public ListViewFlashcard()
{
InitializeComponent();
vm = new ListViewFlascardViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
BindingContext = vm;
}
then correct ListViewFlashcard.xaml as follows
ListViewFlashcard.xaml
<!--<ContentPage.BindingContext>
<viewmodels:ListViewFlascardViewModel/>
</ContentPage.BindingContext>-->
<StackLayout>
<Button
Command="{Binding GoToAddFlashcard}"
Text="Click Me"/>
<ListView ItemsSource="{Binding Fiszka}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:Flashcard">
<TextCell Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Finally you have to correct NoweFiszkiViewModel.cs as follows, adding the new elements to the public static view model:
NoweFiszkiViewModel.cs
public Command SaveFlashcard { get; }
public NoweFiszkiViewModel()
{
SaveFlashcard = new Command(save);
}
//ListViewFlascardViewModel list = new ListViewFlascardViewModel();
private async void save()
{
ListViewFlashcard.vm.Fiszka.Add(new Flashcard { Name = "Bye" });
await Shell.Current.GoToAsync("..");
}

How to navigate between page with datas

I hope someone could help me understand how it could works..
I have a project in Xamarin Forms
I have a page1 with code below
Page1.xaml
<StackLayout BindableLayout.ItemsSource="{Binding List1}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<AbsoluteLayout>
<Button Text="{Binding NameP} Clicked="Button_Clicked"/>
</AbsoluteLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
Page1.cs
using System.Collections.Generic;
using Xamarin.Forms;
namespace app.Views
{
public partial class Page1 : ContentPage
{
public Page1()
{
InitializeComponent();
ListPrograms1 = new List<Programmes1>();
{
ListPrograms1.Add(new Programmes1() { NameP = "Tomato", Detail = "xxxxx" });
ListPrograms1.Add(new Programmes1() { NameP = "Pepperoni", Detail = "yyyyy" });
}
BindingContext = this;
}
public List<Programmes1> ListPrograms1
{
get; set;
}
public class Programmes1
{
public string NameP { get; set; }
public string Detail { get; set; }
}
public async void Button_Clicked(object sender, System.EventArgs e)
{
await Navigation.PushAsync(new Page2());
}
}
}
and in the second page
Page2.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="app.Views.Page2">
<StackLayout VerticalOptions="Start" HorizontalOptions="Center">
<Label Text={Binding NameP}" />
<Label Text={Binding Detail}" />
</StackLayout>
</ContentPage>
So when I click on the button, I want to go to Page2 and pass datas from the previous page or another Modelpage.
What is the good way when you have lists of datas and want to use them in several pages ?
Do you recommend use a db ?
My problem is similar as a ListView ( when you click on item , you can go to another detailpage) but here I use a bindable layout.
As Jason's reply, you can pass data to another pages using constructors.
For the first page, you can find the current select item in Button_click event.
private async void Button_Clicked(object sender, EventArgs e)
{
Button btn = (Button)sender;
Programmes1 data = ListPrograms1.Find(s => s.NameP == btn.Text);
await Navigation.PushAsync(new Page26(data));
}
Then modify the second page constructors that having one parameter.
public partial class Page26 : ContentPage
{
public Programmes1 Model { get; set; }
public Page26(Programmes1 model)
{
InitializeComponent();
Model = model;
Console.WriteLine(Model.NameP);
this.BindingContext = this;
}
}
<StackLayout HorizontalOptions="Center" VerticalOptions="Start">
<Label Text="{Binding Model.NameP}" />
<Label Text="{Binding Model.Detail}" />
</StackLayout>
Please note: you need to bind ListPrograms1 to StackLayout BindableLayout.ItemsSource, not List1, because I don't find where is List1.

What is the best way to start a function in a ViewMomdel just by clicking on the item in the ListView

I want to add the data from my selected item in the ListView to a few entries immediately when clicking on the item.
My XAML Code:
<?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:local="clr-namespace:WishListProject.ViewModels"
x:Class="WishListProject.Views.UpdateGames">
<ContentPage.BindingContext>
<local:GameListViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<ListView ItemsSource="{Binding Games}" SelectedItem="{Binding SelectedGame}" ItemSelected="{Binding InsertGame }">
</ListView>
<Entry Placeholder="ID" IsVisible="False" Text="{Binding IdEntry}"></Entry>
<Entry Placeholder="GameName" Text="{Binding GameNaamEntry}"></Entry>
<Entry Placeholder="GameGenre" Text="{Binding GameGenreEntry}"></Entry>
<Entry Placeholder="GameRelease" Text="{Binding GameReleaseEntry}"></Entry>
<Button Text="Update Game" Command="{Binding UpdateGameCommand}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
And this is the function I want to start for displaying the selected item data in to a few entries USING THE VIEWMODEL:
public void InsertGame(object sender, SelectedItemChangedEventArgs e)
{
Game game = new Game();
game = SelectedGame;
IdEntry.Text = game.Id.ToString();
GameNaamEntry = game.GameNaam;
GameGenreEntry = game.GameGenre;
GameReleaseEntry = game.GameRelease;
}
What is the best way to start a function in a VIEWMODEL just by clicking on the item in the ListView?
You can call your method at the time SelectedItem is set. Since you are using bindings, I assume you have something like this in your ViewModel:
Game _selectedGame;
public Game SelectedGame
{
get { return _selectedGame; }
set {
SetProperty(ref _selectedGame, value);
// code to add in the setter
if (value != null)
InsertGame();
//----------------------------
}
}
Just make sure the method is called after the property is changed.
Then you will also need to simplify your method signature to:
public void InsertGame(){
...
}
And you should be all set.
Happy coding!
ItemSelected is an event and you can't bind it to a method. I will give you a solution about how to start a function in a ViewMomdel just by clicking on the item in the ListView:
In xaml:
<ListView ItemsSource="{Binding Games}" SelectedItem="{Binding SelectedGame}" ItemSelected="ListView_ItemSelected">
And in code behind, get the current ViewModel in the ListView_ItemSelected method and then call InsertGame of ViewModel, you can pass the current selectedGame also:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
Game selectedGame = e.SelectedItem as Game;
var currentVm = this.BindingContext as GameListViewModel;
currentVm.InsertGame(selectedGame);
}
}
And your ViewModel:
public class GameListViewModel : INotifyPropertyChanged
{
public void InsertGame(Game currentGame)
{
GameNaamEntry = currentGame.GameNaam;
}
...
}

Xamarin remove item from list ObservableCollection and listview form

I have a simple app on Xamarin (a to-do list) which purpose is to dynamically create and remove items from a list. I am using ObservableCollection for the list. I spent a ton of time researching about this but I could not get it working.
Right now my app can add items to the list and display it in the main form. Now I want it to delete the corresponding items from the list with a click of a button.
Here is my code:
MainPage.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:local="clr-namespace:App3"
x:Class="App3.MainPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Clicked="addnewitem"/>
</ContentPage.ToolbarItems>
<ContentPage.BindingContext>
<local:viewmod/>
</ContentPage.BindingContext>
<StackLayout>
<Editor x:Name="txtboxNAME"></Editor>
<ListView ItemsSource="{Binding Tasks}" HasUnevenRows="True" x:Name="itemListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame>
<StackLayout>
<Editor Text="{Binding Taskname}"/>
<Switch/>
<Button Text="Delete" CommandParameter="{Binding ItemName}" Clicked="DeleteClicked">
</Button>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
MainPage.xaml.cs (The code behind the MainPage form)
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace App3
{
public partial class MainPage : ContentPage
{
static int itemid = 0;
public MainPage()
{
InitializeComponent();
BindingContext = new viewmod();
}
private void addnewitem(object sender, EventArgs e)
{
var vm = BindingContext as viewmod;
string itemnameval = "item_" + itemid.ToString();
vm.AddItems(this.txtboxNAME.Text, itemnameval);
itemid++;
}
private void DeleteClicked(object sender, EventArgs e) // Item should be deleted from the list
{
// This does not work
var itemsender = (Xamarin.Forms.Button)sender;
var item = itemsender?.BindingContext as Task;
var vm = BindingContext as viewmod;
vm?.RemoveCommand.Execute(item);
//vm.Tasks.Remove(item); // conversion error
// This does not work either. "allItems" is not defined.
TaskClass listitem = (from itm in allItems
where itm.ItemName == item.CommandParameter.ToString()
select itm).FirstOrDefault<TaskClass>();
allItems.Remove(listitem);
}
}
}
TaskClass.cs
namespace App3
{
class TaskClass
{
public string Taskname { get; set; }
public string ItemName { get; set; }
}
}
viewmod.cs
using System.Collections.ObjectModel;
using Xamarin.Forms;
namespace App3
{
class viewmod
{
public ObservableCollection<TaskClass> Tasks { get; set; } = new ObservableCollection<TaskClass>();
public viewmod()
{
}
public void AddItems(string taskn, string taskid)
{
Tasks.Add(new TaskClass { Taskname = $"{taskn}", ItemName=$"{taskid}" });
}
public void DelItem(TaskClass task)
{
Tasks.Remove(task);
}
public Command<TaskClass> RemoveCommand
{
get
{
return new Command<TaskClass>((task) =>
{
Tasks.Remove(task);
});
}
}
}
}
modify your XAML first = the "." syntax passes the entire bound object
<Button Text="Delete" CommandParameter="{Binding .}" Clicked="DeleteClicked" />
then in your code behind
private void DeleteClicked(object sender, EventArgs e)
{
var itemsender = (Xamarin.Forms.Button)sender;
var item = (TaskClass)itemsender?.CommandParameter;
// it would be much cleaner to keep a ref to your VM in your page
// rather than continually casting it from BindingContext
var vm = BindingContext as viewmod;
vm.Tasks.Remove(item);
}

Categories

Resources