首页 > 解决方案 > 多次实例化 MVVM ViewModel 构造函数

问题描述

有人介意帮助我解决 WPF MVVM 理解问题吗?我用 caliburn.micro 作为 MVVM 框架构建了一个 MVVM 项目。请多多包涵,因为这是我第一次创建这种项目。网格应该显示一种主屏幕(HomeViewModel)。因此,它的绑定已添加到内容控件中。有人可以帮忙告诉我为什么我的 HomeViewModel 的构造函数被实例化了 3 次吗?另一方面,如果有人可以解释如何实现 ViewModel 的方法,我会很好吗?所有这些,包括日志记录,都被执行了 3 次。

<Window [...]>
<Window.DataContext>
    <viewModels:ShellViewModel />
</Window.DataContext>
<Grid>
    <Grid.Resources>
        <DataTemplate DataType="{x:Type viewModels:HomeViewModel}">
            <local:HomeView DataContext="{Binding}" />
        </DataTemplate>
    </Grid.Resources>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="0.5*"/>
        <ColumnDefinition Width="0.5*"/>
    </Grid.ColumnDefinitions>
    <ContentControl Grid.Column="1">
        <ContentControl.Content>
            <Binding FallbackValue="{x:Null}"
                     Mode="OneWay"
                     Path="viewModels:HomeViewItem"
                     RelativeSource="{RelativeSource Self}" />
        </ContentControl.Content>
    </ContentControl>
</Grid>

我根据引导程序中的 caliburn.micro 文档实现了一个 SimpleContainer:

    public class Bootstrapper : BootstrapperBase
{
    private SimpleContainer _container;

    public Bootstrapper()
    {
        Initialize();
    }

    protected override void Configure()
    {
        _container = new SimpleContainer();

        _container.Singleton<IWindowManager, WindowManager>();

        // Registering ViewModels
        GetType().Assembly.GetTypes()
            .Where(type => type.IsClass)
            .Where(type => type.Name.EndsWith("ViewModel"))
            .Where(type => !(String.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace.EndsWith("ViewModels"))
            .ToList()
            .ForEach(viewModelType => _container.RegisterSingleton(
                viewModelType, viewModelType.ToString(), viewModelType));

        // Registering Views
        GetType().Assembly.GetTypes()
            .Where(type => type.IsClass)
            .Where(type => type.Name.EndsWith("View"))
            .Where(type => !(String.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace.EndsWith("Views"))
            .ToList()
            .ForEach(viewModelType => _container.RegisterSingleton(
                viewModelType, viewModelType.ToString(), viewModelType));

    }

    protected override void OnStartup(object sender, StartupEventArgs e)
    {
        DisplayRootViewFor<ShellViewModel>();
    }

    protected override object GetInstance(Type service, string key)
    {
        return _container.GetInstance(service, key);
    }

    protected override IEnumerable<object> GetAllInstances(Type service)
    {
        return _container.GetAllInstances(service);
    }

    protected override void BuildUp(object instance)
    {
        _container.BuildUp(instance);
    }

我的 HomeViewModel 是这样实现的:

    public class HomeViewModel : Conductor<IScreen>
    {
        public HomeViewModel()
        {
            Debug.WriteLine("Hello from HomeVM");
        }
    }

我的 ShellViewModel 是这样的:

    public class ShellViewModel : Screen
{
    private IScreen _homeViewItem;

    public ShellViewModel()
    {
        CreateSourceItem();
    }


    public IScreen HomeViewItem
    {
    get => _homeViewItem;
    set
        {
            if (Equals(value, _homeViewItem))
                return;
            _homeViewItem = value;
            NotifyOfPropertyChange(() => HomeViewItem);
        }
    }

    private void CreateSourceItem() 
    {
        HomeViewItem = new HomeViewModel();
    }
}

当应用程序启动时,调试输出中有 3 个 HomeViewModel 实现条目:

Hello from HomeVM
Hello from HomeVM
Hello from HomeVM

如果多次调用构造函数,容器内的 Singleton DI 应该返回现有实例,不是吗?顺便说一句: StartupUri 已从 app.xaml 中删除。提前致谢!

标签: c#wpfmvvmconstructorcaliburn.micro

解决方案


此标记调用 ShellViewModel 的构造函数

<Window.DataContext>
    <viewModels:ShellViewModel />
</Window.DataContext>

它通过调用 HomeViewModel 的构造函数

public ShellViewModel()
{
    CreateSourceItem();
}
private void CreateSourceItem() 
{
    HomeViewItem = new HomeViewModel();
}

这里还有 2 个电话

GetType().Assembly.GetTypes()
            .Where(type => type.IsClass)
            .Where(type => type.Name.EndsWith("ViewModel"))
            .Where(type => !(String.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace.EndsWith("ViewModels"))
            .ToList() // <= !!! look here in debugger, what ToList() returns.
            // Here foreach loop calls constructor ShellViewModel()
            // which calls HomeViewModel() via CreateSourceItem() again
            // then the loop calls constructor of HomeViewModel() separately.
            .ForEach(viewModelType => _container.RegisterSingleton(
                viewModelType, viewModelType.ToString(), viewModelType));

总计: 3次。全部正确。

辛格尔顿很危险。


推荐阅读