【问题标题】:Propagate DependencyProperty default value传播 DependencyProperty 默认值
【发布时间】:2016-09-16 01:45:34
【问题描述】:

我有一个带有 ViewModel 的 WPF UserControl:

<MyUserControl ...>
    <Grid Name="UxRootContainer">
        <Grid.DataContext>
            <MyViewModel/>
        </Grid.DataContext>
    </Grid>
</MyUserControl>

这个 UserControl 有一个 DependencyProperty,它必须被传播到 ViewModel:

public static readonly DependencyProperty DurationProperty = 
     DependencyProperty.Register( "Duration", typeof(TimeSpan),
     typeof(MyUserControl), new FrameworkPropertyMetadata(TimeSpan.FromHour(1), OnDurationChanged ));

public TimeSpan Duration
{
    get { return (TimeSpan)GetValue(DurationProperty); }
    set { SetValue(DurationProperty, value); }
}

private static void OnDurationChanged(DependencyObject source, 
        DependencyPropertyChangedEventArgs e)
{
    MyUserControl control = source as MyUserControl;
    TimeSpan duration = (TimeSpan)e.NewValue;
    control.UxRootContainer.SetDuration(duration);
}

这工作正常,除了我们不会在OnDurationChanged 事件中收到默认值。

我知道我可以自己在构造函数中调用此方法,将默认持续时间放入一个常量中,但是:

  • 我必须为每个 DependencyProperty 创建一个常量
  • 即使最后我不使用默认值,我也必须调用它

关于如何将默认值传播到 ViewModel 的任何好的建议,前提是默认值是最后使用的那个(不是其他值集)。

【问题讨论】:

  • 这不是真正的 MVVM。在视图中创建 ViewModel 通常是个坏主意(对于主窗口可能没问题)。关键是UserControl 通常是ViewModel 的数据模板(这意味着您将在某处拥有ViewModel,但您的控件不能用作数据模板,因为它明确设置了DataContext)。之后你应该做相反的事情:ViewViewModel 获取值(无论是默认值还是新值)。这可以在Loaded 事件中完成(您将 100% 获得此事件)。
  • @Sinatr 这有点离题,但我很抱歉,但我不同意,MVVM 可以先查看或先查看模型。对于视图,我们使用 Prism ViewModelLocator 来获取 ViewModel。在这种情况下,这并不是真正的“视图”,而是真正的用户控件,可以在应用程序的许多不同上下文中使用。如果您的 TextBlock 必须找到自己必须显示的字段,您不觉得这很奇怪吗? ;)
  • @Sinatr 我后面有一个“ViewModel”,只是为了拥有我的内部可绑定属性。我同意这个名字可能不是最好的,但它包含与真正的 ViewModel 相同的逻辑(命令,INotifyPropertyChange 实现,...)
  • “你不觉得如果你的 TextBlock 必须自己找到它必须显示的字段会很奇怪吗” - 不。绑定已经使 View 元素不知道任何事情。 TextBlock 不知道最后会显示什么。如果您创建 2 个 ViewModel 实例,它们可能具有不同的值以显示相同的视图。因此,不要从视图中获得价值。 View 应该从 ViewModel 中获取价值。
  • @Sinatr 不。就像我说的,这个 UserControl 将在很多不同的 View 中使用,这些 View 已经有他们的 ViewModel。更具体地说,我的 UserControl 只有一个 Point 集合和一个持续时间(即要显示的点数)。它不知道这些点来自哪里,这完全取决于将使用哪个视图。与 TextBlock 相同。

标签: c# wpf mvvm user-controls


【解决方案1】:

我决定写一个答案(以防万一我错了:)。

在给定的情况下,您正在创建 自定义控件,这意味着它是其他视图中的简单控件。两点:

  • 不要创建 ViewModel;
  • 不要设置此控件的DataContext

在这种情况下,ViewModel 没有任何用途:没有要从中抽象的底层模型,没有任何东西(不是 View,也不是 ViewModel)会被重用。没有 ViewModel 存在默认值已经是依赖属性的默认值 = 问题解决了。

至于DataContext:如果您尝试在列表中使用此控件将项目属性绑定到它,您将始终必须通过父容器DataContext 引用它(因为控件已被覆盖并绑定"{Binding Text}" 将不要引用该项目Text 属性,而是引用控件ViewModel Text 属性,您将不得不执行类似"{Binding DataContext.Text, RelativeSource={RelativeSource FindAncestor, AncestorType=Grid}}" 的操作)。这在任何设计中都是不正确的。

【讨论】:

  • 您理解正确,但这就是为什么我有一个 ViewModel(或一个模型): 1. 根据我从依赖属性中获得的值,我必须计算其他几个值。因此,拥有一个模型允许我从依赖属性中设置值,并直接计算将绑定在我的 userControl 中的所有其他值。 2. 它允许我轻松地对背后的所有逻辑进行单元测试:如果用户将某些东西绑定到我的 DependencyProperty,我可以检查我的计算字段中是否有预期的结果。关于DataContext,我没有在Control上设置,而是在它的grid上设置。
  • 另外,我同意,这在设计上是一个“CustomControl”,但我没有这样解释,因为我认为 CustomControl 没有 XAML,只有适用于它的样式,没有?这不是我的情况。
  • 我不相信自定义控件需要视图模型,只需要页面。 +1
【解决方案2】:

除非我们不会在 OnDurationChanged 事件中收到默认值

让班级遵守INotifyPropertyChanged,然后让VM(或任何需要信息的人)订阅控件之外的事件。这样,当它发生变化(或设置了值)时,消费者就会收到通知。

看到这个问题的答案How To Raise Property Changed events on a Dependency Property?

关于如何将默认值传播到 ViewModel 的任何好的建议,前提是默认值是最后使用的那个(不是其他值集)。

您描述了需要编码以确定是否使用默认值的业务逻辑。如果没有上述逻辑,您的请求似乎无法完成。


正在发生的是竞争条件。无法绑定控件以通知对不存在的事物的更改...首先创建控件,然后设置默认值,然后您的控件绑定到它,当然这一切都是在运行时进行的。该操作是在设计时设置的,但这并不影响流程本身。

【讨论】:

  • 我的问题不是我的 ViewModel 值没有传播到 UserControl,而是恰恰相反,我的 DependencyProperty 的默认值没有提供给 UserControl 模型(它实际上是一个 UserControl,而不仅仅是一个“查看”。
  • @J4N 发生的是 race 条件。无法绑定控件以通知对不存在的事物的更改...首先创建控件,然后设置默认值,然后您的控件绑定到它,当然这一切都是在运行时进行的。该操作是在设计时设置的,但这并不影响流程本身。
  • 我明白,但有什么好的解决方法?有没有办法在分配默认值之前向 DP 表明我们要注册?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-31
  • 2016-03-18
  • 1970-01-01
  • 2022-07-22
  • 1970-01-01
  • 1970-01-01
  • 2011-01-17
相关资源
最近更新 更多