从 WPF 中的视图设置 ViewModel 的属性

发布于 2024-09-06 16:53:55 字数 1189 浏览 2 评论 0原文

我的 ViewModel 有一个依赖属性,它是我的视图的 DataContext。 ViewModel 没有对 View 的引用。 ViewModel 上的属性将引用视图上的控件,但我需要能够在 XAML 中设置此属性。

这怎么可能?我的一个想法是开发一个具有 Property 属性和 Value 属性的自定义控件,这样您就可以在 View 中执行类似的操作来设置 ViewModel 上的属性:

<PropertySetter Property="{Binding MyViewModelDependencyProperty}" Value="{Binding ElementName=aControlOnMyView" />

在我走这条路线之前,我想检查是否我还可以采取其他方法吗?


感谢雷的详细回复,但是如果我向您提供有关我试图解决的问题的更多详细信息,您可能会更好地了解我为什么提到我所做的方法。

基本上,我想做的就是当用户点击按钮时将焦点设置到文本框。我编写了一个附加属性,您可以将其附加到 Button 控件,指定触发事件是什么(在本例中为“Click”事件),然后指定要关注的控件。这非常有效,并且将所有内容都保留在 XAML 中。

但是,我现在有一个用例,其中焦点应设置为工具栏一部分按钮上的单击事件中的任意文本框。该工具栏本身就是一个用户控件,它位于另一个用户控件内,而该用户控件又位于另一个用户控件内!此工具栏需要可在各种不同的表单中重复使用,并且每次单击按钮后设置焦点的控件都会因表单而异。

这就是为什么我想到将焦点控件(即文本框)作为视图模型本身的属性(准确地说是在我的 ViewModel 基础上),并拥有 ViewModel 基础代码(工具栏绑定到的代码),设置单击按钮时将焦点集中到控件(并且在 ViewModel 基础上调用例如添加/编辑方法)。

在单元测试中,关注属性的控件将为空,因此它的 .Focus() 方法不会被调用。所以我看不出那里有问题。我的问题是如何从 XAML 设置焦点控制属性,这就是我有 PropertySetter 想法的原因。

我不喜欢 ViewModel 对视图上的控件有任何引用,但我看不到另一种方法来实现我所需要的。如果决定是否将焦点设置到控件的逻辑非常复杂怎么办?这肯定会位于 ViewModel 中吗?因此,ViewModel 拥有这个 UIElement 属性有什么坏处吗?它仍然对它绑定到的特定视图一无所知,它只知道当 ViewModel 上发生某些操作时,它需要将焦点设置到一个控件。

I have a dependency property on my ViewModel which is the DataContext for my View. The ViewModel has no reference to the View. The property on the ViewModel is going to reference a control on the view, but I need to be able to set this property in XAML.

How is this possible? One thought I had was to develop a custom control which has a Property property and a Value property, so you could do something like this in the View to set the property on the ViewModel:

<PropertySetter Property="{Binding MyViewModelDependencyProperty}" Value="{Binding ElementName=aControlOnMyView" />

Before I went down this route, I wanted to check if there was any other approach I could take?


Thanks for the detailed reply Ray, but if I give you a bit more detail about the problem I'm trying to solve, you might get a better idea of why I mentioned the approach I did.

Basically, what I'm trying to do is set the focus to a textbox when the user hits a button. I've written an attached property which you can attach to the Button control, specify what the trigger event is (in this case the 'Click' event), and then what control to focus on. This works really nicely, and keeps everything in XAML.

However, I now have a use case where the focus should be set to an arbitrary text box from the click event on a button which is part of a toolbar. This toolbar is itself a user control which is sitting inside another user control, which is inside another user control! This toolbar needs to be reusable across various different forms, and each time, the control to set focus on after you click the button will be different per form.

That's why I had the idea of making the focus control (i.e. a textbox) a property on the view model itself (on my ViewModel base to be precise), and have the ViewModel base code (which the toolbar is bound to), set the focus to the control when the button is clicked (and the e.g. Add/Edit method is called on the ViewModel base).

In unit test land, the control to focus on property will be null, so it's .Focus() method just won't be called. So I can't see an issue there. My problem is then how you set the focus control property from XAML, which is why I had the PropertySetter idea.

I don't like the fact that the ViewModel has any reference to controls sitting on the view, but I can't see another way to achieve what I need. What if the logic that dictates whether to set focus to the control is quite complex? This would sit in the ViewModel surely? Therefore, is there any harm in the ViewModel having this UIElement property? It still knows nothing about the specific View it is bound to, it just knows that there is a control which it needs to set focus to when some action happens on the ViewModel.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我喜欢麦丽素 2024-09-13 16:53:55

