Как изменить источник изображения при изменении свойства через привязку данных в XAML из viewmodel в xamarin forms?

#xamarin.forms #mvvm #data-binding

#xamarin.формы #mvvm #привязка к данным

Вопрос:

Я работаю над предоставлением функции списка желаний для своего приложения, нажимая значок списка желаний на каждом продукте в списке через MVVM. После нажатия выполняется вызов API для обновления базы данных (добавление / удаление из таблицы списка пожеланий). Основываясь на результате вызова api, я обновил соответствующее свойство конкретного продукта на ‘True’ или ‘False’. После обновления свойства я хочу изменить источник изображения значка соответствующего продукта. Я использую триггер на значке списка желаний, чтобы различать продукты, не входящие в список желаний, и продукты из списка желаний при привязке самого списка.

Мой код приведен ниже,

Модель

 public class PublisherProducts
{
   public long ProductId { get; set; }
   public string ProductName { get; set; }
   public string ImageURL { get; set; }
   public decimal Price { get; set; }
   public bool IsWishlistProduct { get; set; }
}
 

VIEWMODEL

 public class OnlineStoreViewModel : BaseViewModel
{
 private ObservableCollection<PublisherProducts> publisherProducts;
 public Command<long> WishlistTapCommand { get; }

 public OnlineStoreViewModel()
 {
    publisherProducts = new ObservableCollection<PublisherProducts>();
    WishlistTapCommand = new Command<long>(OnWishlistSelected);
 }

 public ObservableCollection<PublisherProducts> PublisherProducts
 {
   get { return publisherProducts; }
   set
   {
    publisherProducts = value;
    OnPropertyChanged();
   }
 }                  

 public async Task GetProducts(long selectedCategoryId)
 {
  try
    {
      ...
      PublisherProducts = new ObservableCollection<PublisherProducts>(apiresponse.ProductList);
      ...
    }
    catch (Exception ex) {  ... }
    finally {  ... }
 }

 async void OnWishlistSelected(long tappedProductId)
 {
   if (tappedProductId <= 0)
     return;
   else
     await UpdateWishlist(tappedProductId);
 }

 public async Task UpdateWishlist(long productId)
 {
   try
   {
    var wishlistResponse = // api call
    var item = PublisherProducts.Where(p => p.ProductId == productId).FirstOrDefault();
    item.IsWishlistProduct = !item.IsWishlistProduct;

    PublisherProducts = publisherProducts;  *Stuck here to toggle wishlist icon*

    await App.Current.MainPage.DisplayAlert("", wishlistResponse.Message, "Ok");
   }
   catch (Exception ex) {  ... }
   finally {  ... }
 }    
}
 

XAML

     <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" ... >
     <ContentPage.Content>
      <ScrollView>
        <StackLayout Padding="15,0,15,10">
         <FlexLayout x:Name="flxLayout" BindableLayout.ItemsSource="{Binding PublisherProducts}" ...>
           <BindableLayout.ItemTemplate>
             <DataTemplate>
               <AbsoluteLayout Margin="6" WidthRequest="150">
                  <Frame Padding="0" WidthRequest="150" CornerRadius="10" HasShadow="True">
                     <StackLayout Orientation="Vertical" Padding="10" HorizontalOptions="FillAndExpand">
                       <Image Source="{Binding ImageURL}" WidthRequest="130" HeightRequest="130" HorizontalOptions="Center"/>
                       <Label Text="{Binding ProductName}" Style="{StaticResource ProductNameStyle}"></Label>
                       ...
                       <StackLayout ...>
                         ...                                                
                         <Frame x:Name="wlistFrame" Padding="0" WidthRequest="30" HeightRequest="30" CornerRadius="10" BorderColor="#02457A">
                           <StackLayout Orientation="Horizontal" VerticalOptions="Center" HorizontalOptions="Center">
                              <Image x:Name="wlImage" WidthRequest="13" HeightRequest="12" HorizontalOptions="Center" VerticalOptions="Center" Source="ic_wishlist_open">
                                <Image.Triggers>
                                   <DataTrigger TargetType="Image" Binding="{Binding IsWishlistProduct}" Value="true">
                                      <Setter Property="Source" Value="ic_wishlist_close" />
                                   </DataTrigger>
                                </Image.Triggers>
                               </Image>
                            </StackLayout>
                            <Frame.GestureRecognizers>
                                <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type local:OnlineStoreViewModel}}, Path=WishlistTapCommand}" CommandParameter="{Binding ProductId}" NumberOfTapsRequired="1" />                                                        
                            </Frame.GestureRecognizers>
                         </Frame>

                       </StackLayout>                                            
                     </StackLayout>
                  </Frame>                                    
               </AbsoluteLayout>
             </DataTemplate>
          </BindableLayout.ItemTemplate>
        </FlexLayout>
       </StackLayout>          
     </ScrollView>
    </ContentPage.Content>
  </ContentPage>
 

Я застрял в этом месте, чтобы изменить значок списка желаний, когда значение свойства ‘IsWishlistProduct’ изменяется в UpdateWishlist ().

Комментарии:

1. вероятно, было бы проще отобразить оба значка и просто переключать их свойства isVisible по мере необходимости

2. @Jason, ты имеешь в виду, что isVisible = {Привязка IsWishlistProduct} . Я тоже пробовал это. Когда значение ‘IsWishlistProduct’ изменяется в виртуальной машине, оно не будет отражаться в представлении.

3. ваша модель должна реализовать INotifyPropertyChanged, если вы хотите, чтобы пользовательский интерфейс обновлялся при изменении модели

4. Модель или ViewModel? Виртуальная машина уже наследует это через BaseViewModel

5. какой бы класс ни содержал IsWishlistProduct , если это свойство, которое вы хотите вызвать изменение пользовательского интерфейса

Ответ №1:

Угадывая ваш код, BaseViewModel содержит код, аналогичный следующему:

 public class BaseViewModel : INotifyPropertyChanged
{
   ...
   public event PropertyChangedEventHandler PropertyChanged;
   ...

   public void OnPropertyChanged(string name)
   {
      this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
   }
   ...
}
 

И ваша viewmodel должна быть такой:

 ...
public ObservableCollection<PublisherProducts> PublisherProducts
 {
   get { return publisherProducts; }
   set
   {
    publisherProducts = value;
    OnPropertyChanged(nameof(PublisherProducts));
   }
 }
...          
 

Как упоминал Джейсон, если в ViewModel происходят изменения в данных, это отражается в пользовательском интерфейсе, когда об этом сообщается в представлении через NotifyPropertyChanged. Вы уже реализовали функцию «OnPropertyChanged» в своей BaseViewModel, но, похоже, вы не передаете имя объекта.

Комментарии:

1. Да, модель BaseViewModel аналогична. Я попытался, как вы сказали, передать имя объекта. Но это не отразилось на пользовательском интерфейсе.