2010-09-01 139 views
10

有些东西一直困扰着我关于MVVM的内容 - 如果我使用视图优先的方法来构建我的对象(这看起来是最常见的方法,至少在阅读和搜索之后),我该如何获得上下文信息输入到视图模型中?初始化视图模型

我见过很多类似问题的答案,都说“使用DI容器注入模型”,但这并不能帮助我,所以我将提供一个小例子。

假设我的应用程序是PeopleEditor。它用于加载和编辑复杂的People对象。当你加载应用程序时,你会得到一个主屏幕,将一群人加载到内存中 - 比方说,这些都可以通过我可以从我的容器中获得的集合访问。通过点击一个人,你会被带到编辑器屏幕。编辑器很复杂,所以这不是在一个屏幕中实现的简单的主细节视图。

所以,在主屏幕上,当我点击一个人时,应用程序需要创建一个新的视图和视图模型并显示视图。如果我首先通过容器创建视图模型,我可以用适当的人物对象初始化它。 这对我来说似乎很自然,所以我很难弄清为什么视图优先似乎是主要模式。如何使用视图优先方法来做到这一点?该视图将创建视图模型,该视图模型可以获取People的集合,但不知道其编辑的是哪个人。编辑清晰度:多人编辑可以同时存在,每编辑一个不同的人。

Prism 4.0 alpha的MVVM参考实现使用“状态处理程序”,它基本上是应用程序用来在容器中存储构造函数参数的服务。它保存状态并调用ShowView,并且最终创建的视图模型会导入一个状态对象。这对我来说看起来很笨拙 - 就像它试图假装它真的不是那么松散耦合。其他人是否有其他指导?

+0

为什么你不想当你说的更自然地你使用视图模型第一种方法? marlon grech表示,视图或视图模型首先是个人偏好的选择(视图首先更容易与混合使用)。我也这么认为,我使用最适合我的场景的方法。所以我在我的应用程序中混淆了两者,这取决于我想要做什么。 – blindmeis 2010-09-01 06:36:43

+0

有趣的问题。我总是问自己关于视图优先方法的相同问题......视图模型 - 首先看起来更加自然,所以这就是我始终使用的方法 – 2010-09-01 08:16:50

+0

@blindmeis:谢谢你的回应。这并不是说我不想首先使用viewmodel--事实上,这就是我现在的应用程序的结构 - 只是我很好奇如何以视图优先的方式来完成它。我在应用程序中试图实现的东西对我来说看起来相当普遍,而viewmodel-首先看起来很自然,答案是我无法弄清楚为什么更多更好地选择“viewmodel composition”或“viewmodel-first”。 – nlawalker 2010-09-01 14:20:02

回答

3

nlawalker,

我不是专家,但我了解查看一和模型首先是:

  1. 视图 - 首先:查看视图模型的程序,您可以创建视图,则视图模型自动生成。
  2. Model-First:ViewModel程序查看,您可以在根应用程序中创建ViewModel对象图,将其分配给根视图数据上下文。然后让视图渲染其相关的孩子依赖于视图模型。

不要吝啬说模型的第一种方法是不好的,但我更喜欢查看,第一种方法,因为视图模型可以在代码坐在后面,所以当一些工艺要求不具约束力的友好任务(PasswordBox,DialogConfirmation,ClosingForm等等),我可以在后面的代码中编写我的逻辑。

无论如何,为了解决这个问题,我通常使用IOC和Event Aggregator的组合。它是:

  1. 对于viewmodel需要上下文信息注册它在IOC容器中的实例,而不是它的类型。因此,即使不是它的观点,它也是准备好了。
  2. 导航操作发生时(通过单击人员列表项目)使用IOC容器解析器解析您的视图。并使用指定的参数向导航总线发送事件。此外,这个事件将会被目标ViewModel捕获并执行一些操作。

注册viewmodel的实例并不是必须的。只有在由前一个视图模型分派事件时才确保视图模型已准备就绪。

UPDATE

但随后与任何一种当地的情况,我需要使用的全球性设施向它发送一个事件来填充呢?

在你的情况下,上下文对象不是本地的,而是在对象调用之间传递的消息。显然,在模型中,第一种方法你做:

//selectedPeople is contextual object 
myPeopleDetailVM.LoadData(selectedPeople) 

将几乎相同的,当你通过selectedPeople到事件总线的参数。

如果考虑性能,则可以将其与WPF Routed Event System进行比较,在这种情况下,路由策略比事件总线更复杂,我认为如果您对使用WPF路由事件足够自信,那么您应该使用Event Aggregator。

如果您使用内置框架事件聚合器(棱镜,mvvmlight),您的viewmodel被事件总线污染,如果您抱怨这一点,我同意你的唯一问题。

希望有所帮助。

+0

感谢您的回复。这几乎是我想到的,但对我来说这似乎很奇怪 - 使用IOC创建视图模型,很好,但是随后使用任何类型的本地上下文填充它,我需要使用全局工具将它发送给事件?对我来说这似乎很奇怪。 – nlawalker 2010-09-01 14:23:03

+0

我已经更新了我的答案。 – ktutnik 2010-09-01 22:26:37

0

如果您使用的是棱镜,您可以使用其导航功能轻松整齐地解决此问题。使用IRegionManager.RequestNavigate通过构建目标视图的Uri来为主对象导航到编辑视图,以包含相应人员标识的查询字符串参数。您可以在目标视图模型的OnNavigatedTo()方法实现(INavigationAware成员,视图模型应该实现此接口)中提取该id。

您可以在Prism下载附带的“View-Swithing Navigation”示例应用程序中看到此操作。它在Quickstarts文件夹下。

,从同样的样品应用程式(模仿Outlook)中,这下面的代码被用来从InboxView导航到EmailView为了从收件箱中打开一个特定的电子邮件:

var builder = new StringBuilder(); 
builder.Append(EmailViewKey); 
var query = new UriQuery(); 
query.Add(EmailIdKey, document.Id.ToString("N")); 
builder.Append(query); 
this.regionManager.RequestNavigate(RegionNames.MainContentRegion, new Uri(builder.ToString(), UriKind.Relative)); 

而在EmailView的视图模型EmailViewModel要打开的电子邮件从这样的背景下导航提取:

void INavigationAware.OnNavigatedTo(NavigationContext navigationContext) 
    { 
     // todo: 15 - Orient to the right context 
     // 
     // When this view model is navigated to, it gathers the 
     // requested EmailId from the navigation context's parameters. 
     // 
     // It also captures the navigation Journal so it 
     // can offer a 'go back' command. 
     var emailId = GetRequestedEmailId(navigationContext); 
     if (emailId.HasValue) 
     { 
      this.Email = this.emailService.GetEmailDocument(emailId.Value); 
     } 

     this.navigationJournal = navigationContext.NavigationService.Journal; 
    } 

private Guid? GetRequestedEmailId(NavigationContext navigationContext) 
    { 
     var email = navigationContext.Parameters[EmailIdKey]; 
     Guid emailId; 
     if (email != null && Guid.TryParse(email, out emailId)) 
     { 
      return emailId; 
     } 

     return null; 
    }