views:

1077

answers:

3

How do I close a View from its ViewModel?

I've a WPF window which has defined multiple Regions and being used as a Shell to host views for my application. I would like to have a View able to remove itself from the Region, or close it from a tabbed container. How can I accomplish this behavior from ViewModel.

+3  A: 

Since your ViewModel doesn't (and shouldn't) have a reference to the View, you can't close it directly. However, what you can do is add an Event in your ViewModel to indicate that it wants to be closed.

Josh Smith has written an article showing how to do this (about halfway through the article).

Julien Poulin
Hi thanks, I've been through that already. But Josh's article does not use CompositeWPF, so I am wondering how I could implement such an event handler coupled with Regions and Bootstrapper of Composite WPF.
Raj
You'd take that code and instead of window.Close() as your handler, you would use regionMananger.Regions["MyRegion"].Remove(window)
Anderson Imes
Good article, by the way.
Anderson Imes
+1  A: 

This really depends on your app architecture, but here's how I do it with Prism.

First I want to say, it is ok to have your VM reference the View just as long as it is not a concrete implementation of the View, ie, references by interface.

I marry the View and ViewModel using dependency injection, very similar to how it's done in the StockTraderRI. So I have an IView and an IViewModel. IViewModel has a propery called "View" of type IView.

From the code layer (for me, usually the controller...see StockTraderRI) that works with your regions, add the mechanism to remove your view from the region.

For example:

myRegion.Remove(myIViewModel.View);

If regions are handled by a controller, you may want to put a simple event on the VM to notify when a VM wants to be "closed". You can also experiment with the IEventAggregator if you wish to use a weak eventing model. If the region is handled in the VM, simply add that code there.

Jeremiah Morrill
Thanks. I think I got the logic. But I'm stuck where I resolve the controller interface from the Module. I get a stack overflow exception. LoginController controller = this.container.Resolve<ILoginController>(); throws an error. Any suggestions?
Raj
It sounds like your unity container has some circular dependencies going on and/or is incorrectly configured. I would follow all the constructors and make sure each injected parameter looks correct. If you can try pasting the container setup code and maybe a few classes constructors.
Jeremiah Morrill
Hi pls check the code I've pasted as Answer to this question. Thanks again.
Raj
Your suggestion worked. My View constructor had wrong parameters which was throwing Stack overflow exception. I fixed those and it works.
Raj
A: 

This how my Login module looks like:

    public class LoginModule : IModule
{
    private readonly IUnityContainer container;

    public LoginModule(IUnityContainer container)
    {
        this.container = container;
    }

    #region IModule Members

    public void Initialize()
    {
        this.container.RegisterType<ILoginController, LoginController>(new ContainerControlledLifetimeManager());
        this.container.RegisterType<ILoginView, LoginView>();
        this.container.RegisterType<ILoginViewModel, LoginViewModel>();

        ILoginController controller = this.container.Resolve<ILoginController>();
        controller.Run();
    }

    #endregion
}

This is the controller:

    public class LoginController : ILoginController
{
    private readonly IRegionManager regionManager;
    private readonly ILoginViewModel model;

    public LoginController(IRegionManager regionManager, ILoginViewModel model)
    {
        this.regionManager = regionManager;
        this.model = model;
        model.RequestClose += new EventHandler(model_RequestClose);
    }

    void model_RequestClose(object sender, EventArgs e)
    {
        regionManager.Regions["LoginRegion"].Remove(model.View);
    }

    #region ILoginController Members

    public void Run()
    {
        // Register views here
        regionManager.Regions["LoginRegion"].Add(model.view);
    }

    #endregion
}

And this is my ViewModel:

    public class LoginViewModel : ViewModelBase, ILoginViewModel
{
    IEventAggregator _eventAggregator;
    RelayCommand _loginCommand;
    private readonly UserProfileRepository _userProfileRepository;
    public event EventHandler RequestClose;

    public ICommand LoginCommand
    {
        get
        {
            if (_loginCommand == null)
            {
                _loginCommand = new RelayCommand(
                    param => this.Login(),
                    param => this.IsValid());
            }
            return _loginCommand;
        }
    }

    public LoginViewModel(IEventAggregator eventAggregator, UserProfileRepository userProfileRepository, ILoginView view)
    {
        this._eventAggregator = eventAggregator;
        this._userProfileRepository = userProfileRepository;
        this.View = view;
    }

    #region ILoginViewModel Members

    public ILoginView View { get; private set; }

    #endregion
}
Raj