question

raphaelzhang avatar image
0 Votes"
raphaelzhang asked JarvanZhang-MSFT commented

xamarin help, Error in collectionview property change

I need to write a page that can scroll through a list of many comics. Each comics shows summary information, such as cover thumbnail, book title and author. At the top of the page, there is a book type label that you can click. Every time you click the book type label, the comic list will be refreshed.


Because all the information of books is pulled asynchronously from the network, it is necessary to update the pulled title or cover thumbnail in time in the comic list. Of course, I have set the properties that need to be refreshed asynchronously to PropertyChanged.

The problem is, when the comic list is displayed for the first time, I see that the information of the book is constantly updated. However, when I switch the book type label, the update ability fails, and the Handler of the book triggering PropertyChanged is null.

The number of books is updated correctly, but the summary of each book cannot be updated.

I'm using CollectionView.

dotnet-xamarin
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

JarvanZhang-MSFT avatar image
1 Vote"
JarvanZhang-MSFT answered raphaelzhang commented

Hi, 40317169. How did you create the model class and viewModel class? Please make sure the 'summary' property raise the PropertyChanged event when the value is changed.

Here is the related code about the viewModel class in my test sample, you could refer to it.

public class BookViewModel : INotifyPropertyChanged
{
    public BookViewModel()
    {
        BookInfo = xxx;

        DataCollection = new ObservableCollection<BookModel>();
        //add the data
    }
    public ObservableCollection<BookModel> DataCollection { get; set; }

    private string bookInfo;
    public string BookInfo
    {
        get
        {
            return bookInfo;
        }
        set
        {
            if (bookInfo != value)
            {
                bookInfo = value;
                NotifyPropertyChanged();
            }
        }
    }

    protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    public event PropertyChangedEventHandler PropertyChanged;
}
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Please look at my code

0 Votes 0 ·
raphaelzhang avatar image
0 Votes"
raphaelzhang answered JarvanZhang-MSFT commented

This is my XAML, just the key part:

         <CollectionView ItemsSource="{Binding BookList}"
                         IsGrouped="False" 
                         ItemsLayout="VerticalGrid, 3"
                         SelectionChanged="OnTouchBook"
                         SelectionMode="Single"
                         x:Name="CV">
    
             <CollectionView.ItemTemplate>
                 <DataTemplate>
                     <Grid Padding="2">
                         <Grid.RowDefinitions>
                             <RowDefinition Height="150" />
                             <RowDefinition Height="15" />
                             <RowDefinition Height="12" />
                         </Grid.RowDefinitions>
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition Width="100" />
                         </Grid.ColumnDefinitions>
                         <Image Grid.Row="0" 
                                Source="{Binding CoverThumb}"
                                Aspect="AspectFill"
                                HeightRequest="150" 
                                WidthRequest="100" />
                         <Label Grid.Row="1" 
                                Text="{Binding Name}" 
                                FontSize="12"
                                TextColor="Black"
                                VerticalOptions="Center"/>
                         <Label Grid.Row="2"
                                Text="{Binding Author}"
                                FontAttributes="Italic" 
                                FontSize="10"
                                TextColor="#8F8F8F"
                                VerticalOptions="Start"/>
                     </Grid>
                 </DataTemplate>
             </CollectionView.ItemTemplate>
         </CollectionView>


