CollectionView binding data doesn't update after OnPropertyChanged is triggered - c#

I have a CollectionView with a binding to ObservableCollection<Announce> Announces that is filled correctly with data when the page appears. Then I send a Post request creating a new Announce, the OnPropertyChanged method is called, data in Announces aswell as in announces is updated but the getter isn't called and data in the CollectionView isn't updated. Should the getter be called automatically after OnPropertyChanged? What am I missing in my code that doesn't update the UI?
The AnnouncesService is a simple service that returns ObservableCollections of Announce from http requests.
Here's my AnnouncesViewModel:
public class AnnouncesViewModel : INotifyPropertyChanged
{
ObservableCollection<Announce> announces;
public ObservableCollection<Announce> Announces
{
get => announces;
set { announces = value; OnPropertyChanged("Announces"); }
}
public AnnouncesService service;
public event PropertyChangedEventHandler PropertyChanged;
public AnnouncesViewModel()
{
service = new AnnouncesService();
Announces = new ObservableCollection<Announce>();
getAnnounces();
}
public async Task addAnnounce(Announce announce)
{
Announces = await service.PostAnnounceAsync(announce); //same as using getAnnounces again
}
public async void getAnnounces()
{
Announces = await service.GetAnnouncesAsync();
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Here's my XAML with binding
<ContentPage Title="">
<ContentPage.BindingContext>
<vm:AnnouncesViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<CollectionView ItemsSource="{Binding Announces}"
SelectionMode="Single"
SelectionChanged="CollectionView_SelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding location}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage.Content>
</ContentPage>
and a TabbedPage that calls AddAnnounce from the AnnouncesViewModel
public partial class TabbedPage1 : TabbedPage
{
private AnnouncesService service;
private AnnouncesViewModel viewModel;
public TabbedPage1()
{
InitializeComponent();
service = new AnnouncesService();
viewModel = new AnnouncesViewModel();
}
private async void AddAnnounce(object sender, EventArgs e)
{
Announce announce = new Announce(localtion_entry.Text,sport_entry.Text,1);
await viewModel.addAnnounce(announce);
}
}

So the issue was I was using 2 different instances of AnnouncesViewModel. One in XAML and one in cs code to call AddAnnounce.
To fix this I removed this part from my XAML code
<ContentPage.BindingContext>
<vm:AnnouncesViewModel/>
</ContentPage.BindingContext>
added x:Name = "page" to my ContentPage and assigned the BindingContext in TabbedPage1 constructor:
private AnnouncesViewModel viewModel;
public TabbedPage1()
{
InitializeComponent();
viewModel = new AnnouncesViewModel();
page.BindingContext = viewModel;
}
I also use Add method in AnnouncesViewModel to add content to my Announces ObservableCollection.

Related

RaisePropertyChanged doesn't update UI

RaisePropertyChanged doesn't update the listbox in the UI after it is called.
Initially when the application is opened the update happens and the list is populated. After I call the constructor of the ViewModel (TracksVM) which triggers the method that updates the ObservableCollection the setter populates the collection but the RaisePropertyChanged seems to not have effect on the box.
Solved:
As suggested by Clemens I had 2 instances of the ViewModel class hence the listbox was not able to take the newly created list of Tracks as the new list was being past to the second instance. Once I ran the updating code on the same object instance the Listbox started to update accordingly.
XAML:
<ListBox Name="DownloadsBox" ItemsSource="{Binding Tracks}" SelectedItem="{Binding SelectedTrack}" Margin="10,10,10,10" DockPanel.Dock="Top" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" SelectionChanged="TracksListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FileName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel:
public class TracksVM:INotifyPropertyChanged
{
private FileManager fm;
private ObservableCollection<Track> _tracks;
public ObservableCollection<Track> Tracks
{
get => _tracks;
set
{
_tracks = value; RaisePropertyChanged("Tracks");
}
}
private ObservableCollection<string> _genres;
public ObservableCollection<string> Genres
{
get => _genres;
set { _genres = value; RaisePropertyChanged("Genres"); }
}
private Track _selectedTrack;
public Track SelectedTrack
{
get => _selectedTrack;
set
{
_selectedTrack = value; RaisePropertyChanged("SelectedTrack");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public TracksVM()
{
fm = new FileManager();
LoadData();
}
void LoadData()
{
Console.WriteLine("Loading Tracks in Download box");
Tracks = fm.ListOfTracks(ConfigurationManager.DownloadsDirectory).ToObservableCollection();
Genres = ConfigurationManager.Genres.ToObservableCollection();
}
}
The triggering of the ViewModel and respectively the update of the ObservableCollection:
TracksVM tVM;
internal void MoveTrack(string fileTobeMoved,string folderLocation)
{
Console.WriteLine($"Track: {fileTobeMoved}, location: {folderLocation}");
try
{
File.Move(fileTobeMoved, folderLocation);
tVM = new TracksVM();
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
}
As suggested by Clemens I had 2 instances of the ViewModel class hence the listbox was not able to take the newly created list of Tracks as the new list was being past to the second instance. Once I ran the updating code on the same object instance the Listbox started to update accordingly.

Xamarin forms how to display data on a listView(REST API)

I'm new in xamarin cross platform development in visual studio.
I want to display a list of items on the listView.
<?xml version="1.0" encoding="utf-8" ?>
x:Class="Envelope_Internal.MainAssign">
<ContentPage.BindingContext>
</ContentPage.BindingContext>
<ListView ItemsSource="{ Binding assignmentList }"
HasUnevenRows="True">
</ListView>
namespace Envelope_Internal
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainAssign : ContentPage
{
DataService dataService;
List<AssignListV> assignmentList;
public MainAssign()
{
InitializeComponent();
dataService = new DataService();
RefreshData();
}
async void RefreshData()
{
assignmentList = await dataService.GetAssignmentItemsAsync();
assignmentList.item;
}
}
}
namespace Envelope_Internal
{
public class DataService
{
HttpClient client = new HttpClient();
public DataService()
{
}
public async Task<List<AssignListV>> GetAssignmentItemsAsync()
{
try
{
var response = await client.GetStringAsync("https://munipoiapp.herokuapp.com/api/applications/New");
var assignmentItems = JsonConvert.DeserializeObject<List<AssignListV>>(response);
return assignmentItems;
}
catch (System.Exception exception)
{
return null;
}
}
}
}
Refer this link for showing data in a listview :
https://bsubramanyamraju.blogspot.in/2017/04/xamarinforms-consuming-rest-webserivce_17.html
You missing DataTemplate part in list and wrong using Binding.
From use Binding you can assing exemplar of ViewModel with BindingContext
<ListView
HasUnevenRows="True"
ItemsSource="{Binding ItemsSource}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Name}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public partial class MainAssign : ContentPage
{
public MainAssign()
{
InitializeComponent();
BindingContext = new MainAssignViewModel();
}
protected override async void OnAppearing() {
base.OnAppearing();
await (BindingContext as MainAssignViewModel)?.LoadData();
}
}
public class MainAssignViewModel:INotifyPropertyChanged {
List<AssignListModel> _assignmentList;
public MainAssignViewModel() {
_dataService = new DataService();
}
public List<AssignListModel> AssignmentList {
get { return _assignmentList; }
set {
_assignmentList = value;
OnPropertyChanged(nameof(AssignmentList));
}
}
readonly DataService _dataService;
public async Task LoadData() {
AssignmentList = await _dataService.GetAssignmentItemsAsync();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Switching through different Pages in MVVM

I am trying to implement the ability to switch through different Pages in my Xamarin MVVM project. I have three folders - "Models", "Views" and "ViewModels". "Views" contains "MainView" which's role is to display other Views.
MainView.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="KeepFit.Views.MainView">
<ContentPresenter x:Name="ContentArea" Content="{Binding CurrentView}"/>
</ContentPage>
MainView.xaml.cs:
public partial class MainView : ContentPage
{
public MainView()
{
InitializeComponent();
BindingContext = new MainViewModel();
}
}
As you can see, "MainView" is binded to "MainViewModel" class.
MainViewModel.cs:
public class MainViewModel : INotifyPropertyChanged
{
public Command SwitchViewsCommand { get; private set; }
public MainViewModel()
{
SwitchViewsCommand = new Command((parameter) =>
CurrentView = (ContentPage)Activator.CreateInstance(parameter as Type));
CurrentView = new HomeView();
}
private ContentPage _currentView;
public ContentPage CurrentView
{
get
{
return _currentView;
}
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]
string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The CurrentView is set to my HomeView (let us say it is default ContentPage) in the constructor. It is binded to the ContentPresenter, so it should be visible at the beginning of my application's runtime. But it is not.
I have noticed that the ContentPresenter is expecting the Xamarin.Forms.View object as the "Content" - and my Views are inheriting from Xamarin.Forms.ContentPage. But if I change them to ContentViews for example - I will not be able to set the BindingContext for them. Could anybody explain what am I doing wrong?
You are try this?
async void butonClick(object sender, EventArgs e)
{
await Navigation.PushAsync(new EntryPageCode());
}
Your view instance in location of "EntryPageCode", ok.
Attempt this and tell me about, if work's be fine to you.

ListView ItemsSource binding not displaying items

I am trying to create a bound ListView in Xamarin. Here's the C# code:
public partial class LearnPage : ContentPage
{
public class TodoItem
{
public string DisplayName { get; set; }
}
//ObservableCollection<TodoItem> Items = new ObservableCollection<TodoItem>();
private IEnumerable<TodoItem> _items;
public IEnumerable<TodoItem> Items
{
get { return _items; }
set
{
if (Equals(_items, value))
return;
_items = value;
OnPropertyChanged();
}
}
public LearnPage ()
{
InitializeComponent();
BindingContext = this;
Items = new TodoItem[]{
new TodoItem{ DisplayName = "Milk cartons are recyclable" }
};
//Items.Add(new TodoItem { DisplayName = "Milk cartons are recyclable" });
}
}
You can also see some commented out code with an ObervableCollection, which I have also tried with.
And here's the 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="learn.LearnPage">
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand" Padding="0,10,0,10">
<ListView ItemsSource="{Binding Items}" RowHeight="40" x:Name="sarasas">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding DisplayName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
When I build the app, an empty list is displayed. I'm really new to Xamarin and I think I'm just missing something obvious. Any help appreciated!
I am not sure if ContentPage uses [CallerMemberName] for the OnPropertyChanged() method. So first thing I would try is to write OnPropertyChanged("Items") instead.
Either way, if I were you I would separate concerns and move the Items into a ViewModel class, which implements INotifyPropertyChanged itself. This will help later on if you want to test, add more code such as commands, inject dependencies etc., where you will keep ViewModel code separate from View code.
So you could start with:
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged ([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs (propertyName));
}
}
Then create your ViewModel:
public class LearnViewModel : BaseViewModel
{
public ObservableCollection<TodoItem> Items { get; } = new ObservableCollection<TodoItem>();
private ICommand _addTodoItem;
public ICommand AddTodoItem =>
_addTodoItem = _addTodoItem ?? new Command(DoAddTodoItem);
private int _count;
private void DoAddTodoItem()
{
var item = new TodoItem { DisplayName = $"Item {++_count}" };
// NotifyCollectionChanged must happen on UI thread.
Device.BeginInvokeOnMainThread (() => {
Items.Add(item);
});
}
}
Then you can keep your View code thin like:
public partial class LearnPage : ContentPage
{
public LearnPage ()
{
InitializeComponent();
BindingContext = new LearnViewModel();
}
}
Normally you would invoke a command to populate the Items in your ViewModel, this could be by fetching data from the Internet, or loading local data from a database etc.
In this sample you can just add a constructor to the LearnViewModel and call DoAddTodoItem a couple of times:
public class LearnViewModel : BaseViewModel
{
public LearnViewModel()
{
DoAddTodoItem();
DoAddTodoItem();
}
...
This should show you something like this when you launch the app:

Bind items to a ListView

I am trying to create an infinite scroll in xamarin.forms. I think that the problem is, that my items are not binded to my ListView. Do I have to bind my datatemplate to listview. My datatemplate contains imagecell with text,detail and ImageSource.
While I was debugging, my listview.ItemAppearing += (sender, e) => was never called. So I am assuming that here is my problem.
I am using Http client with json response. Bellow is my code:
public partial class MainPage : ContentPage
{
readonly IList<Article> books = new ObservableCollection<Article>();
readonly BookManager manager = new BookManager();
bool isLoading;
public MainPage()
{
books = new ObservableCollection<Article>();
var listview = new ListView();
listview.ItemsSource = books;
listview.ItemAppearing += (sender, e) =>
{
if (isLoading || books.Count == 0)
return;
//hit bottom!
if (e.Item == books[books.Count - 1])
{
LoadItems();
}
};
LoadItems();
BindingContext = books;
InitializeComponent();
}
my LoadItems method:
public async void LoadItems()
{
//simulator delayed load
var bookCollection = await manager.GetAll();
foreach (Article book in bookCollection.articles)
{
books.Add(book);
}
}
and my xamlpage
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="BookClient.MainPage"
Title="Library Books">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add New" Icon="ic_action_new.png" Clicked="OnAddNewBook" />
<ToolbarItem Text="Refresh" Icon="ic_autorenew.png" Clicked="OnRefresh" />
</ContentPage.ToolbarItems>
<ListView
ItemsSource="{Binding}"
ItemTapped="OnEditBook">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell
Text="{Binding id, StringFormat='ID= {0}'}" Detail="{Binding content}" ImageSource="{Binding images.image_intro}">
<ImageCell.ContextActions>
<MenuItem Clicked="OnDeleteBook"
CommandParameter="{Binding}"
Text="Delete" IsDestructive="True" />
</ImageCell.ContextActions>
</ImageCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
Looks like you have a couple problems, though I have not messed with infinite scrolling before. To answer your question, you do not need to bind your DataTemplate, it looks good exactly how you have it.
In your XAML you specify ItemsSource="{Binding}" but then in your code-behind you set listview.ItemsSource = books; which cancels out your original binding. I would suggest commenting out that line in your code-behind and leave in the XAML line.
You are not awaiting LoadItems();. You should move LoadItems(); into OnAppearing() so that it can be awaited.
Move the books property into your ViewModel and then have you ViewModel inherit from INotifyPropertyChanged and set your ViewModel as the BindingContext. This would then make your ListView.ItemSource change to ItemsSource="{Binding Books}". So your ViewModel will become
public class BooksViewModel : INotifyPropertyChanged {
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Article> _books;
public ObservableCollection<Article> Books {
get { return _books ?? (_books = new ObservableCollection<Article>()); }
set {
if(_books != value) {
_books = value;
OnPropertyChanged();
}
}
}
public async void LoadItems()
{
//simulator delayed load
var bookCollection = await manager.GetAll();
foreach (Article book in bookCollection.articles)
{
books.Add(book);
}
}
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) {
System.ComponentModel.PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null) { handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); }
}
}
Doing the above allows Books to notify the UI that it has changed so the UI will update. You also should be specifying Books as ObservableCollection<> instead of IList<>
A suggestion, I would attach a method to listview.ItemAppearing in ContentPage.OnAppearing() and then remove the event handler in ContentPage.OnDisappearing(). This will prevent memory leaks and should be done for any event handling that it makes sense to do it to. This will require you to put your ItemAppearing lambda code into its own method
Let me know if it is still not working for you after this.
Edit: Forgot about the ViewModel's PropertyChanged event. See ViewModel code again, at the top.

Categories

Resources