Prism2/MVVM Close View from ViewModel - wpf

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.

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).

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.

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
}

Related

OnNavigatedTo is not called in TabControl when selecting tab when using PRISM regions

I have registered my views for the TabControl with Region manager and views are shown properly when tab is selected.
The problem is that when I select new tab item OnNavigatedTo is not called for that view or its view model.
I'm using PRISM 6.3
UPDATE
ViewModel
`public class ValuationViewModel : IViewModel, INavigationAware
{
private IRegionManager _regionManager;
public string Title { get; set; }
public ValuationViewModel(IRegionManager regionManager)
{
Title = "PERFORM VALUATION";
_regionManager = regionManager;
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}`
View
`public partial class ValuationView : UserControl, IView
{
private IRegionManager _regionManager;
public ValuationView(ValuationViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
}
public IViewModel ViewModel
{
get
{
return (IViewModel)DataContext;
}
set
{
DataContext = value;
}
}
}`
Without code, nobody can give you the correct answer.
Its probably the best, if you show us your ViewModel for your "TabItem" View.
Assuming you registered your view and set your ViewModel in DataContext correctly, it could be possible that forget just a simple thing.
To manage your problem make sure you implemented the following things correctly:
Create a region for your TabControl
Register your view in that region
Make sure the DataContext is correctly set to your ViewModel
Make sure your ViewModel implemented INavigationAware
Update 1:
After testing a lot I found a simple answer unfortunately:
Members of INavigationAware (OnNavigatedTo, IsNavigationTarget & OnNavigatedFrom) are called when the NavigationService is navigating.
They aren't if you click on the TabItemHeader.
To solve your problem you have several options.
One option is to start a navigation request when the user click on the TabItemHeader ( bad approach).
In my opinion you should use the IActiveAware Interface ( https://msdn.microsoft.com/en-us/library/microsoft.practices.prism.iactiveaware(v=pandp.50).aspx).
It will solve your problem, because the navigation via RegionManager and the clicking on the TabItemHeader results in the same: INavigationAware.IsActive = true.
Now you are able to detect when your tab is shown or not and react.

Calling a method of a UserControl somewhere in MVVM

I have the following scenario:
I have a user control, let's say UserControl.xaml
In the code behind of this control I have the method DoSomething()
I have viewmodel for this control UserControlViewModel.cs
I need to call usercontrol's DoSomething() method somewhere. Any ideas how to accomplish this?
Thanks!
If I really had to do this, then using the DataContextChanged event may help.
Here's a solution with hopefully minimal coupling between the view and the view-model.
public partial class MainWindow : IMainWindow
{
public MainWindow()
{
this.DataContextChanged += this.MainWindowDataContextChanged;
this.InitializeComponent();
}
private void MainWindowDataContextChanged(object sender,
DependencyPropertyChangedEventArgs e)
{
var vm = this.DataContext as IMainWindowViewModel;
if (vm != null)
{
vm.View = this;
}
}
public void DoSomething()
{
Debug.WriteLine("Do something in the view");
}
}
public interface IMainWindow
{
void DoSomething();
}
public class MainWindowViewModel : IMainWindowViewModel
{
public MainWindowViewModel()
{
this.DoSomethingCommand = new RelayCommand(this.DoSomething);
}
public ICommand DoSomethingCommand { get; set; }
private void DoSomething()
{
Debug.WriteLine("Do something in the view model");
var view = this.View;
if (view != null)
{
view.DoSomething();
}
}
public IMainWindow View { get; set; }
}
public interface IMainWindowViewModel
{
IMainWindow View { get; set; }
}
You really should be using an MVVM framework if you're doing MVVM. A framework would provide a mechanism from which you can invoke a verb (method) on your view model from your view. Caliburn.Micro for example provides Actions.
It sounds as though your application is incorrectly structured.
What does
DoSomething()
do, that isn't reacting to a change in a bound property of the ViewModel?
If you really need to trigger something in the code behind of the View from the ViewModel, use a messaging handler such as the one in the Galasoft MVVMLight framework.

MVVM Light pass parameters to child view model

I am new to MVVM and WPF.
I am using MVVM Light to make an application which contains a DataGrid within a window, which has a view model (MainViewModel) and another window for adding and editing records in the DataGrid, that also has its own view model (EditViewModel).
What I am worried about is the approach I am using to open the Add/Edit window from the MainViewModel. In the MainViewModel I have a property SelectedItem, which is bound to the SelectedItem property of the DataGrid and an IsEdit boolean property that indicates if the Add/Edit window should be launched in Add or Edit mode.
When the Add/Edit window gets opened in edit mode, in the constructor of its view model I have the following line:
MainViewModel mainViewModel = ServiceLocator.Current.GetInstance<MainViewModel>();
That obviously retrieves the current instance of the MainViewModel, which works perfectly fine, but I am not really sure it is the best way to do this.
Also if I have more than one instances of the Main window, that use the same MainViewModel instance and I open an instance of the Add/Edit window from both of them, the Add/Edit windows are going to get data from the same instance of the MainViewModel which may be a problem.
If I try to create a new instance of MainViewModel for each MainWindow I open, then I don't know how to pass the instance of the currently used MainViewModel to the EditViewModel.
I hope I made clear what I need to do. Tell me if I have missed something and I will add it:)
Thanks in advance
Hi if I havent misunderstood your problem incorrect you can do it this way:
Since i need IsRequired dependency Property in both MainView and EditView i created a class that extends Window class
public class ExtendedWindow:Window
{
public static readonly DependencyProperty IsRequiredProperty = DependencyProperty.Register("IsRequired", typeof(bool), typeof(ExtendedWindow));
public bool IsRequired
{
get { return (bool)GetValue(IsRequiredProperty); }
set { SetValue(IsRequiredProperty, value); }
}
}
MainView and ViewModel
public partial class MainWindow:ExtendedWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
EditView editView = new EditView();
**((EditViewModel)editView.DataContext).IsRequired = this.IsRequired;**
editView.Show();
}
}
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
IsRequired = true;
}
private bool isRequired;
public bool IsRequired
{
get { return isRequired; }
set { isRequired = value; Notify("IsRequired"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
EditView and ViewModel
public partial class EditView:ExtendedWindow
{
public EditView()
{
InitializeComponent();
DataContext = new EditViewModel();
}
}
public class EditViewModel : INotifyPropertyChanged
{
private bool isRequired;
public bool IsRequired
{
get { return isRequired; }
set { isRequired = value; Notify("IsRequired"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
This is just kind of dummy but can give you idea how you can do it. I have tried it in dummy and its working fine.

Prism - How to import IRegionManager in ViewModel using MEF

How do we inject IRegionManager in the ViewModel using MEF Container. I have to switch view in my ViewModel's Command delegate. Here is the brief description of what I am doing. I have an entity called Product whose list is displayed in one View (ProductListView). In that view the user can select the Product and click on Edit button. This would switch the view and present a new View(ProductEditView). For activating a different view, I would need a reference to IRegionManager something like this
public class ProductListVM : NotificationObject { //The Product List View Model
[Import]
public IRegionManager RegionManager { get; set; }
private void EditProduct() { //EditCommand fired from ProductListView
IRegion mainContentRegion = RegionManager.Regions["MainRegion"];
//Switch the View in "MainContent" region.
....
}
}
The above code fails with NullReferenceException for RegionManager. This seems logical because the above View Model is constructed by WPF through DataContext property in Xaml and DI doesn't come into play, so it doesn't get a chance to import the RegionManager instance. How do we resolve the IRegionManager in this scenario.
The Container instance can be exported in the bootstrapper using following
container.ComposeExportedValue<CompositionContainer>(container);
Then in the viewmodel, the IRegionManager instance can be imported using the code
IServiceLocator serviceLocator = ServiceLocator.Current;
CompositionContainer container = serviceLocator.GetInstance<CompositionContainer>();
RegionManager = container.GetExportedValue<IRegionManager>();
However, referring a View in ViewModel is a violation of the MVVM pattern. But since I was following an article here to learn Prism , I had to get along the same. Also the article was in Silverlight and I had to find a way to import RegionManager in wpf, which is little different.
regards,
Nirvan.
Try using [ImportingConstructor] like this:
public class ProductView : Window
{
private IProductViewModel viewModel;
[ImportingConstructor]
public ProductView(IProductViewModel ViewModel)
{
this.viewModel = ViewModel;
this.DataContext = this.viewModel;
}
}
public class ProductViewModel: IProductViewModel, INotifyPropertyChanged
{
private IRegionManager regionManager;
private ICommand editUserCommand;
[ImportingConstructor]
public ProductViewModel(IRegionManager InsertedRegionManager)
{
this.regionManager = InsertedRegionManager;
editUserCommand = new DelegateCommand(ExecuteEditUserCommand, CanExecuteEditUserCommand);
}
public ICommand EditUserCommand
{
get {return this.editUserCommnad;}
}
private bool CanExecuteEditUserCommand()
{
return true;
}
private void ExecuteEditUserCommand()
{
this.regionManager......
}
}

Broken binding with Prism, Silverlight and ViewFirst approach

The problem we are having is that we cannot get binding to work in our
prism silverlight application when using the view-model first
approach. The view first approach work fine. We have gone over the
official documentation and various web sites, but have still not
resolved the issue. Below is the code for both the view-model first,
and the view first approach. Are we missing something? Read about it on my blog http://silvercasts.blogspot.com
View-Model first approach:
Bootstrapper:
internal void RegisterLoginRegionAndView()
{
IRegionManager regionManager = Container.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion(ShellRegionNames.MainRegion,
() => Container.Resolve<IViewModel>().View);
}
ViewModel:
public ViewModel(IView view)
{
View = view;
View.SetModel(this);
User = new User();
User.Username = "TestUser";
}
ViewModel Interface:
public interface IViewModel
{
IView View { get; set; }
}
View Interface:
public interface IView
{
void SetModel(IViewModel model);
}
View Xaml:
<TextBox x:Name="Username" TextWrapping="Wrap" Text="{Binding User.Username}" />
View Code Behind:
public void SetModel(IViewModel viewModel)
{
this.DataContext = viewModel;
}
View first approach
Bootstrapper:
regionManager.RegisterViewWithRegion(ShellRegionNames.MainRegion, typeof(IView));
ViewModel:
public ViewModel()
{
User = new User();
User.Username = "TestUser";
}
View Code Behind:
public View(IViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
Your implementation of SetModel on your view needs to be as follows:
public void MyUserControl : UserControl, IView
{
//...
public void SetModel(IViewModel vm)
{
this.DataContext = vm;
}
}
If that's not there, it needs to be (you haven't posted your implementation of SetModel, but this would be the source of the issue in this case).
If this is not the issue, it's likely because your ViewModel does not implement INotifyPropertyChanged. I usually use a base ViewModel that does this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And then all of my ViewModels derive from that:
public class MyViewModel : ViewModelBase
{
private User _user;
public User User
{
get { return _user; }
set
{
_user = value;
OnPropertyChanged("User");
}
}
}
Note: in your case the "User" object should probably also be a ViewModel and also raise OnPropertyChanged for the Username property.
Hope this helps.
The obvious difference to me is that you set the DataContext in the "view first" approach, but not in the "view model first" approach. I'm not sure if Prism sets the DataContext for you (I'd guess that you're assuming that it does) but try setting the DataContext manually to see if this is the problem. In your ViewModel constructor you call View.SetModel(this) - does that call set the DataContext?
The problem was that I was using the SetModel method before the data object was instanced. Moving it like this:
public ViewModel(IView view)
{
View = view;
User = new User();
User.Username = "TestUser";
View.SetModel(this);
}
solved the problem.

Resources