在XAML中从DependencyProperty到DataContext(ViewModel)的绑定

4
假设有这样一种情况:
我创建了一个名为"MyControl"的新控件,其中包含一个名为"SuperValue"的DependencyProperty。
现在,在XAML中,我将"SuperValue"设置为"TestValue":
<local:MyControl SuperValue="TestValue" />

这个控件有一个ViewModel(DataContext)。 我想把DependencyProperty的值(在这个例子中是“TestValue”)传递给ViewModel中的属性。 我该怎么做呢? 假设我的控件的ViewModel进行一些计算,例如:用户输入国家名称,控件会给出当前时间。 问题是:如何提供计算结果?假设ViewModel中有一个名为“Results”的公共属性。我想创建一个像“TextBox.Text”、“ListView.SelectedItem”这样的属性,它可以将ViewModel数据的一部分“提供给外部”。 例如TextBox和Text属性:
<TextBox Text={Binding GiveMeTextValue} />

在这种情况下,DP “Text”为外部提供了ViewModel属性,该属性当前存储输入的文本。
我希望能以同样的方式使用我的控件。

我们能否获得更多关于“无法设置”的上下文信息?你尝试过什么?你是否遇到编译错误或运行时异常,还是应用程序在你期望它执行时根本没有任何反应? - Kenogu Labz
你无法设置的 MyDependencyProperty 在哪里? - Rohit Vats
我在问题中添加了新的信息。 - Never
5个回答

3
我不知道我是否正确理解了你的问题:你想在XAML中设置一个静态的非绑定值到控件的DependencyProperty,并将控件的DataContext属性设置为这个静态值?如果需要这样做,你的概念有些问题,为什么不在ViewModel中提供相应的字段并将控件的DP与该字段绑定呢?
然而,你可以按照以下步骤来获得所需的效果:
在注册DP时定义一个PropertyChangedCallback。
// Dependency Property
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test", typeof(string),
typeof(MyControl), new FrameworkPropertyMetadata("123", new PropertyChangedCallback(OnTestChanged)));

在OnTestChanged方法中,将您的DataContext强制转换为ViewModel类型,并将相应的值设置为ViewModel的新值:
private static void OnTestChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MyControl c = d as MyControl;
            ViewModelType vm = c.DataContext as ViewModelType;
            vm.Property = e.New;
            Console.WriteLine(e.NewValue);
        }

这是您要求的内容吗?


是的,这应该可以工作,但我不确定它是否完全符合MVVM模式和组件分离规则。 - Never
这不是一个好的 MVVM 设计方式。View(XAML)定义中不应包含“真实数据”,即在逻辑上进行处理的数据。你到底想做什么?显然你想在 XAML 中定义一个值为“TestValue”的常量。为什么不在 ViewModel 中定义此常量,并只公开一个只读属性来返回它呢?然后,你可以将控件绑定到 ViewModel 的此属性上... - Marc

1
你可以向 MyControl 添加一个名为 InitialSuperValue 的属性,当设置了该属性时,会将 SuperValue 的值设置为它的值。然后编写如下 XAML 代码:
<local:MyControl InitialSuperValue="TestValue" SuperValue="{Binding SuperValueInViewModel, Mode=OneWayToSource}" />

是的,它可以工作,但每次使用控件时都写上“SuperValue = {Binding SuperValueInViewModel,Mode = OneWayToSource}”有点疯狂。 - Never
同意。如果您希望重复使用此绑定逻辑(即控件在项控件中,可能有数百个实例或类似情况),那么我建议您对控件进行模板化,并在模板中设置绑定。 - Albert Oldfield

1

如何在SomethingValueInDataContext属性的setter中设置MyDependencyProperty

编辑

您可以在控件使用的位置设置控件的DependencyProperty,而不是在其声明中设置。这将起作用(local是控件所在的命名空间)-

<Grid>
   <local:MyOwnControl MyDependencyProperty="{Binding Test}"/>
</Grid>

就像在xaml中创建一个实例时可以设置TextBoxWidth一样-

<TextBox Width="{Binding PropertyName}"/>

听起来不错,但我使用MVVM模式,因此ViewModel(DataContext)实际上并不知道视图的任何信息。 - Never
好的,你能详细说明为什么不能从视图中设置MyDependencyProperty吗?这个DP位于哪里? - Rohit Vats
它几乎是好的,但我的控件提供了 ViewModel 中执行操作的结果(向外部),因此我不能使用您的解决方案,因为我必须为 MyDependencyProperty 设置目标点。您的解决方案可能很好,但它使该属性成为无法使用的 Dependency Property(无法访问 get 或 set)。其中存在属性“Test”的 ViewModel 是 ControlViewModel,而不是“ApplicationViewModel”。 - Never

1

请注意,您的 XAML 根元素是 UserControl 而不是 MyOwnControl。UserControl 是 MyOwnControl 的基类;您的属性未在基类中定义。这就是为什么您无法从 UserControl 的根元素中引用 MyDependencyProperty 的原因。

使用您的示例,您可以切换绑定并获得所需的效果。

<UserControl 
    x:Class="namespace.MyOwnControl"
    x:Name="root">
    <UserControl.DataContext>
         <local:ControlViewModel  
             Test={Binding MyDependencyProperty, ElementName=root}" />
    </UserControl.DataContext>
</UserControl>

1
看起来不错,但是我遇到了错误:无法在类型为“ControlViewModel”的“Test”属性上设置“Binding”。只能在DependencyObject的DependencyProperty上设置“Binding”。我可以尝试将ControlViewModel从DependecyObject中创建,但我不确定这是否符合MVVM的自然和一致性要求? - Never
@Never:没错。ControlViewModel必须扩展DependencyObject,而Test必须是一个DependencyProperty。我总是让我的VM成为DependencyObjects。这使得生活变得更加轻松。此外,DP绑定是最快的绑定方式,因此您可以获得速度优势。这从来不是问题,直到它成为问题(几乎永远不会)。 - user1228
好的,所以我需要从DependencyObject派生,并以与我在问题中创建MyDependencyProperty相同的方式创建“Test”DP? - Never
@MrDosu:UI也是如此,因此适用相同的规则。这绝对不是什么大问题。 - user1228
实际上,关于这个主题有一个SO问题:https://dev59.com/ZXVC5IYBdhLWcg3wcgud - MrDosu
显示剩余9条评论

1

由于您正在使用MVVM设计模式,所有数据都应该与ViewModel相关。因此,您的DP应通过VM属性中的绑定进行设置。

如果测试数据将在Blend/VS设计器中使用,则可以检查该数据是否与Debug/Release有关...然后根据测试检查对属性进行某种赋值。


1
好的,但是怎么做呢?我如何通过绑定到我的VM属性来设置DP的值? - Never

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接