I'm creating an application with a book library. I can add books to the library and they appear correctly when I rebuild the application. However I want the book to appear immediately on the screen after adding them and thought of doing so by using a observable collection.
My collection view:
<CollectionView ItemsSource="{Binding Books}" Margin="36, 20" Grid.Row="1">
<CollectionView.ItemTemplate>
<DataTemplate>
<Border StrokeThickness="3" Margin="0, 0, 0, 4">
<Border.Stroke>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="White" Offset="0.1" />
<GradientStop Color="{StaticResource Gray950}" Offset="1.0" />
</LinearGradientBrush>
</Border.Stroke>
<Grid HeightRequest="84">
<Image Aspect="AspectFill" Source="{Binding ThumbnailSource}"/>
<FlexLayout JustifyContent="Center" Direction="Column" HeightRequest="84" Margin="16">
<FlexLayout.Background>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Color="#80000000" Offset="0.4"/>
<GradientStop Color="#00000000" Offset="0.9"/>
</LinearGradientBrush>
</FlexLayout.Background>
<Label FontSize="20" TextColor="White" Text="{Binding Title}" FontAttributes="Bold"/>
</FlexLayout>
</Grid>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
My view model:
namespace eBooks.ViewModel
{
public partial class LibraryViewModel : ObservableObject
{
public LibraryViewModel()
{
BookService.OnBooksChanged = RefreshLibrary;
RefreshLibrary();
}
public void RefreshLibrary(List<Book> books = null)
{
using var db = new BooksContext();
_books = new ObservableCollection<Book>(db.Books);
}
[ObservableProperty]
ObservableCollection<Book> _books;
}
}
My Book model
namespace eBooks.Models
{
[Table("Books")]
public class Book
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Title { get; set; }
public double Progress { get; set; }
public string ThumbnailData { get; set; }
public string AudioData { get; set; }
public string ThumbnailFormat { get; set; }
public string AudioFormat { get; set; }
private byte[] _thumbnailData = null;
public ImageSource ThumbnailSource
{
get
{
if (_thumbnailData == null || _thumbnailData.Length == 0 && ThumbnailData.Length != 0)
_thumbnailData = Convert.FromBase64String(ThumbnailData);
return ImageSource.FromStream(() => new MemoryStream(_thumbnailData));
}
}
}
}
My service that handles actions for my EF Core database layer:
namespace eBooks.Services
{
public class BookService
{
public delegate void BooksChanged(List<Book> books);
public static event BooksChanged OnBooksChanged;
public void Add(Book book)
{
using var db = new BooksContext();
db.Books.Add(book);
db.SaveChanges();
OnBooksChanged?.Invoke(db.Books.ToList());
}
public void Delete(int id)
{
using var db = new BooksContext();
db.Books.Remove(db.Books.Single(book => book.Id == id));
db.SaveChanges();
OnBooksChanged?.Invoke(db.Books.ToList());
}
}
}
I have tried adding a 'refreshlibrary' method that runs when the OnBooksChanged
event is invoked in the book service as you can see in the code above. By adding a breakpoint to the method I can see that it contains the newly added book after adding it, however the UI still does not show the book.
CodePudding user response:
In your case ObservableCollection
has no advantage over a simple List
. The advantage of an ObservableCollection
is that it will notify changes to the controls it's bound to, if you manipulate the contents of the collection. Here, you are not manipulating the contents of _books
, you are assigning a new instance every time. So let's say at the beginning you have (Book1, Book2)
in _books
, let's name this collection A
, if Book3
gets added to your database, you will assign a new collection (Book1, Book2, Book3)
to _books
, let's name the new collection B
. Note that you haven't added Book3
to A
anywhere. Now, your CollectionView
is still bound to A
which has not been changed, hence no update notifications will be received. All you need to do is to make sure the CollectionView
re-evaluates its binding and is bound to B
. There are a couple of ways to do this:
- Set the generated property instead of the backing field, as it will automatically call
OnPropertyChanged
:
public void RefreshLibrary(List<Book> books = null)
{
using var db = new BooksContext();
Books = new ObservableCollection<Book>(db.Books);
}
- Call
OnPropertyChanged
:
public void RefreshLibrary(List<Book> books = null)
{
using var db = new BooksContext();
_books = new ObservableCollection<Book>(db.Books);
OnPropertyChanged("Books");
}
Again, you can safely change the type of _books
to List<Book>
as it's not manipulated anywhere.