多次实例化 MVVM ViewModel 构造函数

MVVM ViewModel constructor instantiated several times

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

<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应该return现有实例,不是吗? 顺便说一句:StartupUri 已从 app.xaml 中删除。 提前致谢!

此标记调用 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 次。全部正确。

单例很危险。