创建视图模型时如何将引用传递给类实例

时间:2019-02-21 15:43:32

标签: c# mvvm viewmodel

我正在开发一个包含主视图和5个用户控件的程序。我已经创建了XAML并创建了一个视图模型,使其位于其中也绑定了视图的每个视图的后面。

我有一个主要的Program类,并且我想拥有其他一些类,例如producttestTool等。

在应用程序启动时,我加载mainWindow,然后将创建mainWindowViewModel,并依次创建Program类。

当用户按下按钮时,我希望mainWindowViewModel显示userControl1,但是我希望userControl1ViewModel能够看到Program类并访问其属性和方法

我一直在阅读“通过引用传递类的实例”之类的内容,但是如果userControl1View创建了userControl1ViewModel,我该如何传递对在创建的'program'类的引用呢?程序启动了吗?

2 个答案:

答案 0 :(得分:1)

这是依赖注入旨在解决的问题。

首先,如果您正在执行MVVM,那么您应该能够运行整个应用程序而无需创建任何视图,即仅创建视图模型。如果您的MainWindow带有ChildView(例如),则通常将它们与相应的视图模型匹配:

public MainViewModel : ViewModeBase
{
    public ChildViewModel MyChild {get; } // gets assigned later

然后在您的XAML中:

<Window ...

   <local:ChildView DataContext="{Binding MyChild}" />

有时候,您需要MyChild显示不同的视图,每个视图都有其自己的对应视图模型,并且您可能需要在运行时进行更改。在这种情况下,MyChild必须是object类型(或某些常见的基类),并且还需要支持属性更改通知:

public class MainViewModel : ViewModelBase
{
    private object _MyChild;
    public object MyChild
    {
        get { return this._MyChild; }
        set
        {
            if (this._MyChild != value)
            {
                this._MyChild = value;
                RaisePropertyChanged(() => this.MyChild);
            }
        }
    }
}

然后在XAML中,您改为创建ContentControl:

<Window ...

   <ContentControl ="{Binding MyChild}" />

在这里放置之后,您可以在窗口或应用程序“资源”部分中使用DataTemplate来指定哪些视图与哪些视图模型匹配:

    <DataTemplate DataType="{x:Type vm:FooViewModel}">
        <view:FooView />
    </DataTemplate>

    <DataTemplate DataType="{x:Type vm:BarViewModel}">
        <view:BarView />
    </DataTemplate>

现在,如果您在MainViewModel中执行类似的操作...

this.MyChild = new FooViewModel();

... ContentControl将自动填充为FooView类型的控件。此外,它将DataContext自动设置为您创建的FooViewModel的实例。然后您像这样重新分配它:

this.MyChild = new BarViewModel();

...然后FooView将被替换为BarView。

因此,在使用DataTemplating的情况下,您只需担心将ViewModel的引用彼此传递,这就是依赖注入的地方。这是一个很大的话题,我建议您继续阅读,但是这个想法是通过DI框架(而不是new运算符)创建所有视图模型,然后将所有位粘合在一起。例如,您的产品可能是管理所有产品的存储库类的一部分,因此首先声明一个接口:

public interface IProductRepository
{
    Products[] AllProducts();
    Product GetProductByName(string name);
    ... etc ...

然后,您将创建一个实现此接口的实际类,并在安装过程中为依赖框架提供规则,以规定任何东西请求IProductRepository(使用单个实例,创建一个新实例等)时应执行的操作。然后,只要您整个应用程序中的任何内容需要访问产品存储库,它要做的就是声明带有[Inject]标签的属性(这是如果您使用Ninject,则每个库都有其自己的方式) :

public class MyClass
{
    [Inject]
    public IProductRepository ProductRepo {get; set;} // <-- gets injected

现在,当您创建MyClass类型的实例时,依赖项注入框架将为您创建它,并使用您提供的规则自动初始化ProductRepo。

这是MVVM中的DataTemplating和依赖注入的工作原理的非常简单的概述,一旦开始使用它们,您会想知道如果没有它们,您将如何进行管理。据我所知,问题中的主要问题是您正在尝试使视图模型彼此对话。通常,这不是MVVM的实现方式。视图模型通过注入到其中的服务进行通信作为一般经验法则,它们的工作是充当这些服务与前端GUI元素之间的管道,而不是彼此之间。

答案 1 :(得分:0)

您所谈论的实际上不是一个简单的过程,您所谈论的是架构,以便在期望的位置获得期望的引用。这可以通过很多方法来解决,因此下面我将抛出一个相当不合理但非常快速的示例。体系结构问题与// HACK: s

密切相关

通常,您会希望模型来自中央位置,例如数据库支持,该位置控制着正确实例的移交。

public abstract class Model
{ 
    // HACK: Don't bother wiring up OnPropertyChanged here, since we don't expect ID to get updated that often, but need a public setter for the Provider
    Guid ID { get; set; }
}

// HACK: While using a generic here makes for readable code, it may become problematic if you want to inherit your models
public class ModelProvider<TModelType> where TModelType : Model, new()
{
    // HACK: Use better dependency injection than this
    private static ModelProvider<TModelType> _instance = new ModelProvider<TModelType>();
    public static ModelProvider<TModelType> Instance => _instance;
    private ModelProvider() { }

    // TODO: Make this into a dictionary of WeakReferences so that you're not holding stale data in memory
    ConcurrentDictionary<Guid, TModelType> LoadedModels = new Dictionary<Guid, TModelType>();

    private TModelType GenerateModel(Guid id) => new TModelType { ID = id };
    private TModelType LoadKnownModel(Guid id)
    {
        throw new NotImplementedException("Implement a data store to get known models");
    }

    public TModelType GetNew() => LoadedModels.AddOrUpdate(Guid.NewGuid(). GenerateModel);
    public TModelType GetById(Guid id) => LoadedModels.GetOrAdd(id, LoadKnownModel);
}

然后您的ViewModel可以访问

ModelProvider<Product>.Instance.GetById(WellKnownGuid);

对于测试,WellKnownGuid可能也是程序中的静态ID