Визуальное обновление привязки элементов CollectionView (Xamarin.Формы)

#c# #xamarin.forms #mvvm #binding

Вопрос:

Я попытался создать некоторый шаблон MVVM в своем приложении, и у меня возникла проблема с визуальным представлением данных. Данные, если привязанный observablecollecrion обновлен, но визуальный-нет. некоторый код: ViewModel:

 public class HlavnaViewModel : BaseViewModel {  public HlavnaViewModel()  {  }     private Doklady _selectedDok;  public Doklady vm_selectedDok  {  get =gt; _selectedDok;  set  {  _selectedDok = value;  OnPropertyChanged(nameof(vm_selectedDok));  update_polozky();  }  }   public async void update_polozky()  {  Polozky dok = new Polozky() { id_doklad = _selectedDok.id };  ObservableCollectionlt;Polozkygt; pol = new ObservableCollectionlt;Polozkygt;(await App.Database.GetPolozkyAsync(dok));  vm_polozky = pol;  }  private ObservableCollectionlt;Polozkygt; _polozky;  public ObservableCollectionlt;Polozkygt; vm_polozky  {  get =gt; _polozky;  set  {  _polozky =value;  OnPropertyChanged(nameof(vm_polozky));  }  } }  

в XAML:

 lt;CollectionView x:Name="polozky" SelectionMode="Single" ItemsSource="{Binding vm_polozky}"gt;...  

Базовая модель:

 public class BaseViewModel : INotifyPropertyChanged {  string title = string.Empty;  public string Title  {  get { return title; }  set { SetProperty(ref title, value); }  }   protected bool SetPropertylt;Tgt;(ref T backingStore, T value,  [CallerMemberName] string propertyName = "",  Action onChanged = null)  {  if (EqualityComparerlt;Tgt;.Default.Equals(backingStore, value))  return false;   backingStore = value;  onChanged?.Invoke();  OnPropertyChanged(propertyName);  return true;  }   #region INotifyPropertyChanged  public event PropertyChangedEventHandler PropertyChanged;  protected void OnPropertyChanged([CallerMemberName] string propertyName = "")  {  var changed = PropertyChanged;  if (changed == null)  return;   changed.Invoke(this, new PropertyChangedEventArgs(propertyName));  }  

finally in View:

 public Hlavna()  {  InitializeComponent();  hvm = new HlavnaViewModel();  this.BindingContext = hvm;    }  

если я выберу строку в CollectionView, в которой установлена привязка vm_selectedDok, он выберет этот элемент, запустит функцию update_polozky (), vm_polozky будет заполнен правильными данными, но визуальный элемент просто не показывает элементы из vm_polozky.

Я прочитал пару подобных вопросов, но не могу понять, где я допустил ошибку.

РЕДАКТИРОВАТЬ: итак, проблема была где-то в другом месте, у меня были неправильно заданы определения grid.row, поэтому сетка находилась за пределами видимой области.

@ToolmakerSteve сделал хорошие предложения по вызову async/await, пожалуйста, прочитайте его ответ.

Ответ №1:

Есть два альтернативных способа исправить это.


Один из способов-это ответ Джеральда. Это подходит для небольших коллекций, но может быть медленнее, если добавляется много элементов.


Второй способ сделать то, что вы сделали, — заменить коллекцию. Но в вашем коде требуется исправление.

То, как вы позвонили update_polozky , не будет работать надежно. Вы не начинаете async/await последовательность внутри an await .

Заменять:

 update_polozky();  

С:

 Device.BeginInvokeOnMainThread(async () =gt; {  await update_polozky(); });  

НЕОБЯЗАТЕЛЬНО: Может также внести это изменение. (Хотя в этом не должно быть необходимости.) Это дает хорошее место для размещения точки останова, чтобы увидеть, получит ли «результат» ожидаемое содержимое.

Заменять:

 ObservableCollectionlt;Polozkygt; pol = new ObservableCollectionlt;Polozkygt;(await App.Database.GetPolozkyAsync(dok));  

С:

 var result = await App.Database.GetPolozkyAsync(dok); ObservableCollectionlt;Polozkygt; pol = new ObservableCollectionlt;Polozkygt;(result);  

ВАЖНЫЕ ПРИМЕЧАНИЯ ДЛЯ ДРУГИХ ПРОГРАММИСТОВ:

Второй подход («заменить коллекцию») опирается на OnPropertyChanged(nameof(vm_polozky)); в setter из ObservableCollection .

У вас это есть, так что для вас это не проблема. Я упоминаю об этом для всех, кто может адаптировать этот код.

Например, я видел, как люди пытались установить частное значение напрямую, например:

 // Don't do this to replace the collection. XAML won't know you changed it! _myPrivateField = new ObservableCollectionlt;MyItemgt;(result);  

Я также видел, как люди пытались создать наблюдаемую коллекцию без сеттера:

 // This is okay UNLESS you replace the collection - in which case you need `OnPropertyChanged(nameof(MyCollection))` somewhere: public ObservableCollectionlt;MyItemgt; MyCollection { get; set; }  

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

1. Выполнение задач только в потоке пользовательского интерфейса также не очень хорошо с точки зрения производительности и в данном случае не работает. Замена ObservableCollection на новый экземпляр приведет к разрыву привязки данных к пользовательскому интерфейсу, если вы не установите его снова после этого.

2. @ToolmakerSteve отличные предложения, спасибо.

3. @GeraldVersluis — извините, но мой опыт говорит об обратном. Я использовал этот код несколько раз. 1) производительность в потоке пользовательского интерфейса — асинхронность/ожидание должны хорошо справляться с этим, если предположить, что весь вызываемый код правильно написан для ожидания. В этом смысл асинхронности/ожидания!! Если код может вызвать задержку, я вызываю отдельный поток, а затем снова вызываю основной поток, когда список будет готов. 2) Замена всего списка сразу более эффективна в XF, чем добавление элементов по одному за раз. 3) Привязка данных-Как я уже упоминал, для этого требуется OnPropertyChanged. У ОП это уже есть в их коде. См. раздел vm_polozky setter.

