【问题标题】:How to initialize viewModel using MEF?如何使用 MEF 初始化 viewModel?
【发布时间】:2015-02-16 08:03:57
【问题描述】:

我有一个名为 ModuleMenu 的模块。在这个模块中,我有一个名为 MenuView 的 UserControl 和一个名为 UserControlViewModel 的对应 ViewModel。我还有一个名为 Module 的类。所有代码如下:

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);
            }
        }
    }

}

模块.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>());
    }
}

现在在我的主项目中,我有一个名为 BootStrapper.cs 的类,如下所示:

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));
    }
}

在我的 App.xaml 中:

<Application ..............>
    <Application.Resources>
        <DataTemplate DataType="{x:Type modMenu:MenuViewModel}">
            <modMenu:MenuView />
        </DataTemplate>
    </Application.Resources>
</Application>

最后在 App.xaml.cs 中:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        Bootstrapper bootstrapper = new Bootstrapper();
        bootstrapper.Run();

    }
}

当我运行应用程序时,我得到了预期的 shell。它向我显示 MenuView,但未加载 MenuView 中的数据。我尝试使用虚拟转换器对其进行调试,并显示 viewModel 从未初始化。

那么,现在我的问题是如何初始化 viewModel?

更新:

尝试您的代码后,我得到如下异常:

尝试向区域“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().

当我查看内部异常时,我收到以下错误消息:

{"Activation error occured while trying to get instance of type MenuView, key \"\""}

更新2:

这里是 ServiceFactory 类型的导出:

[Export(typeof(IServiceFactory))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ServiceFactory : IServiceFactory
{
    public T CreateClient<T>() where T : IServiceContract
    {
        return ObjectBase.Container.GetExportedValue<T>();
    }
}

【问题讨论】:

    标签: c# wpf xaml prism mef


    【解决方案1】:

    您将DataTemplate 定义为视图模型的视图,但实际上并未使用它。

    使用 Prism 有很多方法可以解决您的问题,请参考this topic

    您可以在 XAML 中设置视图的 DataContext 属性:

    <UserControl.DataContext>
        <my:MyViewModel/>
    </UserControl.DataContext>
    

    您可以在视图的构造函数中创建视图模型:

    public MyView()
    {
        InitializeComponent();
        this.DataContext = new MyViewModel();
    }
    

    更好的方法是通过依赖注入导入视图模型:

    [ImportingConstructor]
    public MyView(MyViewModel viewModel)
    {
        InitializeComponent();
        this.DataContext = viewModel;
    }
    

    您可以使用 Prism 的视图模型定位服务:

    <MyView prism:ViewModelLocator.AutoWireViewModel="True"/>
    

    最后但同样重要的是:您也可以使用 DataTemplate,但您应该为您的对象设置一个 DataContext 以让 WPF 为您创建视图,而不是在代码中创建视图。


    更新: 因此,您想使用DataTemplate 功能。好吧,这不是“Prism”的做事方式,因为在这种情况下,视图将由 WPF 创建,而不是由 Prism 的IRegion 创建。但无论如何,这是可能的。

    我将解释差异:在所有其他(Prism)方法中,视图是“主”,它将首先创建,然后将创建适当的视图模型并将其附加到视图。在 Prism 中,您可以定义应创建哪些视图、何时(导航)和何处(区域)。在DataTemplate 方法中,视图模型(数据)是“主”,它将首先创建,WPF 确定如何在创建视图时显示它们。所以在这种情况下,您不能使用 Prism 的区域和导航,因为 WPF 处理所有事情。

    所以我真的建议你使用上述任何一种方法,但不要使用DataTemplate 一种。

    按如下方式创建视图:

    _regionManager.Regions[RegionNames.MenubarRegion].Add(ServiceLocator.Current.GetInstance<MenuView>());
    

    我建议您将其更改为:

    _regionManager.RegisterViewWithRegion(RegionNames.MenubarRegion, typeof(MenuView));
    

    直接使用ServiceLocator不是一个好的模式(除非你无法避免),所以让Prism为你实例化视图。

    在视图的构造函数中,只需添加视图模型的依赖注入,并将视图的DataContext 设置为它。瞧!你明白了。

    [Export]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public partial class MenuView : UserControl
    {
        [ImportingConstructor]
        public MenuView(MenuViewModel viewModel)
        {
            this.InitializeComponent();
            this.DataContext = viewModel;
        }
    }
    

    使用DataTemplate,您只能以“旧的普通 WPF 方式”进行操作。您必须手动创建视图模型的实例并将其公开为父(shell)视图模型的属性(或使其成为静态,但这是一种糟糕的方法)。

    <Window>
      <ContentControl Content="{Binding MenuViewModelInstace}"/>
    </Window>
    

    然后,WPF 将为您创建视图以显示视图模型,但您应该直接在 XAML 标记中定义它,正如我之前提到的。 Prism 在这里帮不了你。

    【讨论】:

    • 感谢您的出色回答。你能扩展你最后一点的解释(使用DataTemplate)????
    • 抱歉,回复晚了,因为我没有收到关于您回答中的更新的通知。现在,我已尝试实现您的代码,现在我收到了问题的更新部分中提到的错误。
    • @Vishal,将一些复制链接发布到外部存储不是一个好习惯。关于错误:请确保您的所有依赖注入都可以解决。异常状态不能实例化视图。这是因为 Prism 无法解析您的 viewmodel 依赖注入,这可能是由于 viewmodel 本身无法实例化造成的。您确定有一些导出满足IServiceFactory 导入吗?
    • 我很抱歉,但我对依赖注入、MEF 和 Prism 非常陌生。所以,我问你这个问题。而且我认为我有一个满足 IServiceFactory 导入的导出。请查看有问题的 Update2 以了解更多详细信息。
    • @Vishal,我查看了您的“示例”项目。这似乎不是一个示例,而是您的完整解决方案。此外,它甚至无法编译,因为缺少引用的项目。请注意,没有人会为了好玩而研究您的整个(不可编译)代码。我建议您创建一个小项目来重现您的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-22
    • 1970-01-01
    • 1970-01-01
    • 2021-12-19
    相关资源
    最近更新 更多