使用 ViewModelLocator 时如何标记视图模型? [英] How to tag viewmodel when using ViewModelLocator?

查看:21
本文介绍了使用 ViewModelLocator 时如何标记视图模型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有多个视图模型实例:

<views:MyView x:Name="view2" mefed:ViewModelLocator.NonSharedViewModel="MyViewModel"/>

这两个实例应该监听不同的消息.因此,我需要以某种方式标记这些视图模型实例.怎么样?

我正在使用 MEFedMVVMPrism.应该有一种方法可以让视图模型知道某个状态.例如:

<!-- 让视图模型知道它是 X 类型的 --></views:MyView><views:MyView x:Name="view2" mefed:ViewModelLocator.NonSharedViewModel="MyViewModel"><!-- 让视图模型知道它是 Y 类型的 --></views:MyView>

如何实现?

在理想情况下,您将通过 XAML 使用视图的参数化构造函数,但不支持这种方式.另一个想法是为视图使用不同的类,但这很快会使代码膨胀!

解决方案

快速解答和总结:

正如您所注意到的,您真正想要的是从 XAML 参数化您的 ViewModel.所以我的直觉是编写一个附加行为来提供为您的 ViewModel 传递参数的能力.更具体地说,想到的是我们想要一个单个附加行为类,我们可以指定两者(a)我们想要的ViewModel> (b) 该 ViewModel 的参数.为了在一个班级中满足两者这些愿望,同时也像DRY 尽可能,我认为使用混合"行为是最简单的,因为混合行为不是静态类,因此能够使用它们将两个相关信息一起传递似乎更容易.>

说明:

首先快速免责声明:我没有使用过 MEFedMVVM 或 Prism(但我使用过其他 MVVM 库),所以我的回答使用了我最近学会使用的更通用的方法.因此,当您以正常"方式使用事物(即自动连接 DataContext 等)时,这种方法不依赖于 Prism 可能给您的任何神奇"的东西,所以让这种心态构成框架.

关于常规"附加行为和混合"行为之间差异的好文章,我喜欢 这篇博文.我不会在这里重复他的解释,但我注意到的关键是常规的附加行为似乎只依赖单个信息(即单个参数")来执行它的东西.如(来自博文):

现在让我们根据您的情况来说明,如果您只使用常规附加行为,请检查它如何工作.您将编写一个附加行为类,将其称为MyViewModel1Creator",它将:(1) 注册一个名为Type"的附加属性,然后(2) 包括类型"的更改回调处理程序(在初始设置时也会被调用 - 请参阅链接博客文章中的HookupBehavior"方法).在此更改回调中,您将实例化ViewModel1"并将类型"附加属性的值传递给它.同样在此方法中,您可以处理任何其他必需品,例如为 View 设置 DataContext 等.您可以使用回调处理程序(依赖对象参数).

然后您对MyViewModel1Creator"类的 Xaml 使用将如下所示:

<views:MyView x:Name="view2" MyBehaviors:MyViewModel1Creator.Type="Y"/>

