【问题标题】:MVVM ViewModel constructor instantiated several times多次实例化 MVVM ViewModel 构造函数
【发布时间】:2020-07-18 20:57:48
【问题描述】:

有人介意帮助我解决 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 中删除。 提前致谢!

【问题讨论】:

  • 当您在 XAML 中设置 DataContext 时,它将创建一个实例。 CreateSourceItem 方法创建一个实例和 DI 容器。 1+1+1=3 q.e.d.

标签: c# wpf mvvm constructor caliburn.micro


【解决方案1】:

这个标记调用 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 次。都正确。

单身很危险。

【讨论】:

  • 提前致谢!有没有一种最佳实践方式来结合它?
  • @AtzeBK 您应该选择初始化实例的唯一方法。手动或单例实现。就是这样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多