Показ дочерних View в рамках патерна MVVM (часть 2)

Пару лет назад уже была статья "Показ дочерних View в рамках патерна MVVM", т.к. сейчас это делаем по другому, да и вопрос тут возник на тостере... Еще раз, в рамках паттерна предполагается что ViewModel (бизнес-логика) работает только с классами ViewModel и Model, а нам необходимо показать окно, т.е. кроме создания ViewModel для него, нужно создать еще и View. Как это сделать? Четвертый вариант под катом.

Пример будет максимально упрощен, но основные идеи постараюсь показать.
1. Создаем пустой WPF проект. В него добавляем класс окно вот с такой разметкой:

<Window x:Class="ChildWindowsDemo.ChildWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ChildWindowsDemo"
        mc:Ignorable="d"
        Title="{Binding Title}"SizeToContent="WidthAndHeight">
    <Grid>
        <ContentPresenter Content="{Binding }" />
    </Grid>
</Window>

В коде у него ничего не добавляем. Именно в этом окне будут показываться все дочерние ViewModel. Его состояние можно привязать к модели, например, здесь показано как привязать заголовок, но точно так же можно Visability или другие свойства (для свойств типа Visability можно через конвертор, а в можели хранить bool).
2. Добавляем класс базового ViewModel:

public class ViewModelBase : DependencyObject
{
    ///
    ///Окно в котором показывается текущий ViewModel
    ///
    private ChildWindow _wnd = null;

    ///
    /// Заголовок окна
    ///
    public string Title
    {
        get { return (string)GetValue(TitleProperty); }
        set { SetValue(TitleProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Title.  This enables animation, styling, binding, etc...
    public static readonly DependencyPropertyTitleProperty =
        DependencyProperty.Register("Title", typeof(string), typeof(ViewModelBase), new PropertyMetadata(""));       

    ///
    /// Методы вызываемый окном при закрытии
    ///
    protected virtual void Closed()
    {

    }

    ///
    /// Методы вызываемый для закрытия окна связанного с ViewModel
    ///
    public bool Close()
    {
        var result = false;
        if (_wnd != null)
        {
            _wnd.Close();
            _wnd = null;
            result = true;               
        }
        return result;
    }

    ///
    /// Метод показа ViewModel в окне
    ///
    /// viewModel">
    protected void Show(ViewModelBase viewModel)
    {
        viewModel._wnd = new ChildWindow();
        viewModel._wnd.DataContext = viewModel;
        viewModel._wnd.Closed += (sender, e) => Closed();
        viewModel._wnd.Show();
    }
}
Потомки этого класса могут передавать произвольного потомка этого класса в метод Show, чтобы показать его в отдельном окне.
3. Создаем View для демо, т.к. у нас всегда показывается в окне из пункта 1, то View делаем на основе UserControl:

<UserControl x:Class="ChildWindowsDemo.View.DemoView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:ChildWindowsDemo.View"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanelWidth="200">
        <DatePicker SelectedDate="{Binding Date}" />
        <Button Command="{BindingCloseCommand}">Закрыть</Button>
    </StackPanel>
</UserControl>
Наш дочерний ViewModel позволяет вводить дату и содержит кнопку для закрытия окна.
4. Демонстрационный ViewModel, потомок нашего ViewModelBase:

class DemoViewModel : ViewModelBase
{
    public DateTime Date
    {
        get { return (DateTime)GetValue(DateProperty); }
        set { SetValue(DateProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Date.  This enables animation, styling, binding, etc...
    public static readonly DependencyPropertyDateProperty =
        DependencyProperty.Register("Date", typeof(DateTime), typeof(DemoViewModel), new PropertyMetadata(null));

    public ICommandCloseCommand
    {
        get { return (ICommand)GetValue(CloseCommandProperty); }
        set { SetValue(CloseCommandProperty, value); }
    }

    // Using a DependencyProperty as the backing store for CloseCommand.  This enables animation, styling, binding, etc...
    public static readonly DependencyPropertyCloseCommandProperty =
        DependencyProperty.Register("CloseCommand", typeof(ICommand), typeof(DemoViewModel), new PropertyMetadata(null));

    public DemoViewModel()
    {
        CloseCommand = new SimpleCommand(() => Close());
    }
}

5. Главный ViewModel тоже является потомком BaseViewModel, в нем реализуем показ дочернего окна вызовом метода Show:

class MainViewModel : ViewModelBase
{
    public ICommandCreateChildCommand { get; set; }

    public MainViewModel()
    {
        CreateChildCommand = new SimpleCommand(CreateChild);
    }

    private void CreateChild()
    {
        var child = new DemoViewModel()
        {
            Title = "Дочернее окно",
            Date = DateTime.Now
        };
        Show(child);
    }
}

6. Ну и магия, в ресурсах приложения создаем связку между View и ViewModel:
<Application.Resources>
    <DataTemplateDataType="{x:Type viewmodel:DemoViewModel}">
        <view:DemoViewHorizontalAlignment="Stretch" />
    </DataTemplate>
</Application.Resources>

Все, можно запускать наше приложение. Клики по кнопке на главной форме показывают дочерние окна, ну а клик на кнопке в дочернем окне закрывает дочернее окно:
Полный код примера можно скачать здесь.