ViewModel:

    public class RemoteViewModel : INotifyPropertyChanged
     {
         private readonly Dictionary<int, BookModel> DictBooks = new Dictionary<int, BookModel>();
    
    
         public RemoteViewModel(ICommand _cmd)
         {
             BookList = new ObservableCollection<BookModel>();
         }
    
    
    
         private ObservableCollection<BookModel> _BookList;
         public ObservableCollection<BookModel> BookList
         {
             get { return _BookList; }
             set
             {
                 if (value != _BookList)
                 {
                     _BookList = value;
                     NotifyPropertyChanged("BookList");
                 }
             }
         }
    
     
    
         public void SelectedLabel(string label)
         {
             if (label == "全部")
             {
                 LabelSelected = label;
    
                 BookList?.Clear();
                 BookStore.ForEachLabel((label_name, label_list) =>
                 {
                     foreach (ComicBook book in label_list)
                     {
                         BookModel model = new BookModel(book) { Store = BookStore, };
    
                         BookList?.Add(model);
                           
                         if (!DictBooks.ContainsKey(model.ID))
                             DictBooks.Add(model.ID, model);
                     }
    
                     return true;
                 });
    
    
             }
             else if(BookStore.TryGetBookList(label, out List<ComicBook> comics))
             {
                 LabelSelected = label;
    
                 BookList?.Clear();
    
                 foreach (ComicBook book in comics)
                 {
                     BookModel model = new BookModel(book) { Store = BookStore, };
    
                     BookList?.Add(model);
    
                     if (!DictBooks.ContainsKey(model.ID))
                         DictBooks.Add(model.ID, model);
                 }
    
             }
         }
     
    
         public event PropertyChangedEventHandler PropertyChanged;
    
         protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
         {
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
     }

Model:

   public class BookModel : INotifyPropertyChanged
     {
         public BookStore Store { get; set; }
    
         private string _Name;
         public string Name
         {
             get { return _Name; }
             private set
             {
                 if (value != _Name)
                 {
                     _Name = value;
                     NotifyPropertyChanged("Name");
                 }
             }
         }
    
         private int _ID;
         public int ID
         {
             get { return _ID; }
             private set
             {
                 if (value != _ID)
                 {
                     _ID = value;
                     NotifyPropertyChanged("ID");
                 }
             }
         }
    
         private string _Author;
         public string Author
         {
             get 
             {
                 if (string.IsNullOrEmpty(_Author)) return "无";
                 else return _Author; 
             }
             private set
             {
                 if (value != _Author)
                 {
                     _Author = value;
                     NotifyPropertyChanged("Author");
                 }
             }
         }
    
         private string _Editor;
         public string Editor
         {
             get
             {
                 if (string.IsNullOrEmpty(_Editor)) return "无";
                 else return _Editor;
             }
             private set
             {
                 if (value != _Editor)
                 {
                     _Editor = value;
                     NotifyPropertyChanged("Editor");
                 }
             }
         }
    
         private string _EditDate;
         public string EditDate
         {
             get
             {
                 if (string.IsNullOrEmpty(_EditDate)) return "无";
                 else return _EditDate;
             }
             private set
             {
                 if (value != _EditDate)
                 {
                     _EditDate = value;
                     NotifyPropertyChanged("EditDate");
                 }
             }
         }
    
         public string _Desc;
         public string Desc
         {
             get
             {
                 if (string.IsNullOrEmpty(_Desc)) return "无";
                 else return _Desc;
             }
             private set
             {
                 if (value != _Desc)
                 {
                     _Desc = value;
                     NotifyPropertyChanged("Desc");
                 }
             }
         }
    
         public ImageSource CoverThumb
         {
             get
             {
                 if (Comic.NoAbstract)
                 {
                     Store.RefreshBookAbstract(Comic.ID);
                     return null;
                 }
                 else if (Comic.Cover != null && Store.TryReadThumbnails(Comic.Cover, out MemoryStream thumb1))
                     return ImageSource.FromStream(() => { return thumb1; });
                 else if (Comic.Cover == null && Store.TryReadThumbnails(Comic.GetPage(0), out MemoryStream thumb2))
                     return ImageSource.FromStream(() => { return thumb2; });
                 else return null;
             }
             private set{ NotifyPropertyChanged("CoverThumb"); }
         }
    
         public ComicBook Comic { get; private set; }
    
         public BookModel(ComicBook comic) { Refresh(comic); }
    
         public void Refresh(ComicBook comic)
         {
             Name = comic.BookName; 
             ID = comic.ID; 
             Author = comic.BookAuthor; 
             Comic = comic;
             Editor = comic.BookEditor;
             EditDate = comic.EditDate.ToString("D");
             Desc = comic.Describe;
         }
    
         public void RefreshCoverThumb() { CoverThumb = null; }
    
         public event PropertyChangedEventHandler PropertyChanged;
    
         protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
         {
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
     }


· 10
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

In addition, TryReadThumbnails will return null at the first call and request the server to pull the data. After got the data, they will call the RefreshCoverThumb or Refresh function in the model to trigger PropertyChanged

0 Votes 0 ·

问题的关键是,漫画列表第一次显示的时候,我可以看到封面缩略图、书名、作者陆续更新成功,但如果我切换书籍类型标签后,也就是说BookList改动之后,BookModel都失去了更新能力,我调试到NotifyPropertyChanged中发现event handler变成了null


The key to the problem is that when the comic list is displayed for the first time, I can see the cover thumbnail, title and author update successfully. However, if I switch the book type label, that is, after the booklist changes, the bookmodel will lose the update ability. I debug it to notifypropertychanged and find that event handler becomes null

0 Votes 0 ·

Hi,

ObservableCollection<T> implements collection changed notification, it doesn't have to implement the INotifyCollectionChanged interface on collections. You could just create the 'BookList' property in the as 'RemoteViewModel' class below:

public class RemoteViewModel : INotifyPropertyChanged
{
    public ObservableCollection<BookModel> BookList { get; set; }
}

Check the doc: https://docs.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/mvvm#model

0 Votes 0 ·

That's not the point, but thanks for your help

0 Votes 0 ·
Show more comments

I've been bothered by this problem for many days

0 Votes 0 ·

Hi,

I cannot reproduce the issue on my side, could you share a repro demo on the github and post the link here? It'll help to get a solution.

0 Votes 0 ·

remind~~~~~~~~~

0 Votes 0 ·