Sabyasachi Mukherjee
Sabyasachi Mukherjee

Reputation: 466

Using MEF with Screen Conductor of Caliburn Micro

(TLDR version is below.) I am designing a WPF application with MEF as the IoC and Caliburn Micro as the framework. The Main Window of the application is like this:

Main Window

Here is the Viewmodel for the window:

[Export(typeof(MainViewModel))]
class MainViewModel : Conductor<PropertyChangedBase>, IHandle<ViewModelType>
{
    private readonly IEventAggregator _eventAggregator;

    private IEnumerable<Screen> _screenList { get; set; }

    [ImportingConstructor]
    public MainViewModel(IEventAggregator eventAggregator, [ImportMany]IEnumerable<Screen> screenList)
    {
        _screenList = screenList;
        _eventAggregator = eventAggregator;                    
        _eventAggregator.Subscribe(this);
        ShowMenu();
    }

    public void Handle(ViewModelType message)
    {           
        ActivateItem(_screenList.FirstOrDefault(c => c.GetType() == message.VMtype));
        DisplayName = "B.I. Surgical & Dressing - " + (ActiveItem as Screen)?.DisplayName;
        NotifyOfPropertyChange(() => CanShowMenu);
    }

    public void ShowMenu() => _eventAggregator.PublishOnUIThread(new ViewModelType(typeof(Menu.MenuViewModel)));
    public bool CanShowMenu => ActiveItem.GetType() != typeof(Menu.MenuViewModel);
}

So, _screenList contains all the screens that needs to be displayed, and here's the MenuViewModel which publishes an event indicating the ViewModel to be displayed:

[Export(typeof(MenuViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared), Export(typeof(Screen))]
class MenuViewModel : Screen
{
    private readonly IEventAggregator _eventAggregator;        

    [ImportingConstructor]
    public MenuViewModel(IEventAggregator eventAggregator)
    {
        _eventAggregator = eventAggregator;
        DisplayName = "Menu";
    }

    public void CreateInvoice() => _eventAggregator.PublishOnUIThread(new ViewModelType(typeof(Invoice.InvoiceViewModel)));
    public void EnterPaymentsReceived() => _eventAggregator.PublishOnUIThread(new ViewModelType(typeof(ReceivedPayments.PaymentsReceivedViewModel)));
    public void EnterPurchases() => _eventAggregator.PublishOnUIThread(new ViewModelType(typeof(PurchaseDetails.PurchaseDetailsViewModel)));
    public void AddClientAndRates() => _eventAggregator.PublishOnUIThread(new ViewModelType(typeof(AddClient.AddClientViewModel)));
    public void EditClientAndRates() => _eventAggregator.PublishOnUIThread(new ViewModelType(typeof(EditClient.EditClientViewModel)));
    public void AddItem() => _eventAggregator.PublishOnUIThread(new ViewModelType(typeof(AddItem.AddItemViewModel)));
    public void EditItems() => _eventAggregator.PublishOnUIThread(new ViewModelType(typeof(EditItem.EditItemViewModel)));

}

But, the problem I am facing is that, instead of getting a new VM when I click a Button in the MenuViewModel, I am getting a single instance again and again, which is actually expected, because this line finds the single instance of the ViewModel every time: ActivateItem(_screenList.FirstOrDefault(c => c.GetType() == message.VMtype));

But I need to dispose of the ViewModel whenever a view is deactivated (I guess I have to do that using the OnDeactivate method of Screen class). But I don't know how to get a new instance of a ViewModel every time from _screenList. My idea is to create some kind of factory, but I have no idea how to implement that using MEF, and how to dispose of a ViewModel whenever the view is deactivated.

SHORT VERSION: -TLDR-

In MEF, I can have something like this:

private IEnumerable<Screen> _screenList { get; set; }

[ImportingConstructor]
public MainViewModel(IEventAggregator eventAggregator, [ImportMany]IEnumerable<Screen> screenList)
{
    _screenList = screenList;
}

This will import all components marked with [Export(typeof(Screen))] attribute. However, each of those components are also marked with some other attribute, like [Export(typeof(ViewModelX))]. Basically, Screen is the base class from which each of the ViewModels are derived.

In my application, I am using _screenList like so: ActivateItem(_screenList.FirstOrDefault(c => c.GetType() == typeof(ViewModelX)));

However, in my problem, I want _screenList to return a new instance of a ViewModelX every time. How can I do that?

Upvotes: 1

Views: 323

Answers (1)

Sabyasachi Mukherjee
Sabyasachi Mukherjee

Reputation: 466

Finally, found a solution to the problem. Using the ExportFactory solved the problem.

The implementation is as follows:

private IEnumerable<ExportFactory<Screen>> _screenList { get; set; }

[ImportingConstructor]
public MainViewModel(IEventAggregator eventAggregator, [ImportMany] IEnumerable<ExportFactory<Screen>> screenList)
{            
    _screenList = screenList;
    _eventAggregator = eventAggregator;                    
    _eventAggregator.Subscribe(this);
    ShowMenu();
}

public void Handle(ViewModelType message)
{
    ActivateItem(_screenList.FirstOrDefault(c => c.CreateExport().Value.GetType() == message.VMtype).CreateExport().Value);
}

Upvotes: 1

Related Questions