Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make one viewmodel update a property on another viewmodel?

I need a simple example on how to make one viewmodel update a property on another viewmodel.

Here' is the situation. I got a view and viewmodel responsable fo displaying a list of albums. I got another view and viewmodel responsable for added a new albums ( couple of textboxes and a button ) now when the new album gets added how do I tell the Collection in the other view that the a new album has been added to it? I read about frameworks that can do this for me, but Im trying to learn so I won't use a framework for the being..

like image 982
Ols1 Avatar asked Feb 26 '11 16:02

Ols1


2 Answers

here are some pieces of the puzzle, lifted from Josh Smith's classic demo app, showing how events can be used to support the testability and loose couple afforded by mvvm

The data

This isn't a view model of cource, but most interesting apps have data & it has to come from somewhere! And it is an obvious and convenient candidate for holding an event for when a new item has been added:

public class CustomerRepository
{
    ...

    /// <summary>Raised when a customer is placed into the repository.</summary>
    public event EventHandler<CustomerAddedEventArgs> CustomerAdded;

    /// <summary>
    /// Places the specified customer into the repository.
    /// If the customer is already in the repository, an
    /// exception is not thrown.
    /// </summary>
    public void AddCustomer(Customer customer)
    {
        if (customer == null) throw new ArgumentNullException("customer");
        if (_customers.Contains(customer)) return;

        _customers.Add(customer);

        if (CustomerAdded != null)
            CustomerAdded(this, new CustomerAddedEventArgs(customer));
    }

    ...
}

The Shell

Consider having a view model that coordinates a given presentation, perhaps by managing workspaces. Some people might call it a Manager (yuk!), or MainViewModel. I like ShellViewModel. This view model has a command to create new items:

public class MainWindowViewModel : WorkspaceViewModel
{

    readonly CustomerRepository _customerRepository;

    public MainWindowViewModel(...)
    {
        _customerRepository = new CustomerRepository(customerDataFile);
    }

    void _createNewCustomer()
    {
        var newCustomer = Customer.CreateNewCustomer();
        var workspace = new CustomerViewModel(newCustomer, _customerRepository);
        Workspaces.Add(workspace);
        _setActiveWorkspace(workspace);
    }

    ObservableCollection<WorkspaceViewModel> _workspaces;

    void _setActiveWorkspace(WorkspaceViewModel workspace)
    {
        var collectionView = CollectionViewSource.GetDefaultView(Workspaces);
        if (collectionView != null)
            collectionView.MoveCurrentTo(workspace);
    }

 }

The model object

Did you notice the static factory constructor method (Customer.CreateNewCustomer)? It makes clear what it's purpose is, and gives an opportunity to encapsulate any complexities involved in creating the new customer.

The model object ViewModel wrapper

This typically derives from a base class that makes INPC notification simple to use, since it is the basis for data binding. Notice the Email property is a direct pass through to the model object's email property, yet DisplayNAme is purely UI driven. In the case of adding a new item, it appropriately says..."New Cistomer":

public class CustomerViewModel : WorkspaceViewModel, IDataErrorInfo
{

    public CustomerViewModel(Customer customer, CustomerRepository customerRepository)
    {
        if (customer == null) throw new ArgumentNullException("customer");
        if (customerRepository == null) throw new ArgumentNullException("customerRepository");

        _customer = customer;
        _customerRepository = customerRepository;
     }

    readonly Customer _customer;

    public string Email
    {
        get { return _customer.Email; }
        set
        {
            if (value == _customer.Email) return;

            _customer.Email = value;
            base.OnPropertyChanged("Email");
        }
    }

    public override string DisplayName
    {
        get {
            if (IsNewCustomer)
            {
                return Strings.CustomerViewModel_DisplayName;
            }
            ...

            return String.Format("{0}, {1}", _customer.LastName, _customer.FirstName);
        }
    }


    #region Save Command