4. Тогда я, наверное, неправильно вас понимаю 🙂 рад, что все разрешилось!

Ответ №2:

В основном все сводится к тому, чтобы не делать этого: ObservableCollectionlt;Polozkygt; pol = new ObservableCollectionlt;Polozkygt;(await App.Database.GetPolozkyAsync(dok));

Всякий раз, когда вы создадите новый ObservableCollection , он потеряет привязку данных к пользовательскому интерфейсу. Очистите свой ObservableCollection с .Clear() и добавьте в него новые элементы с for помощью цикла. Например:

 public async void update_polozky() {  Polozky dok = new Polozky() { id_doklad = _selectedDok.id };  var results = await App.Database.GetPolozkyAsync(dok);   vm_polozky.Clear();   foreach(var item in results)  vm_polozky.Add(item); }  

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

1. ПРЕДОСТЕРЕЖЕНИЕ: Это хорошо для небольших коллекций, но может быть медленным, если элементов много — каждый Add запускает обновление пользовательского интерфейса в формах Xamarin. (В идеале цикл foreach завершится до того, как XF попытается отобразить, но в прошлом у меня были проблемы.) Когда много пунктов, я рекомендую подход, который пытается сделать OP. Однако в коде, показанном в вопросе, есть ошибка. (Я добавил ответ, который пытается исправить эту ошибку.)

2. приведенный выше код работает правильно, но он показывает, что привязка данных все еще сохраняется при создании новой коллекции ObservableCollection, поэтому его тоже можно использовать таким образом.

3. @Taliga — верно; сеттер OnPropertyChanged в vm_polovsky указывает Xamarin обновить любой XAML, связанный с vm_polovsky.