Modal MVVM Dialog Using Locator

I am developing a WPF application that follows the MVVM pattern. To display modal dialogs, I try to follow what the following article suggests. http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern?fid=1541292&fr=26#xx0xx

But in such articles, as I noticed, the ShowDialog method of the DialogService interface is called from MainWindowViewModel.

The situation in my application is slightly different. MainWindow.xaml contains a ChildView user control that contains an Add button. MainWindowViewModel contains another ViewModel that says ChildVM, which is associated with ChildView. ChildVM contains AddCommand, and I need to display the Modal Dialog when the AddExecute method matches the call to AddCommand. How can i do this?

Edited Code

private Window FindOwnerWindow(object viewModel) { FrameworkElement view = null; // Windows and UserControls are registered as view. // So all the active windows and userControls are contained in views foreach (FrameworkElement viewIterator in views) { // Check whether the view is an Window // If the view is an window and dataContext of the window, matches // with the viewModel, then set view = viewIterator Window viewWindow = viewIterator as Window; if (null != viewWindow) { if (true == ReferenceEquals(viewWindow.DataContext, viewModel)) { view = viewWindow; break; } } else { // Check whether the view is an UserControl // If the view is an UserControl and Content of the userControl, matches // with the viewModel, then set view = userControl // In case the view is an user control, then find the Window that contains the // user control and set it as owner System.Windows.Controls.UserControl userControl = viewIterator as System.Windows.Controls.UserControl; if (null != userControl) { if (true == ReferenceEquals(userControl.Content, viewModel)) { view = userControl; break; } } } } if (view == null) { throw new ArgumentException("Viewmodel is not referenced by any registered View."); } // Get owner window Window owner = view as Window; if (owner == null) { owner = Window.GetWindow(view); } // Make sure owner window was found if (owner == null) { throw new InvalidOperationException("View is not contained within a Window."); } return owner; } 
+6
source share
2 answers

Well, if I understand correctly, you want to open a modal dialog not from MainWindowViewModel, but from another ChildViewModel?

Take a look at the MainWindowViewModel constructors of the CodeProject article that you linked:

ViewModel has a constructor with the following signature:

 public MainWindowViewModel( IDialogService dialogService, IPersonService personService, Func<IOpenFileDialog> openFileDialogFactory) 

This means that to build you need a service that shows modal dialogs, another service (personService), which does not matter here, and a factory for a specific dialog for opening openFileDialogFactory files.

To use the service, which is the main part of the article, a simple ServiceLocator is implemented and a default constructor is defined that uses the ServiceLocator to get service instances that the ViewModel requires:

 public MainWindowViewModel() : this( ServiceLocator.Resolve<IDialogService>(), ServiceLocator.Resolve<IPersonService>(), () => ServiceLocator.Resolve<IOpenFileDialog>()) {} 

This is possible because the ServiceLocator is static. In addition, you can set the local field for services in the constructor using ServiceLocator. This approach is better because it allows you to install services yourself if you do not want to use ServiceLocator.

You can do the same in your own ChildViewModel.

 public ChildViewModel(IDialogService dialogService) { _dialogService = dialogService; } 

Create a default constructor that calls the aforementioned constructor with a service instance permitted from ServiceLocator:

 public ChildViewModel() : this(ServiceLocator.Resolve<IDialogService>()) {} 

Now you can use the service from anywhere in the ChildViewModel, for example:

 _dialogService.ShowDialog<WhateverDialog>(this, vmForDialog); 

To find the owner window of your view, which is not the view itself, you need to change the FindOwnerWindow method of FindOwnerWindow to find the parent window of the view, rather than expecting the window to look like the view itself. You can use VisualTreeHelper for this:

 private Window FindOwnerWindow(object viewModel) { var view = views.SingleOrDefault(v => ReferenceEquals(v.DataContext, viewModel)); if (view == null) { throw new ArgumentException("Viewmodel is not referenced by any registered View."); } DependencyObject owner = view; // Iterate through parents until a window is found, // if the view is not a window itself while (!(owner is Window)) { owner = VisualTreeHelper.GetParent(owner); if (owner == null) throw new Exception("No window found owning the view."); } // Make sure owner window was found if (owner == null) { throw new InvalidOperationException("View is not contained within a Window."); } return (Window) owner; } 

You still need to register UserControl by setting the attached property to UserControl:

 <UserControl x:Class="ChildView" ... Service:DialogService.IsRegisteredView="True"> ... </UserControl> 

As far as I can tell, this works.

Additional Information:

To accomplish the same, I use the PRISM framework, which comes with many features for this decoupling, Inversion of Control (IoC) and dependency injection (DI). Maybe it's worth a look at this for you too.

Hope this helps!

Edited to review the comment.

+4
source

See if you like this idea ... I use Castle Windsor and Prism, so your mileage may vary, but the concepts should be the same with other MVVMs and IoCs.

You start with your MainViewModel.cs that wants to open a modal dialog

 var view = ServiceLocator.Current.GetInstance<SomeDialogView>(); view.ShowDialog(); 

but of course it does not respect what you set in MainView.xaml

 WindowStartupLocation="CenterOwner" 

get out!

But wait, could a ServiceLocator give me a MainView?

 var view = ServiceLocator.Current.GetInstance<SomeDialogView>(); view.Owner = ServiceLocator.Current.GetInstance<MainView>(); // sure, why not? view.ShowDialog(); 

This line throws an exception with my IoC configuration, since my IoC registers views with a "temporary lifetime". In Castle Windsor, this means that each request is provided with a new instance, and I need an instance of MainView, and not a new one that has not been shown.

But just changing the registration from each view, which is "transitional"

 container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn") .InNamespace("WhateverNamespaceTheyreIn") .LifestyleTransient()); 

to be a little more discriminatory using the free Unless () and If ()

 container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn") .InNamespace("WhateverNamespaceTheyreIn") .Unless(type => type == typeof(MainView)) .LifestyleTransient()); // all as before but MainView. container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn") .InNamespace("WhateverNamespaceTheyreIn") .If(type => type == typeof(MainView)) .LifestyleSingleton()); // set MainView to singleton! 

provided by MainView - the one we need!

NTN

0
source

All Articles