我的第一反应(而且是强烈的)是说“不要那样做!”通过为视图模型提供对 UI 部分的引用,您就打破了使视图模型如此强大和有用的封装。

例如,如果您想对视图模型进行单元测试或将其序列化到磁盘,该怎么办?在每种情况下,您的 UI 部分都不会出现,因为根本没有视图。您的测试将错过覆盖范围,并且您的重构将不完整。

如果您的视图模型实际上需要对 UI 对象的引用,并且没有更好的方法来构建它,那么最好的解决方案是让视图模型本身构造它需要引用的那些控件。然后,您的视图可以通过绑定将该控件合并为 ContentPresenter 的内容,并提供样式来配置该控件,包括提供其内容的 ControlTemplate。因此:

public class MyViewModel
{
  public ListBox SpecialControl { get; set; }
  public MyViewModel()
  {
    SpecialControl = new ListBox();
  }
}

其他

<DataTemplate TargetType="{x:Type local:MyViewModel}">
  <DataTemplate.Resources>
    <Style TargetType="ListBox" ... />
  </DataTemplate.Resources>
  ...
  <ContentPresenter Content="{Binding SpecialControl}" />
</DataTemplate>

可能性是:

  1. 让视图模型实际上从 Control 类派生,然后重写 OnApplyTemplate() 并使用 GetTemplateChild 查找名称以“PART_”开头的模板项
  2. 实现采用属性名称的附加属性,发现DataContext 中的属性,并将其设置为该属性所附加到的 DependencyObject。
  3. 实现您的 PropertySetter 想法

我的选项 #2 如下所示:

<DataTemplate TargetType="{x:Type MyViewModel}">
  ...
  <TextBox local:PropertyHelper.SetViewModelToThis="SpecialControl" />
  ...
</DataTemplate>

SetViewModelToThis PropertyChangedCallback 中的代码将从 DataContext 获取视图模型,对其进行反思以查找“SpecialControl”属性,然后将其设置为 TextBox。请注意,SetViewModelToThis 的实现必须考虑到 DataContext 未立即设置的可能性,并且它可能会发生更改,需要删除旧设置并进行新设置。

My first reaction (and it's a strong one) is so say "Don't do that!" By giving your view model a reference to a part of your UI you are breaking the encapsulation that makes view models so powerful and useful.

For example, what if you want to unit test your view model or serialize it to disk? In each case the piece of your UI will not be present, because there will be no view at all. Your tests will miss coverage and your reconstitution will be incomplete.

If your view model actually needs references to UI objects and there is no better way to architect it, the best solution is to have the view model itself construct those controls it requires a reference to. Then your view can incorporate that control as the Content of a ContentPresenter via binding and provide a Style to configure the control, including a ControlTemplate to provide its content. Thusly:

public class MyViewModel
{
  public ListBox SpecialControl { get; set; }
  public MyViewModel()
  {
    SpecialControl = new ListBox();
  }
}

and

<DataTemplate TargetType="{x:Type local:MyViewModel}">
  <DataTemplate.Resources>
    <Style TargetType="ListBox" ... />
  </DataTemplate.Resources>
  ...
  <ContentPresenter Content="{Binding SpecialControl}" />
</DataTemplate>

Other possibilities are:

  1. Have the view model actually derive from the Control class, then override OnApplyTemplate() and use GetTemplateChild to find a template item whose name starts with "PART_"
  2. Implement an attached property that takes a property name, finds that property in the DataContext, and sets it to the DependencyObject to which the property is attached.
  3. Implement your PropertySetter idea

My option #2 would look like this:

<DataTemplate TargetType="{x:Type MyViewModel}">
  ...
  <TextBox local:PropertyHelper.SetViewModelToThis="SpecialControl" />
  ...
</DataTemplate>

The code in the SetViewModelToThis PropertyChangedCallback would get the view model from the DataContext, reflect on it to find the "SpecialControl" property, then set it to the TextBox. Note that the implementation of SetViewModelToThis must take into account the possiblity that DataContext is not set right away, and that it maybe changed requiring the old setting to be removed and a new one made.

叶落知秋 2024-09-13 16:53:55

首先,控件的 DataContext 应该是 ViewModel 对象,而不是它的属性。其次,当您 TwoWayViewModel 的属性绑定到控件时,控件值的更改将更新(在您的情况下为“设置”)的值ViewModel 的属性。

First of all, the DataContext of the control should be the ViewModel object and not a property of it. Second, when you TwoWay bind a property of ViewModel to your control, changes in the control's value will update (in your case, 'set') the value of ViewModel's property.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文