KirklandBrown373
KirklandBrown373

Reputation: 105

Updating ComboBox ObservableCollection Binding in XAML from ViewModel WPF

I am working in WPF and have a view model ModifiedReasonViewModel that is my DataContext for an XAML view. The ComboBox doesn't display any of the 4 items that I can see in the data context when I'm debugging. I think it definitely has to do with the fact that I'm pulling the reason labels asynchronously. Any ideas on how to fix this?

When I test this code with just a list of test strings initialized in the constructor, it works as intended.

This is the RadComboBox what I'm currently working with in the XAML:

d:DataContext="{d:DesignInstance Type=vm:ATMModifiedReasonViewModel, IsDesignTimeCreatable=True}">
<telerik:RadComboBox Name="ReasonCmbo"
                     ItemsSource="{Binding ReasonLabels}" 
                     DisplayMemberPath="Name"
                     IsEditable="False"
                     Margin="2" 
                     Grid.Column="1"
                     Grid.Row="1"
                     Grid.ColumnSpan="2">            
</telerik:RadComboBox>

And this is the ViewModel code I'm working with:

public class ATMModifiedReasonViewModel : INotifyPropertyChanged
{
   private List<LabelFileModel> _reasonLabels;

   public List<LabelFileModel> ReasonLabels { get { return _reasonLabels; } set { _reasonLabels = value; } }

   public ATMModifiedReasonViewModel(){
      GetReasonLabels();
   }

   public void GetReasonLabels()
   {
      LabelFileProvider lfProvider = new LabelFileProvider();
      LabelFileModelFilter filter = new LabelFileModelFilter() {LabelDefinition = "ModifiedReason"};
      lfProvider.GetFiltered(filter,10, getResult => GetReasonLabelsCallback(getResult));
   }

   private void GetReasonLabelsCallback(Func<IEnumerable<LabelFileModel>> getResult)
   {
       try
       {
            _reasonLabels = (List<LabelFileModel>) getResult();                
       }   
       catch (Exception ex)
       {
            Messenger.Default.Send(new UnhandledExceptionMessage(this, ex));
       }
   }
}

Thank you so much in advance for the help!

Upvotes: 0

Views: 701

Answers (2)

mm8
mm8

Reputation: 169320

Set the property in your callback and raise the PropertyChanged event from the setter of it, or raise it the PropertyChanged event in the callback after you have set the backing field:

private void GetReasonLabelsCallback(Func<IEnumerable<LabelFileModel>> getResult)
{
    try
    {
        _reasonLabels = (List<LabelFileModel>)getResult();
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ReasonLabels )));
    }
    catch (Exception ex)
    {
        Messenger.Default.Send(new UnhandledExceptionMessage(this, ex));
    }
}

Raising the event is required for the framework to know when to refresh the UI.

Upvotes: 0

thatguy
thatguy

Reputation: 22099

If you reassign the ReasonLabels collection at runtime, you have to implement INotifyPropertyChanged and raise the PropertyChanged event, otherwise the bindings will not notice the change.

Similarly, if you modify the collection itself, e.g. adding or removing items, the collection needs to implement INotifyCollectionChanged, which provides events to trigger an update in the user interface. The List<T> type does not implement this interface, use ObservableCollection<T> instead. It automatically raises the CollectionChanged event, if the collection is modified and therefore triggers an update of bindings.

public class ATMModifiedReasonViewModel : INotifyPropertyChanged
{
   private ObservableCollection<LabelFileModel> _reasonLabels;

   public ObservableCollection<LabelFileModel> ReasonLabels
   {
      get => _reasonLabels;
      set
      {
         if (_reasonLabels = value)
            return;

         _reasonLabels = value;
         OnPropertyChanged();
      } 
   }

   // ...your code here.

   public event PropertyChangedEventHandler PropertyChanged;

   protected virtual void OnPropertyChanged(string propertyName = null)
   {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
   }
}

You also have to assign the ReasonLabels property instead of the backing field _reasonLabels, otherwise the property setter that triggers the PropertyChanged event will not be executed.

private void GetReasonLabelsCallback(Func<IEnumerable<LabelFileModel>> getResult)
{
   try
   {
      ReasonLabels = (List<LabelFileModel>) getResult();                
   }   
   catch (Exception ex)
   {
      Messenger.Default.Send(new UnhandledExceptionMessage(this, ex));
   }
}

Upvotes: 2

Related Questions