I have a Module called ModuleMenu. In this module I have a UserControl called MenuView and a Corresponding ViewModel called UserControlViewModel. I also have a class called Module. All the code is as given below:
MenuView.xmal
<UserControl ..............>
<ListBox ItemsSource="{Binding MenuItems, Converter={StaticResource dummy}}" DisplayMemberPath="MenuItemName" SelectedItem="{Binding SelectedMenuItem}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Margin" Value="10,0" />
</Style>
</ListBox.Resources>
</ListBox>
</UserControl>
MenuView.xaml.cs
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class MenuView : UserControl
{
public MenuView()
{
InitializeComponent();
}
}
UserControlViewModel.cs
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class MenuViewModel : ViewModelBase
{
IServiceFactory _ServiceFactory;
[ImportingConstructor]
public MenuViewModel(IServiceFactory serviceFactory)
{
_ServiceFactory = serviceFactory;
}
protected override void OnViewLoaded()
{
_MenuItems = new ObservableCollection<MenuItem>();
WithClient<IMenuItemService>(_ServiceFactory.CreateClient<IMenuItemService>(), menuItemClient =>
{
MenuItem[] menuItems = menuItemClient.GetAllParentMenuItemsWithChildren();
if (menuItems != null)
{
foreach (MenuItem menuItem in menuItems)
{
_MenuItems.Add(menuItem);
}
_SelectedMenuItem = _MenuItems[2];
}
});
}
private ObservableCollection<MenuItem> _MenuItems;
public ObservableCollection<MenuItem> MenuItems
{
get
{
return _MenuItems;
}
set
{
if (_MenuItems != value)
{
_MenuItems = value;
OnPropertyChanged(() => MenuItems, false);
}
}
}
private MenuItem _SelectedMenuItem;
public MenuItem SelectedMenuItem
{
get
{
return _SelectedMenuItem;
}
set
{
if (_SelectedMenuItem != value)
{
_SelectedMenuItem = value;
OnPropertyChanged(() => SelectedMenuItem);
}
}
}
}
Module.cs
[ModuleExport(typeof(Module), InitializationMode=InitializationMode.WhenAvailable)]
public class Module : IModule
{
IRegionManager _regionManager;
[ImportingConstructor]
public Module(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void Initialize()
{
_regionManager.Regions[RegionNames.MenubarRegion].Add(ServiceLocator.Current.GetInstance<MenuView>());
}
}
Now in my main Project I have a class called BootStrapper.cs as follows:
public class Bootstrapper : MefBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.GetExportedValue<Shell>();
}
protected override void InitializeShell()
{
base.InitializeShell();
App.Current.MainWindow = (Window)Shell;
App.Current.MainWindow.Show();
}
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Bootstrapper).Assembly));
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(ModuleMenu.Module).Assembly));
}
}
In my App.xaml:
<Application ..............>
<Application.Resources>
<DataTemplate DataType="{x:Type modMenu:MenuViewModel}">
<modMenu:MenuView />
</DataTemplate>
</Application.Resources>
</Application>
And finally in App.xaml.cs:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Bootstrapper bootstrapper = new Bootstrapper();
bootstrapper.Run();
}
}
When I run the application I get shell as expected. It shows me MenuView, but the data in MenuView is not loaded. I tried to debug it using a dummy converter and show that the viewModel is never initialized.
So, now my question is how can I initialize the viewModel?
update:
After trying your code I get exception as follows:
An exception has occurred while trying to add a view to region 'MenubarRegion'.
- The most likely causing exception was was:
'Microsoft.Practices.ServiceLocation.ActivationException: Activation
error occured while trying to get instance of type MenuView, key "" --->
Microsoft.Practices.ServiceLocation.ActivationException: Activation
error occured while trying to get instance of type MenuView, key ""
at Microsoft.Practices.Prism.MefExtensions.MefServiceLocatorAdapter
.DoGetInstance(Type serviceType, String key)
at Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase
.GetInstance(Type serviceType, String key)
--- End of inner exception stack trace ---
at Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase
.GetInstance(Type serviceType, String key)
at Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase
.GetInstance(Type serviceType)
at Microsoft.Practices.Prism.Regions.RegionViewRegistry
.CreateInstance(Type type)
at Microsoft.Practices.Prism.Regions.RegionViewRegistry
.<>c__DisplayClass1.<RegisterViewWithRegion>b__0()
at Microsoft.Practices.Prism.Regions.Behaviors
.AutoPopulateRegionBehavior
.OnViewRegistered(Object sender, ViewRegisteredEventArgs e)'.
But also check the InnerExceptions for more detail or call
.GetRootException().
When I take a look at inner exception, I get the following error message:
{"Activation error occured while trying to get instance of type MenuView, key \"\""}
update2:
Here is the Export of type ServiceFactory :
[Export(typeof(IServiceFactory))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ServiceFactory : IServiceFactory
{
public T CreateClient<T>() where T : IServiceContract
{
return ObjectBase.Container.GetExportedValue<T>();
}
}
You define a DataTemplate
as a view for your viewmodel, but don't actually use it.
There are many ways to solve your issue using Prism, please refer to this topic.
You could set the DataContext
property of your view in XAML:
<UserControl.DataContext>
<my:MyViewModel/>
</UserControl.DataContext>
You could create a viewmodel in the view's constructor:
public MyView()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
A better way would be to import the viewmodel via dependency injection:
[ImportingConstructor]
public MyView(MyViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
You could use the Prism's viewmodel location services:
<MyView prism:ViewModelLocator.AutoWireViewModel="True"/>
Last but not least: you could use a DataTemplate
too, but you should set a DataContext
of your objects to let WPF create the view for you, and don't create the view in code.
UPDATE:
So, you want to use the DataTemplate
feature. Well, that is not a 'Prism' way to do things, because in that case the view will be created by WPF, not by the Prism's IRegion
. But that's possible anyway.
I'll explain the difference: in all the other (Prism) methods, a view is the 'master', it will be created first, then an appropriate viewmodel will be created and attached to the view. In Prism, you define what views should be created, when (navigation) and where (regions). In the DataTemplate
method, the viewmodel (data) is the 'master', it will be created first, and WPF determines how to display them creating a view. So in this case, you can't use Prism's regions and navigation, because WPF handles all the things.
So I really suggest you to use any of the above methods for this, but not the DataTemplate
one.
You create your view as follows:
_regionManager.Regions[RegionNames.MenubarRegion].Add(ServiceLocator.Current.GetInstance<MenuView>());
I would suggest you to change that to:
_regionManager.RegisterViewWithRegion(RegionNames.MenubarRegion, typeof(MenuView));
Using ServiceLocator
directly is not a good pattern (unless you can't avoid it), so let Prism instantiate the view for you.
In your view's constructor, just add a dependency injection of the viewmodel, and set the view's DataContext
to it. Voilà! You've got it.
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class MenuView : UserControl
{
[ImportingConstructor]
public MenuView(MenuViewModel viewModel)
{
this.InitializeComponent();
this.DataContext = viewModel;
}
}
Using DataTemplate
, you can do it only in an 'old plain WPF way'. You have to create an instance of the viewmodel manually and expose it as a property of the parent (shell's) viewmodel (or make it static, which is a poor approach however).
<Window>
<ContentControl Content="{Binding MenuViewModelInstace}"/>
</Window>
WPF will then create the view for you to display the viewmodel, but you should define this directly in XAML markup, as I mentioned before. Prism can't help you here.