-1

Please can you advise if this is possible or what approach is best?

I will add my code afterwards but I am worried that it will add limited value to what I am trying to ask/explain.

The WPF examples that I have seen implement

  1. A (poco) model e.g Customer.
  2. Then they implement a view model (MVVM pattern). (The View Model needs to implement ObservableCollection and or implement INotifyPropertyChanged interface so that any changes in the model are reflected in the UI once the model changes.)
  3. In the xaml code behind, in the page constructor, the ViewModel is passed to the DataContext.
  4. The xaml can then bind to this View Model Data with a Mode of update e.g. TwoWay.

Here is what I need to understand. I have implemented my own data model / class, which has async tasks constantly updating the status of different fields in this class.

My model resides in a separate class library that I would like to inject/supply it to a view model. However, since my object/class is 'self-updating', I can't simply copy across values into my view model - since they will change over time. I want my view model to be aware of changes that underlying values and show these changes in the UI as the async tasks update the model.

How do I go about this? So in my example, my Customer object will be self-updating, some background task would add/remove customers in a class library outside of my ViewModel.

I hope that I have managed to ask my question in a way that is clear to understand.

The XAML binding to the Customer View Model

 <ListView Grid.Row="1" ItemsSource="{x:Bind ViewModel.Customers,Mode=OneWay}"
            SelectedItem="{x:Bind ViewModel.SelectedCustomer,Mode=TwoWay}">
    <ListView.ItemTemplate>
      <DataTemplate x:DataType="model:Customer">
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="{x:Bind FirstName,Mode=OneWay}" FontWeight="Bold"/>
          <TextBlock Text="{x:Bind LastName,Mode=OneWay}"
                     Margin="5 0 0 0"/>
          <TextBlock Text="(Developer)" Margin="5 0 0 0" Opacity="0.5"
                     Visibility="{x:Bind IsDeveloper,Mode=OneWay}"/>
        </StackPanel>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>

Reference for this sample code

     public class MainViewModel : Observable
  {
    private ICustomerDataProvider _customerDataProvider;
    private Customer _selectedCustomer;

    public MainViewModel(ICustomerDataProvider customerDataProvider)
    {
      _customerDataProvider = customerDataProvider;
      Customers = new ObservableCollection<Customer>();
    }

    public Customer SelectedCustomer
    {
      get { return _selectedCustomer; }
      set
      {
        if (_selectedCustomer != value)
        {
          _selectedCustomer = value;
          OnPropertyChanged();
          OnPropertyChanged(nameof(IsCustomerSelected));
        }
      }
    }

    public bool IsCustomerSelected => SelectedCustomer != null;

    public ObservableCollection<Customer> Customers { get; }

    public async Task LoadAsync()
    {
      Customers.Clear();

      var customers = await _customerDataProvider.LoadCustomersAsync();
      foreach (var customer in customers)
      {
        Customers.Add(customer);
      }
    }

    public async Task SaveAsync()
    {
      await _customerDataProvider.SaveCustomersAsync(Customers);
    }

    public void AddCustomer()
    {
      var customer = new Customer { FirstName = "New" };
      Customers.Add(customer);
      SelectedCustomer = customer;
    }

    public void DeleteCustomer()
    {
      var customer = SelectedCustomer;
      if (customer != null)
      {
        Customers.Remove(customer);
        SelectedCustomer = null;
      }
    }
  }

The INotifyPropertyChanged is implemented here:

  public class Observable : INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
  }
Craig Gers
  • 544
  • 3
  • 9
  • I'm not sure what your issue is exactly. How are `AddCustomer` and `DeleteCustomer` being called? Are they in a class outside of your ViewModel, and that is the problem? – Peter Boone Oct 05 '20 at 18:13
  • 1
    You can implement INotifyPropertyChanged even in your model. When the property changes, it can call a method to send 'new' data to the view-model. Alternatively, you can create event and publish whenever the model changes and your view-model can subscribe to it and update the observablecollection. – Bandook Oct 06 '20 at 06:52

1 Answers1

0

Thanks @Bandook for your comments. Sometimes the coding is the easy part, but the thought behind and the process of fitting the parts together in a simple and coherent way can be more difficult.

The solution I implemented was as follows:

As per the customer example, I had had to implement methods that updated my class properties that implemented the INotifyPropertyChanged interface. (This was almost identical to the Customer code.) The result of this is that any changes to the underlying model are reflected in the UI.

The kicker was this. I implemented a Publish Subscribe Design Pattern in my class library - that continually refreshed the data. My 'Customer' class had to then had to subscribe to change events published by the class library.

There was one problem, however, the thread for the Class Library code and that of the application thread was not the same, resulting in a clash.

The article here allowed me to solve this issue.

In summary, the solution was to implement a Publish Subscribe Design Pattern. My class library published updates and my View Model class (similar to the customer class) subscribed to the events it published.

Craig Gers
  • 544
  • 3
  • 9