Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVVM Modal Dialog using Service Locator

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

But in these kind of articles, What I have observed, that ShowDialog method of DialogService interface is called from MainWindowViewModel.

Situation in my application is slightly different. MainWindow.xaml contains a user control say ChildView that contains a button Add. MainWindowViewModel contains another ViewModel say ChildVM that is bind with ChildView. ChildVM contains AddCommand, and I need to display Modal Dialog when AddExecute method corresponding to AddCommand is called. How can I accomplish that?

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;
        }
like image 286
Anirban Paul Avatar asked Mar 05 '13 10:03

Anirban Paul


1 Answers

Ok, if I get you right, you want to open the modal dialog not from the MainWindowViewModel, but from a different ChildViewModel?

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

The ViewModel has a constructor with the following signature:

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

This means that for construction you need the service which shows the modal dialogs, another service (personService), which doesn't matter here and a factory for the specific dialog to open files, openFileDialogFactory.

In order to use the service, which is the core part of the article, a simple ServiceLocator is implemented and a default constructor is defined, which uses the ServiceLocator to get instances of the services that the ViewModel needs:

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

This is possible, because ServiceLocator is static. Alternatively, you could set the local field for the services in the constructor, using the ServiceLocator. The above approach is better, because it allows you to set the services yourself, if you don't want to use the ServiceLocator.

You can do exactly the same in your own ChildViewModel.

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

Create an default constructor, which calls the above constructor with the service instance resolved from the ServiceLocator:

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

And now you can use the service from anywhere in your ChildViewModel like this:

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

In order to find the owner window of your view, which is not a view itself, you need to modify the FindOwnerWindow method of the DialogService to find the parent window of the view, rather than expecting a Window as the view itself. You can use the VisualTreeHelper to do so:

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 the UserControl though, setting the attached property on the UserControl:

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

As far as I can tell, this works.

Additional information:

To accomplish the same thing, I use the PRISM framework, which comes with a lot of functionality for exactly this kind of decoupling, Inversion of Control (IoC) and dependency injection (DI). Maybe it is worth it to have a look at it for you, too.

Hope this helps!

Edited to consider the comment.

like image 150
Marc Avatar answered Nov 06 '22 17:11

Marc