    /// <summary>
    /// Returns a command that saves the customer.
    /// </summary>
    public ICommand SaveCommand
    {
        get
        {
            return _saveCommand ??
                   (_saveCommand = new RelayCommand(param => _save(), param => _canSave));
        }
    }
    RelayCommand _saveCommand;

    /// <summary>
    /// Returns true if the customer is valid and can be saved.
    /// </summary>
    bool _canSave
    {
        get { return String.IsNullOrEmpty(_validateCustomerType()) && _customer.IsValid; }
    }

    /// <summary>
    /// Saves the customer to the repository.  This method is invoked by the SaveCommand.
    /// </summary>
    void _save()
    {
        if (!_customer.IsValid)
            throw new InvalidOperationException(Strings.CustomerViewModel_Exception_CannotSave);

        if (IsNewCustomer)
            _customerRepository.AddCustomer(_customer);
        base.OnPropertyChanged("DisplayName");
    }

}

The ViewModel collection of ViewModels

This might support filtering, sorting, summing. In the case of adding a new customer, notice it is subscribing to that event we added to the Repository. Notice also that it uses an ObservableCollection, since it has built in support for databinding.

public class AllCustomersViewModel : WorkspaceViewModel
{

    public AllCustomersViewModel(CustomerRepository customerRepository)
    {
        if (customerRepository == null) throw new ArgumentNullException("customerRepository");

        _customerRepository = customerRepository;

        // Subscribe for notifications of when a new customer is saved.
        _customerRepository.CustomerAdded += OnCustomerAddedToRepository;

        // Populate the AllCustomers collection with CustomerViewModels.
        _createAllCustomers();              
    }

    /// <summary>
    /// Returns a collection of all the CustomerViewModel objects.
    /// </summary>
    public ObservableCollection<CustomerViewModel> AllCustomers
    {
        get { return _allCustomers; }
    }
    private ObservableCollection<CustomerViewModel> _allCustomers;

    void _createAllCustomers()
    {
        var all = _customerRepository
            .GetCustomers()
            .Select(cust => new CustomerViewModel(cust, _customerRepository))
            .ToList();

        foreach (var cvm in all)
            cvm.PropertyChanged += OnCustomerViewModelPropertyChanged;

        _allCustomers = new ObservableCollection<CustomerViewModel>(all);
        _allCustomers.CollectionChanged += OnCollectionChanged;
    }

    void OnCustomerViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        const string IsSelected = "IsSelected";

        // Make sure that the property name we're referencing is valid.
        // This is a debugging technique, and does not execute in a Release build.
        (sender as CustomerViewModel).VerifyPropertyName(IsSelected);

        // When a customer is selected or unselected, we must let the
        // world know that the TotalSelectedSales property has changed,
        // so that it will be queried again for a new value.
        if (e.PropertyName == IsSelected)
            OnPropertyChanged("TotalSelectedSales");
    }

    readonly CustomerRepository _customerRepository;

    void OnCustomerAddedToRepository(object sender, CustomerAddedEventArgs e)
    {
        var viewModel = new CustomerViewModel(e.NewCustomer, _customerRepository);
        _allCustomers.Add(viewModel);
    }

}

Check out the full article and download the code!

HTH,
Berryl

like image 189
Berryl Avatar answered Sep 24 '22 17:09

Berryl


There several ways:

1) AlbumsVM is aware of CreateAlbumVM (for example first opens the second). In this case you can simply add album into AlbumsVM using details provided by CreateAlbumVM
2) CreateAlbumVM is aware of AlbumsVM. Then it can insert albums into AlbumsVM itself.
3) AlbumsVM receives albums as ObservableCollection from somewhere. Then CreateAlbumVM can insert new album into original ObservableCollection which will be reflected in AlbumsVM
4) There is some mediator between these viewModels which provide event AlbumWasAdded.

like image 23
Snowbear Avatar answered Sep 22 '22 17:09

Snowbear