ListView binding to ObservableCollection does not update GUI

13»

Posts

  • venkat.0969venkat.0969 USMember

    Hello All... Xamarin forms document clearly mentions to use "ObservableCollection" instead of list to update any newly added items. please go through the link pasted below.

    ex: ObservableCollection employeeList = new ObservableCollection();
    listView.ItemsSource = employeeList;

    //Mr. Mono will be added to the ListView because it uses an ObservableCollection
    employeeList.Add(new Employee(){ DisplayName="Mr. Mono"});

    Ref: https://developer.xamarin.com/guides/xamarin-forms/user-interface/listview/data-and-databinding/

  • SergioFariasSergioFarias USMember ✭✭

    This works fine.

    @mkvonarx said:
    Hi

    Can anyone tell me why this simple code is not working as expected? Does the ListView control not listen for changes in the bound property?

    • Expected: when I click the Add button, the GUI (ListView) updates through the binding and shows the new element in the ObservableCollection.
    • Observed: GUI does not update but always shows the initial three items of the ObservableCollection.
    • Btw: OnAddButtonClicked() does get called and ListViewItems does get updated (checked in the debugger). Calling OnPropertyChanged("ListViewItems") inside OnAddButtonClicked() did not help either.
    • And: if I update the code as follows, the ListView works: add a Label control, bind that label to a 2nd property, update that property inside OnAddButtonClicked(), call OnPropertyChanged() from inside the property setter. So obviously this call to OnPropertyChanged() somehow triggers the ListView to get updated as well...
    • I'm now using Xamarin.Forms version 1.1.0.6201. Didn't work with the previous version either.

    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="Test1.MainPage"
                 Padding="10" Title="MainPageXaml">
        <StackLayout VerticalOptions="StartAndExpand" HorizontalOptions="Fill">
            <Button Text="Add" Clicked="OnAddButtonClicked" />
            <ListView ItemsSource="{Binding ListViewItems}" />
        </StackLayout>
    </ContentPage>
    

    C#:

    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            ListViewItems = new ObservableCollection<string> { "one", "two", "three" };
            BindingContext = this;
        }
    
        private void OnAddButtonClicked(object sender, EventArgs e)
        {
            ListViewItems.Add(DateTime.Now.ToString());
            // OnPropertyChanged("ListViewItems");
        }
    
        public ObservableCollection<string> ListViewItems { get; set; }
    }
    

    Markus

  • SergioFariasSergioFarias USMember ✭✭

    Works fine.

  • harikristaharikrista USMember

    If you add { get; set; } in the property of the class that you are trying to render then it works.

  • Arun_Robinson_AArun_Robinson_A USMember ✭✭

    Issue ****Solved
    By Implementing **INotifyPropertyChanged **for Model Class like below

    ObservableCollection<MyModel> listviewItemSource=new ObservableCollection<MyModel>();

    public class **MyModel **: **INotifyPropertyChanged **
    {
    private string name ;
    public string Name
    {
    get
    {
    return name;
    }
    set
    {
    this.name = value;
    OnPropertyChanged("Name");
    }
    }
    .
    .
    .
    .
    .
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName]string propertyName = null)
    {
    if (PropertyChanged != null)
    {
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    }

    }

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    I suggest you to take a look to PropertyChanged.Fody

  • A possible fix might be for you to reset the item source.

    https://forums.xamarin.com/discussion/18868/tableview-reloaddata-equivalent-for-listview

    I am using ReactiveLists but the fix worked for me so I guess it's another angle for you to try.

  • I could resolve the issue by setting the ListViewCachingStrategy of the ListView to ListViewCachingStrategy.RetainElement instead of ListViewCachingStrategy.RecycleElement.

  • VishalShahiVishalShahi INMember

    I ended up with the same bug , List's ItemSource gets updated but does not reflect in UI on iOS, until I change the Orientation of the device. It worked perfectly fine on Andorid. Any workaround to make it work in iOS ?
    Thanks

  • NickWhitehurstNickWhitehurst GBMember ✭✭

    @RobCrabtree Thanks, as suggested, wrapping in a grid with a RowDefinition of "*" fixed the problem.

    Another weird bug - fixed in a weird way.

    Fix is from September 2014! It's 2017 now... :/

  • ArunkumaarCNArunkumaarCN INMember ✭✭

    hi
    i have listview accordion in a page and its expands but can't able to collapse when i again tap the list item. i have a stacklayout (accordion) which is inside the listview and its controlled by IsVisible control on stackLayout. my question is it get expand but cant able to collapse?

  • RogerEdwardSurmayRogerEdwardSurmay USMember ✭✭
    edited May 2017

    @M2M said:
    Hi @mkvonarx‌ ,
    How did you resolve this problem?
    I get the same problem. After reloading the observationcollection datasource, nothing change but if I rotate the device the listview update and show the items correctly

    In this case, I have the follow solution:

    xaml

    <ListView.ItemTemplate>



    </ListView.ItemTemplate>

    code behind:

    A. listview.IsRefreshing = true;
    B. update your observableCollection.
    C. listview.IsRefreshing = false;

    I Hope it helps

  • RogerEdwardSurmayRogerEdwardSurmay USMember ✭✭
    edited May 2017

    stop thinking its a bug. It's not a bug. Is the way how you update your listview.

    listview.isRefreshing = true;

    //update your observableCollection. do not forget INotify etc..

    //even you could do some tricks like delay(1000) etc

    listview.isRefreshing = false;

  • SimonGilesSimonGiles CAMember ✭✭
    edited June 2017

    I had this problem too and discovered that by switching the declaration of the ObservableCollection in the view model from a field to a property the problem went away.

    i.e. bad:

    public class MasterViewModel : INotifyPropertyChanged
    {
       public ObservableCollection<DetailViewModel> ListItemData = new ObservableCollection<DetailViewModel>();
    }
    

    vs. good:

    public class MasterViewModel : INotifyPropertyChanged
    {
       private ObservableCollection<DetailViewModel> _listItemData = new ObservableCollection<DetailViewModel>();
       public ObservableCollection<DetailViewModel> ListItemData 
       {
          get { return _listItemData; }
          set { _listItemData = value; }
       }
    }
    

    note: omitted INotifyPropertyChanged handling for brevity.

    For those of you who declare ListItemData in your controller class instead of a VM just change the declaration so it's a property instead of a field in your controller.

  • ThomasMielkeThomasMielke DEMember ✭✭
    edited June 2017

    Good one!

    Comes on my Dependency Object Checklist:

    // 1. Check, if you haven't forgotten to derive your data model from INotifyPropertyChanged:
    
    class MyItem : INotifyPropertyChanged
    {
          private String _text;
          public String Text
          {
              get => _text;
              set => SetProperty(ref _text, value);
          }
    }
    
    // 2. Check, if you haven't forgotten to derive your ViewModel from INotifyPropertyChanged
    //    and declare your ObservableCollection itself as a dependency property:
    
    public class MyViewModel : INotifyPropertyChanged
    {
       private ObservableCollection<MyItem> _listItemData = new ObservableCollection<MyItem>();
       public ObservableCollection<MyItem> ListItemData 
       {
          get => _listItemData;
          set => SetProperty(ref _listItemData, value);
       }
    }
    

    note: omitted SetProperty and subsequent OnPropertyChanged implementaions for brevity -- see here for details: https://www.danrigby.com/2015/09/12/inotifypropertychanged-the-net-4-6-way (consequently, you should provide an abstract class -- in the link it's BindableBase -- instead of using INotifyPropertyChanged directly, so you won't need to implement SetProperty and OnPropertyChanged in each class.)

  • The real problem is not the ObservableCollection, INotifyPropertyChanged, Etc. The Real problem is the property ITEMSSOURCE's ListView, That not refresh the List, i do:

    DataContext.TextProperty = "Two"; //Change from One to Two the Text's Label
    DataContext.Collection_Books = DataContext.Collection_Books; //Not Refresh List
    DataContext.OnPropertyChanged("Collection_Books"); //Same, Not Refresh List

    But if we Bind the Collection_Books to a Text's Label and a converter the value change, example: Count

    I did:

    Listview_Books.RemoveBinding(ListView.ItemsSourceProperty);

    And:

    Listview_Books.SetBinding(ListView.ItemsSourceProperty, new Binding("Collection_Books", BindingMode.Default, null, null, null, DataContext));

    And don't do nothing. but if we do:

    Listview_Books.Itemssource = DataContext.Collection_Books;

    neither works, we need really to do:

    Listview_Books.Itemssource = null;

    Listview_Books.Itemssource = DataContext.Collection_Books;

    Sorry my shit english, i speak spanish and i did without translate ;).

  • ThomasMielkeThomasMielke DEMember ✭✭

    This is really not the way it's meant to be. I've got another one for my Dependency Object Checklist -- maybe this will help you:

    // 3. Check, if you are on the user interface thread:
    
    public class MyViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<MyItem> _listItemData = new ObservableCollection<MyItem>();
        public ObservableCollection<MyItem> ListItemData 
        {
           get => _listItemData;
           set => SetProperty(ref _listItemData, value);
        }
    
        public MyViewModel()
        {
            ListItemData.CollectionChanged += OnCollectionChanged;
        }
    
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            var handler = CollectionChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }
    
        public void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
             if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Remove)
            {
                Xamarin.Forms.Device.BeginInvokeOnMainThread(() => {
                    RaiseCollectionChanged(e);
                });
            }
        }
    }
    
    or maybe just:
    
    Xamarin.Forms.Device.BeginInvokeOnMainThread(() => {
        (MyViewModel)vm.ListItemData.Add(new MyItem());
    }
    
  • Damien_DoumerDamien_Doumer Member ✭✭

    Hi, had such issue and the solution was to add items to the observable collection one at a time, when you just create a new observable collection for the property you bound you will face that issue, just do as I say and everything will be fine.

  • IStepIStep USMember ✭✭

    This still has bugs. My model implements INotifyPropertyChanged and it does not refresh view. I also implemented list with public get and set and it does not refresh view. I tried it with private member also and it does not refresh view.

    This is not optimized but always works for me. Calling method in code-behind from view model and setting ItemSource over x:Name element. This works.

  • NijnhoofdNijnhoofd Member
    edited December 2018

    Hello everyone,

    Unfortunately I've got the same problem. The loading of my ObservableCollection isn't ready on time but it somehow doesn't notify the view when it's ready. I've tried all the solutions above but none of them seems to work. Can somebody help me out?

    This is the model Round, it inherits from a BaseModel : INotifyPropertyChanged.

    public class Round : BaseModel
    {
    public string Id { get; set; }
    public Answer _SelectedAnswer { get; set; }

        private ImageSource _RealPicture { get; set; }
        public ImageSource RealPicture
        {
            get { return _RealPicture; }
            set { _RealPicture = value; RaisePropertyChanged(nameof(RealPicture)); }
        }
    
        private List<Answer> _Answers { get; set; }
        public List<Answer> Answers
        {
            get { return _Answers; }
            set { _Answers = value; RaisePropertyChanged(nameof(RealPicture)); }
        }
    
        private Answer _RightAnswer { get; set; }
        public Answer RightAnswer
        {
            get { return _RightAnswer; }
            set { _RightAnswer = value; RaisePropertyChanged(nameof(RightAnswer)); }
        }
    

    This is the viewmodel GameDetailViewModel that inherits from BaseViewModel : INotifyPropertyChanged.

    public class GameDetailViewModel : BaseViewModel
    {
    public int hour = 0, min = 0, sec = 0, lap = 13;
    public Timer timer;
    public Game Game { get; set; }
    public Round Round { get; set; }
    public Command LoadRoundsCommand { get; set; }
    public Command StartPlayTime { get; set; }

        private ObservableCollection<Round> rounds = new ObservableCollection<Round>();
        public ObservableCollection<Round> Rounds
        { get { return rounds; }
          set { rounds = value; OnPropertyChanged(nameof(Rounds)); }
        }
    
        public GameDetailViewModel(Game game = null)
        {
            Title = game?.Text;
            Game = game;
            Rounds = new ObservableCollection<Round>();
            Rounds.CollectionChanged += OnCollectionChanged;
            LoadRoundsCommand = new Command(async () => await ExecuteLoadRoundsCommand());
            StartPlayTime = new Command(StartStopwatch);
    
        }
    
        public void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Remove)
            {
                Device.BeginInvokeOnMainThread(() => {
                    RaiseCollectionChanged(e);
                });
            }
        }
    
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            CollectionChanged?.Invoke(this, e);
        }
    
        async Task ExecuteLoadRoundsCommand()
        {
            if (IsBusy)
                return;
    
            IsBusy = true;
    
            try
            {
                Rounds.Clear();
    
                var rounds = await RoundDataStore.GetItemsAsync(true); 
                foreach (var round in rounds)
                {
                    Rounds.Add(round);
                }
                Round = GetRound(); 
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            finally
            {
                IsBusy = false;
            }
    

    This is the contentpage, the C# part.

    public partial class Game1DetailPage : ContentPage
    {
        public GameDetailViewModel viewModel;
    
        public Game1DetailPage(GameDetailViewModel viewModel)
        {
            InitializeComponent();
    
            BindingContext = this.viewModel = viewModel;
            InitializeGame();
        }
    
        protected override void OnAppearing()
        {
            base.OnAppearing();
            viewModel.LoadRoundsCommand.Execute(null);
        }
    

    This is the contengpage with the XAML part.

            <Frame OutlineColor="#e91e63"
                   HasShadow="True"
                   HorizontalOptions="Center" 
                   Padding="0"
                   Margin="0">
                <Image Source="{Binding Round.RealPicture}"/>
            </Frame>
            <Picker x:Name="picker" 
                Title="Select an answer"
                ItemsSource="{Binding Round.Answers}"
                SelectedItem="{Binding SelectedAnswer}"
                SelectedIndexChanged="OnPickerSelectedIndexChanged" >
            </Picker>
            <Button x:Name="buttonSubmitAnswer"
                Margin="0,10,0,0" 
                HorizontalOptions="Center"
                VerticalOptions="End"
                BindingContext="{x:Reference picker}"
                Text="Submit answer:"
                Command="{Binding CheckAnswerCommand}"
                Clicked="OnButtonSubmitAnswerClicked"
                BackgroundColor="#e91e63" 
                TextColor="White" />
        </StackLayout>
    </ContentView>
    

    Can somebody tell me what I'm doing wrong?

  • In the end I solved it!

    I changed

    public Round Round { get; set; }

    to

    public Round round;
    public Round Round
    { get { return round; }
    set
    {
    round = value; OnPropertyChanged(nameof(Round));
    }
    }

    The inheritance of BaseModel and the following code are apparently not necessary in my case:

    //Rounds.CollectionChanged += OnCollectionChanged;

        //public void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        //{
        //    if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Remove)
        //    {
        //        Device.BeginInvokeOnMainThread(() => {
        //            RaiseCollectionChanged(e);
        //        });
        //    }
        //}
    
        //public event NotifyCollectionChangedEventHandler CollectionChanged;
        //void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
        //{
        //    CollectionChanged?.Invoke(this, e);
        //}
    
  • M.MehdiDehghaniM.MehdiDehghani USMember ✭✭
    edited February 10

    Hi everyboy.
    PLEAE HELP MMMMMMMMMMMMEEEEEE :'(
    I have a ListView inside of a CarouselView.
    I fired CarouselView_ItemSelected and Exceute a command for getting data for listView.
    But The ListView just update in UI and showing data for the first time (when carsoulView has been loaded), and when i swipe, updated data doesn't show in the ListView and it's empty (:

  • ThomasMielkeThomasMielke DEMember ✭✭
    1. Check, if you have forgotten to derive your data model from INotifyPropertyChanged.
    2. Check, if you have forgotten to derive your ViewModel from INotifyPropertyChanged and declare your ObservableCollection itself as a dependency property.
    3. Check, if you are on the user interface thread.
  • M.MehdiDehghaniM.MehdiDehghani USMember ✭✭

    Dear ThomasMielke
    Thanks a lot for your response. I checked it according to your description and i think it was OK!
    I'm new in Xamarin.Form, But I saw a lot of training about that and created it according to them.
    Could you please check my attached code and tell me which part is wrong? 🙈🙈 Thank you so much 🙏🏻🙏🏻

  • ThomasMielkeThomasMielke DEMember ✭✭

    This smells like case 3:

    Try to encapsulate the code where you renew and fill UserLessonWords in function GetUserLessonWordsAsync() like so:

            if (myTask.Result != null)
            {
                Xamarin.Forms.Device.BeginInvokeOnMainThread(() => {
                    UserLessonWords = new ObservableCollection<LessonWordModel>();
                    foreach (var lessonWord in myTask.Result)
                        UserLessonWords.Add(lessonWord);
                }
            }
    
  • M.MehdiDehghaniM.MehdiDehghani USMember ✭✭

    Dear ThomasMielke
    Thank you so much for your response. 🙏🏻🙏🏻🙏🏻🙏🏻😘
    I changed it according to your code. but it didn't work :(

13»
Sign In or Register to comment.