WPF:具有子用户控件 (MVVM) 的窗口的正确配置
我正在努力正确完成以下任务。我有一个用户控件(ProgramView)。它有一个视图模型(ProgramViewViewModel)。 ProgramView 作为窗口 (ProgramWindow) 内的子控件使用。 ProgramWindow有一个公共属性ProgramId,因此窗口的使用者可以指定想要显示的Program(数据实体)。 ProgramView 有一个属性 ProgramId,因为它的主要工作是显示此数据。 ProgramWindow 只不过是此用户控件的包装窗口。
ProgramViewViewModel 还有一个属性 ProgramId。对此属性的更改会驱动视图模型的操作,这些操作是使用 ProgramView 可以绑定到的其他属性从视图模型中浮现出来的。
我试图向 ProgramView 和 ProgramWindow 的使用者隐藏视图模型的操作。
该 ProgramId 应该通过所有这些层进行绑定。对 ProgramWindow.ProgramId 的更改应流向 ProgramView.ProgramId,然后流向 ProgramViewViewModel.ProgramId。我不知道如何正确实施这一点。
我当前的方法是在所有三个类中将 ProgramId 作为 DP 来显示。在窗口中,我想象 ProgramView 被实例化:
<local:ProgramView ProgramId="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ProgramWindow}}, Path=ProgramId}" />
这似乎确实有效。在 ProgramView 中,我确实获得了属性的更改事件,并且它们确实具有正确的值。 FindAncestor 似乎运行正常。
那么我应该如何同步 ProgramViewViewModel.ProgramId 属性?我看到两种方法。一种方法是在 ProgramViewViewModel 实例本身上设置 Binding,同时使用 FindAncestor,并在 ProgramViewViewModel 上查找 ProgramId。这有两个缺点。它需要 ProgramViewViewModel 将 ProgramId 作为依赖属性显示。我宁愿避免这种情况,但这可能是可以接受的。不管怎样,我无法在 XAML 中完成它。
<local:View.DataContext>
<local:ProgramViewViewModel
ProgramId="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ProgramView}}, Path=ProgramId}" />
</local:View.DataContext>
这是行不通的。看来我无法在实例的实例化中引入绑定表达式。 FindAncestor 报告找不到 ProgramView。我的理论是,实例位于逻辑树之外,因此无法遍历到它的父级。
第二个选项更有意义,是将 ProgramView.ProgramId 属性绑定到“ProgramId”(在 DataContext 中)。我无法完成此任务,因为我无法弄清楚如何在代码隐藏中定义的属性上指定绑定表达式。在 XAML 中是必需的,但 ProgramId 存在的类型实际上是 。我不知道如何指定这个属性。
如果我手动(在 ProgramView 的代码隐藏中)创建一个 Binding 实例并调用 SetBinding(ProgramIdProperty, binding),则该值不再流入视图本身。我相信这是因为我刚刚替换了 ProgramView.ProgramId 上的绑定,该绑定之前由 ProgramWindow 设置。每个属性一个绑定?
我剩下的想法是在 ProgramView 中提供两个 ProgramId 属性。一个绑定到 DataContext,另一个可公开供使用者 (ProgramWindow) 绑定,然后编写同步两者的 OnValueChanged 处理程序。这感觉就像是黑客攻击。另一种方法是在 ProgramView 的代码隐藏中手动监视 ProgramView.ProgramId 和 ProgramView.DataContext 的更改,并自行传播该值。这些想法都不理想。
我正在寻找其他建议。
I am trying to properly accomplish the following. I have a UserControl (ProgramView). It has a viewmodel (ProgramViewViewModel). ProgramView is consumed as a child control within a Window (ProgramWindow). ProgramWindow has a public property ProgramId, so the consumer of the window can specify the desired Program (data entity) to show. ProgramView has a property ProgramId, as it's primary job is to display this data. ProgramWindow is little more than a wrapper window for this user control.
ProgramViewViewModel also has a property ProgramId. Changes to this property drive out the operation of the view model, which are surfaced out of the view model using other properties, which ProgramView can bind to.
I am trying to hide the operation of the view model from the consumer of the ProgramView and ProgramWindow.
This ProgramId should be bound through all of these layers. Changes to ProgramWindow.ProgramId should flow to ProgramView.ProgramId and then to ProgramViewViewModel.ProgramId. I cannot figure out how to properly implement this.
My current approach is to surface ProgramId in all three classes as a DP. Within the Window, I would imagine ProgramView being instantiated thusly:
<local:ProgramView ProgramId="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ProgramWindow}}, Path=ProgramId}" />
This appears to actually work. Within ProgramView, I do obtain changed events for the property, and they do appear to have the correct value. FindAncestor seems to operate properly.
How then should I synchronize the ProgramViewViewModel.ProgramId property? I see two ways. One way would be to set a Binding on the ProgramViewViewModel instance itself, to also use FindAncestor, and find the ProgramId on the ProgramViewViewModel This has two downsides. It requires ProgramViewViewModel to surface ProgramId as a dependency property. I'd rather like to avoid this, but it might be acceptable. Either way, I cannot accomplish it in XAML.
<local:View.DataContext>
<local:ProgramViewViewModel
ProgramId="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ProgramView}}, Path=ProgramId}" />
</local:View.DataContext>
This does not work. It appears that I cannot introduce a binding expression within the instantiation of the instance. FindAncestor reports that it cannot find ProgramView. My theory here is that the instance is outside of the logical tree, and thus cannot traverse to it's parent.
The second option, which makes more sense, is to bind the ProgramView.ProgramId property to "ProgramId" (in the DataContext). I cannot accomplish this because I cannot figure out how to specify a binding expression on a property defined in the code-behind. is required in the XAML, but the type ProgramId exists on is actually . I cannot figure out how to specify this property.
If I manually (in code-behind of ProgramView) create a Binding instance and call SetBinding(ProgramIdProperty, binding), the value no longer flows into the View itself. I believe this is because I just replaced the binding on ProgramView.ProgramId, which was previously set by ProgramWindow. One binding per-property?
My remaining ideas are to provide TWO ProgramId properties in ProgramView. One bound to the DataContext, the other publicly available to be bound by the consumer (ProgramWindow), and then write OnValueChanged handlers that synchronize the two. This feels like a hack. The other is to manually watch for changes on ProgramView.ProgramId and ProgramView.DataContext within the code-behind of ProgramView, and propagate the value myself. Neither of these ideas feel ideal.
I'm looking for other suggestions.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您的描述似乎很详细,但我无法理解为什么您需要实现此设计。我忍不住想干。
如果您需要在两个此类相关视图模型中公开依赖属性,我建议您将子视图模型(对于用户控件视图)设置为第一个视图模型(对于程序窗口视图)的属性。类似于:
MainView 可以使用 ChildViewModel.ProgramId(数据上下文设置为 MainViewModel)获取属性。 ProgramView 通过 ProgramId 访问它(数据上下文设置为 MainViewModel.ChildViewModel)。
Your description seems detailed but I'm having trouble understanding why you need to implement this design. I can't help but think DRY.
If you need to expose a dependency property in two such-related view models, I would suggest that you make the child view model (for the user control view) a property of the first (for the program window view). Something like:
The MainView can get the property using ChildViewModel.ProgramId (data context set to MainViewModel). The ProgramView accesses it by ProgramId (data context set to MainViewModel.ChildViewModel).