Matteo Beretta
Matteo Beretta

Reputation: 89

Prism Unity - EventAggregator hierarchy

i have a problem with EventAggregator, on how make it work, i show a sample application where i have 4 projects:

-Shell -Module 'Order' -Module 'Customer' -Module 'Customer references' -Shared (global event aggregator)

Assume that i want to add a new Order, inside my order form (Module.Order) i have a combobox with customers, and a button that open another form to create a customer (Module.Customer) and i want that when i create my customer, the combobox inside the order form reload

I can manage this with that

OrderForm

   private void AddCustomerExecute(object Dialog)
    {
        DialogVisibility = System.Windows.Visibility.Collapsed;

        _unityContainer.RegisterInstance<string>("RegionName", M_G_O_ListModule._RegionDialog);

        _eventAggregator.GetEvent<UIX_GlobalEvent.PassParameter>().Subscribe(ReloadCustomers);

        _moduleManager.LoadModule("M_T_S_CustomersModule");

        _shellService.ShowDialogUser(M_G_O_ListModule._RegionDialog, "M_T_S_CustomersModule_Add");
    }

CustomerForm

    private void SaveExecute()
    {
        Customer.CustomerCollection.LogAdded = DateTime.Now;
        Customer.CustomerCollection.LogUserID = _userCompany.User.UserID;

        _customerRepository.InsertCollection(Customer.CustomerCollection);

        _shellService.CloseDialogUser(_regionName, M_T_S_CustomersModule._viewAdd);

        _eventAggregator.GetEvent<UIX_GlobalEvent.PassParameter>().Publish(Customer.CustomerCollection.CustomerID);
    }

The problem is when from the CustomerForm, i want to add a Reference (Module.CustomerReference) because, if i use the same process, when i save the CustomerReference it reload the combobox inside the order form, because the global event is subscribed

Someone know how can i manage this? Thank you

Upvotes: 0

Views: 605

Answers (2)

Adam Vincent
Adam Vincent

Reputation: 3811

I'm going to restate the question so I make sure I understood it properly. Bear with me.

You have a Customer Form, (Modules.Customer) in which you can both 1) Add a new Customer and 2) Add a new Customer Reference.

When you create a new Customer, it fires an Event (UIX_GlobalEvent.PassParameter>().Publish())

Both Modules.Orders and Modules.CustomerReference subscribe to this event.

So your problem is, when you use add a new Customer Reference, that global event tells your Module.Orders to reload the combo box containing the Customers and you don't want that behavior.

Is that correct? If so...


You should not need to 'reload' your combo boxes. That should be the job of data binding and implementing INotifyPropertyChanged on your view model. Check out the ViewModelBase base class included with Prism Library.

When SaveExecute(); is called on the CustomerForm, you add the Customer to the CustomerCollection. And it fires a global event.

In your OrderView, Your ComboBox should be bound to an ObservableCollection<Customer> defined in your OrderViewModel (**Or ObservableDictionary<int, string>, you shouldn't really store the entire Customer)

In your OrderViewModel in the global event handler you should:

  1. Receive the CustomerID (The payload from the event)
  2. Look up the Customer from the repository by it's ID.
  3. Add the Customer to the ObservableCollection<Customer>

How this solves your problem:

When you add a new CustomerReference (I'm assuming the Customer must already exist.) and your OrderViewModel receives the event, you can check if the Customer exists in the CustomerCollection, and if it does exist, the CustomerCollection is not changed, and your combo box will not 'reload'.

Also, When you add a new Customer, it's only asking for 1 customer by Id, instead of all customers to update your ComboBox.


UPDATE

Let's use the new simplified example provided from the comments and apply the same logic as above.

public class OrdersViewModel : ViewModelBase
{
    private IProductsDataProvider _dataProvider;
    private IEventAggregator _eventAggregator;
    private ObservableCollection<Product> _productsComboBox;
    public ObservableCollection<Product> ProductsComboBox 
    { 
        get { return _productsComboBox; }
        set { _productsComboBox = value; OnPropertyChanged(); }
    }

    public OrdersViewModel(IProductsDataProvider dataProvider, IEventAggregator eventAggregator) 
    {
        _dataProvider = dataProvider;
        _eventAggregator = eventAggregator;
        ComboBoxItems = new ObservableCollection<Product>();
        _eventAggregator.GetEvent<GlobalUIX.ProductCreatedEvent>().Subscribe(OnProductCreatedEventHandler);
    }

    public void Initialize() 
    {
        //Logic to load combo box with data from the database
        var products = _dataProvider.GetAllProducts();
        ProductsComboBox.AddRange(products);
    }

    private void OnProductCreatedEventHandler(int eventPayloadProductId) 
    {
        var isProductInComboBox = ProductsComboBox.SingleOrDefault(p => p.Id == eventPayloadProductId);

        if (isProductInComboBox == null) {      
            var existingProduct = _dataProvider.GetProductById(eventPayloadProductId);
            ProductsComboBox.Add(newProduct);
        }
    }
}

Order of Operations

  1. ViewModel is constructed when OrdersModule is instantiated.
  2. ViewModel takes in an IProductsDataProvider, which is this ViewModels gateway to the database in the Data Access layer.
  3. The ComboBox is initialized with an empty ObservableCollection();
  4. OrdersModule calls the Load() method to initialize the combo box from products from the database (Really, this should just be a Dictionary<int, string>() so you don't load the entire Product)
  5. _eventAggregator subscribes to the ProductCreatedEvent
  6. When ProductCreatedEvent fires, OnProductCreatedEventHandler is called..
  7. If the item is already listed in the combo box, do nothing.
  8. If the item is not listed in the combo box, add it.
  9. OnPropertyChanged(); is fired from the setter of ProductsComboBox
  10. INotifyPropertyChanged tells the UI to update.

Duplicate this exactly for UnitsOfMeasureModule.

Which will give you 2 separate modules, that will each have a combo box that is updated from a single event, but does not reload the entire combo box when the event is fired.

Upvotes: 1

Andy
Andy

Reputation: 12276

You could forget eventaggregator entirely.
Register your observablecollection as singleton with unity.
or
Put it in application.current.resources if that doesn't suit for some reason. One way or another, share the same observablecollection.
When you add in one window then it's the same collection bound in the other and you get the new customer there too.
or
You could add an event to whatever _customerrepository is so it says it's dirty to anything subscribing.
That then tells the subscriber to read the data again.

You have an awful lot of decoupling going on there.
If you don't really need it then the cost of the overhead created to make stuff work despite the decoupling is likely to exceeed the benefit. Of course you might really need it and then you have no option.

Upvotes: 0

Related Questions