How do you do proper binding and updating of Xamarin.Forms ListView?

19,041

Solution 1

i think your model needs to implement INotifyPropertyChanged. So the UI knows that a value in model has changed

something like this :

public class Model : INotifyPropertyChanged
{
    // boiler-plate
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    protected bool SetField<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    // props
    private string _count;
    public string Count 
    {
        get { return _count; }
        set { SetField(ref _count, value, "Count"); }
    }
}

Solution 2

Remove the "set" method from your ViewModelList property. Whenever you use an ObservableCollection, just change the items in that one collection. Never replace it with another ObservableCollection.

Share:
19,041
Ken K
Author by

Ken K

Updated on July 02, 2022

Comments

  • Ken K
    Ken K almost 2 years

    Using the MVVM pattern, I have a Model, ViewModel and View, which contains a ListView. The ListView is bound to a member of the ViewModel that is an ObservableCollection of the Model class. I can get the binding for the initial display to work and can update properties on the Model class for the appropriate row upon acting on the view, but I cannot get the view to refresh, pulling data from the Model class in the ObservableCollection. The ListView class does not contain method to invalidate or force a refresh, which would address my issue. How do I get the ListView to refresh after updating data on the Model?

    Here is a simple example of what I am trying to do: Each row contains a Button and Label. Upon clicking a button, I can update the label, which will be reflected on screen. What I need to do is update the Model, which in turn should force an update of the view. However, I cannot get this to work. In an actual application, updating of the Model will be in a business logic layer, not in the view, after which I need to force refresh of the ListView.

    Example Code:

    using System;
    using System.Collections.ObjectModel;
    using Xamarin.Forms;
    
    namespace ListViewTest
    {   
        public class Model
        {
            public static int ids = 0;
            public Model(string count) 
            {
                Id = +1;
                Count = count;
            }
            public int Id { get; set; }
            public string Count { get; set; }
        }
    
        public class ModelList : ObservableCollection<Model>
        {
        }
    
        public class ViewModel
        {
            ModelList list = new ModelList();
            public ModelList ViewModelList
            {
                get { return list; }
                set 
                { 
                    list = value; 
                }
            }
        }
    
        public partial class MainPage : ContentPage
        {   
            public ViewModel viewModel;
    
            public class DataCell : ViewCell
            {
                public DataCell()
                {
                    var Btn = new Button();
                    Btn.Text = "Click";
                    var Data = new Label();
                    Data.SetBinding(Label.TextProperty,"Count");
                    Btn.Clicked += (object sender, EventArgs e) => 
                    {
                        Model model = (Model)(((Button)sender).Parent.BindingContext);
                        int count = Convert.ToInt32(model.Count);
                        count++;
                        model.Count = count.ToString();
    
                        // Need to refresh ListView from data source here... How???
                    };
    
                    StackLayout s = new StackLayout();
                    s.Orientation = StackOrientation.Horizontal;
                    s.Children.Add(Btn);
                    s.Children.Add(Data);
                    this.View = s;
                }
            }
    
            public MainPage ()
            {
                viewModel = new ViewModel();
                viewModel.ViewModelList.Add(new Model("0"));
                viewModel.ViewModelList.Add(new Model("0"));
                InitializeComponent();
            }
    
            public void InitializeComponent()
            {
                ListView listView = new ListView
                {
                    ItemsSource = viewModel.ViewModelList,
                    ItemTemplate = new DataTemplate(() =>
                        {
                            return new DataCell();
                        })
                };
                Content = listView;
            }
        }
    }