虽然这可行,但我看到了这种方法的缺点(使用常规附加属性).要使用这种方法,您必须为每个 ViewModel 创建一个单独的附加行为类,这意味着如果您有 3 个 ViewModel(ViewModel1"、ViewModel2"、ViewModel3"),那么您将需要编写 3 个附加行为类(ViewModel1Creator"、ViewModel2Creator"、ViewModel3Creator").每个都将实例化其各自的 ViewModel(并公开一个类型"附加属性,如上所示).另一个缺点是似乎很难找到一种方法来添加附加参数来传入.

上述方法略有不同,但同样不足DRY,将有一个类(称为MyViewModelCreator"-这次没有1"),其中包含多个名称为CreateViewModel_1_WithType"、CreateViewModel_2_WithType"、CreateViewModel_3_WithType"的附加属性等.它的用法如下:

<views:MyView x:Name="view2"MyBehaviors:MyViewModelCreator.CreateViewModel_1_WithType="Y"/>

同样,这些方法不是很 DRY,所以我们真的需要......

现在让我们考虑一下如果我们使用 混合"行为:

您将编写一个派生自类型化行为的类 - 对于您的视图,它可能是 Behavior<UserControl>,因此您的类标题可能如下所示:
公共类 ViewModelSetupBehavior : Behavior.在本课程中,您将:(1)注册任意数量的依赖属性,包括类型"依赖属性和ViewModelName"依赖属性,以及(2) 您将覆盖 OnAttached() 方法,在该方法中您将实例化由ViewModelName"依赖属性的值指示的任何 ViewModel,并将Type"的值传递给它依赖属性.同样,这也是处理任何其他必需品的地方,例如为视图设置 DataContext 等.您可以使用 访问行为附加到"的对象(在本例中为您的视图)AssociatedObject 属性.

这可以让你做到这一点:

<i:Interaction.Behaviors><MyBehaviors:ViewModelSetupBehavior ViewModelName="ViewModel1" Type="X" SomeOtherParam="bla"/></i:Interaction.Behaviors></views:MyView><views:MyView x:Name="view2"><i:Interaction.Behaviors><MyBehaviors:ViewModelSetupBehavior ViewModelName="ViewModel1" Type="Y"/></i:Interaction.Behaviors></views:MyView>

现在请注意,我们可以使用单个 Behavior 类来创建所有 ViewModel,并且我们可以传入几个em> 参数来指示我们希望如何实例化 ViewModel.

I have multiple instances of a viewmodel:

<views:MyView x:Name="view1" mefed:ViewModelLocator.NonSharedViewModel="MyViewModel" />
<views:MyView x:Name="view2" mefed:ViewModelLocator.NonSharedViewModel="MyViewModel" />

These two instances should listen for different messages. Hence, I need to somehow tag these viewmodel instances. How?

I'm using MEFedMVVM and Prism. There ought to be a way to let the viewmodel know about some state. E.g.:

<views:MyView x:Name="view1" mefed:ViewModelLocator.NonSharedViewModel="MyViewModel">
  <!-- Let the viewmodel know it is of type X -->
</views:MyView>
<views:MyView x:Name="view2" mefed:ViewModelLocator.NonSharedViewModel="MyViewModel">
  <!-- Let the viewmodel know it is of type Y -->
</views:MyView>

How can this be achieved?

In an ideal world you would use a view's parameterized constructor through XAML, but this is not supported. Another idea is to use different classes for the views, but that will soon bloat the code!

解决方案

Quick Answer and Summary:

As you noticed, what you really want is to parameterize your ViewModel from XAML. So my instinct is to write an Attached Behavior to provide the ability to pass in parameters for your ViewModel. And more specifically, what comes to mind is that we want a single Attached Behavior class that we can specify both (a) which ViewModel we want and (b) a parameter for that ViewModel. To satisfy both of these desires in a single class while also being as DRY as possible, I think it is easiest to use a "Blend" Behavior, since Blend behaviors are not static classes and thus it seems much easier to be able to use them to pass in both pieces of related information together.

Explanation:

First a quick disclaimer: I have not used MEFedMVVM or Prism (but I have used other MVVM libraries), so my answer uses a more general approach that I have recently learned to make use of. Thus, this approach does not rely on any "magic" stuff that Prism may give you when you use things in their "normal" way (i.e., hooking up DataContext automatically, etc.), so let that frame this mindset.

For a good writeup about the differences between "regular" Attached Behaviors and "Blend" Behaviors, I like this blog post. I won't repeat his explanations here, but the key thing I notice is that regular Attached Behaviors seem to just rely on a single piece of information (i.e., a single "parameter") in order to do its thing. Such as (from the blog post):

<GridView local:ItemClickNavBehavior.Destination="Home" ...>

Now lets put that in terms of your case, and examine how it could work if you just used regular Attached Bahaviors. You would write an Attached Behavior class, call it "MyViewModel1Creator" that would: (1) register an attached property called "Type" and (2) include a change callback handler for "Type" (which also gets called when its initially set - see the "HookupBehavior" method in the linked blog post). In this change callback you would instantiate "ViewModel1" and pass in to it the value of the "Type" Attached Property. Also in this method you could take care of any other necessities such as setting the DataContext for the View, etc. You can access the object that the Attached Property is attached to (the the View object in this case) by using the first parameter in the callback handler (the Dependency object param).

Then your Xaml use of the "MyViewModel1Creator" class would look like:

<views:MyView x:Name="view1" MyBehaviors:MyViewModel1Creator.Type="X" />
<views:MyView x:Name="view2" MyBehaviors:MyViewModel1Creator.Type="Y" />

Although this would work, I see a disadvantage to this approach (using regular Attached Properties). To use this approach, you would have to create a separate Attached Behavior class for each ViewModel, meaning that if you had 3 ViewModels ("ViewModel1", "ViewModel2", "ViewModel3") then you would need to write 3 Attached Behavior classes ("ViewModel1Creator", "ViewModel2Creator", "ViewModel3Creator"). Each one would instantiate its respective ViewModel (and expose a "Type" Attached Property as shown above). Another disadvantage is that it seems to be harder to find a way to add additional parameters to pass in.

A slightly alternate approach to the one above, but equally deficient in terms of being DRY, would be to have a single class (call it "MyViewModelCreator" - without the "1" this time) that houses several Attached Properties with names like "CreateViewModel_1_WithType", "CreateViewModel_2_WithType", "CreateViewModel_3_WithType", etc. Its usage would look like:

<views:MyView x:Name="view1"
              MyBehaviors:MyViewModelCreator.CreateViewModel_1_WithType="X" />
<views:MyView x:Name="view2" 
              MyBehaviors:MyViewModelCreator.CreateViewModel_1_WithType="Y" />

Again, these approaches are not very DRY, so we really need to...

Now let's consider how it could work if we used a "Blend" behavior:

You would write a class that derives from a typed Behavior - for your views it would probably be Behavior<UserControl>, so your class heading might look like:
public class ViewModelSetupBehavior : Behavior<UserControl>. In this class you would: (1) register as many Dependency Properties as you like, including a "Type" Dependency Property, and a "ViewModelName" Dependency Property, and (2) You will override the OnAttached() method, where you will instantiate whichever ViewModel is indicated by the value of the "ViewModelName" Dependency Property, and also pass in to it the value of the "Type" Dependency Property. Again, this would be the place to also handle any other necessities such as setting the DataContext for the View, etc. You can access the object that the Behavior is "attached to" (your View in this case) by using the AssociatedObject property.

This could let you do this:

<views:MyView x:Name="view1">
    <i:Interaction.Behaviors>
        <MyBehaviors:ViewModelSetupBehavior ViewModelName="ViewModel1" Type="X" SomeOtherParam="bla" />
    </i:Interaction.Behaviors>
</views:MyView>

<views:MyView x:Name="view2">
    <i:Interaction.Behaviors>
        <MyBehaviors:ViewModelSetupBehavior ViewModelName="ViewModel1" Type="Y" />
    </i:Interaction.Behaviors>
</views:MyView>

Now notice that we have the ability to use a single Behavior class for the creation of all of our ViewModels, and we can pass in several parameters to dictate how we want the ViewModel to be instantiated.

这篇关于使用 ViewModelLocator 时如何标记视图模型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