Захотелось мне тут сделать Wizard UserControl, да так чтобы к нему можно было добавлять страницы во время инициализации. Те клиентский код будет создавать мой Wizard, инициализировать его своими страницами – читай usercontrol – и работать с ним.
Сначала я написал Wizard так, что все страницы были реализованы в том же проекте что и Wizard и привязаны статически к нему. При этом я, как и всякий православный WPF программист, строго придерживался MVVM. Я создал UserControl WizardView, который содержит стили, DataTemplates и прочее. WizardView агрегирует экземпляр WizardViewModel, который содержит список страниц. Схематически дизайн изображен на диаграмме ниже:
У класса WizardViewModel есть метод CurrentPage, который возвращает PageViewModelBase. По нажатию кнопки Next происходит смена CurrentPage на следующую в списке. Возникает дилемма – как сделать так, чтобы как только сменилась страница, те PageViewModel, менялось и View. Те как сделать отображение PageViewModel <->PageView.
Самый простой метод, при котором не нужно писать ни строчки кода на C#, это использовать DataType DataTemplate:
<DataTemplate DataType="{x:Type viewModel:SomePageViewModel}">
DataTemplate>
Прописываем этот код для каждой страницы в Wizard и – все магическим образом работает.
Но что если страницы приходят из клиентского кода, то что делать? Самое очевидное и простое решение добавить в код конструктора WizardView добавление соответствующих шаблонов к ресурсам.
Для начала напишем функцию, которая будет создавать DataTemplate нужного вида и добавлять его в Window.Resources:
///
/// Add DataType DataTemplates to the Resources.
/// It provides mapping between Vew and ViewModel.
/// Note that it must be called before the call of method InitializeComponent.
///
private void AddTemplateToResources(Type pageView, Type pageViewModel)
{
//generate the following xaml code:
//
// < pageView />
//
FrameworkElementFactory fef = new FrameworkElementFactory(pageView);
DataTemplate dataTemplate = new DataTemplate();
dataTemplate.DataType = pageViewModel;
dataTemplate.VisualTree = fef;
DataTemplateKey dtKey = new DataTemplateKey(pageViewModel);
this.Resources.Add(dtKey, dataTemplate);
}
Fef - это содержимое DataTempalate, dtKey - это ключ для ResourceDictionary, так как Window.Resource это именно этот класс.
Далее напишем такой код в конструкторе:
public partial class WizardDialog : Window
{
readonly WizardViewModel m_wizardViewModel;
public WizardDialog(IEnumerable<WizardPage> wizardPages)
{
foreach (var page in wizardPages)
{
AddTemplateToResources(page.View.GetType(),
page.ViewModel.GetType());
}
InitializeComponent();
…
}
…
Важно чтобы ресурсы добавлялись до вызова InitializeComponent.