如何正确绑定到MVVM框架中的usercontrol的依赖项属性

Ala*_*yne 10 data-binding wpf user-controls dependency-properties mvvm

我一直无法找到一个干净,简单的示例,说明如何使用在MVVM框架中具有依赖项属性的WPF 正确实现usercontrol.每当我为usercontrol分配一个datacontext时,我的代码都会失败.

我在尝试着:

  1. 从调用ItemsControl设置依赖项属性,和
  2. 使该依赖项属性的值可用于被调用的usercontrol的ViewModel.

我还有很多东西需要学习,并真诚地感谢任何帮助.

这是最顶层的usercontrol中的ItemsControl,它使用依赖项属性TextInControl调用InkStringView用户控件(来自另一个问题的示例).

   <ItemsControl 
                ItemsSource="{Binding Strings}" x:Name="self" >

        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>


        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <DataTemplate.Resources>
                    <Style TargetType="v:InkStringView">
                        <Setter Property="FontSize" Value="25"/>
                        <Setter Property="HorizontalAlignment" Value="Left"/>
                    </Style>
                </DataTemplate.Resources>

                <v:InkStringView TextInControl="{Binding text, ElementName=self}"  />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
Run Code Online (Sandbox Code Playgroud)

这是带有依赖项属性的InkStringView用户控件.

 XAML:
<UserControl x:Class="Nova5.UI.Views.Ink.InkStringView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         x:Name="mainInkStringView"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>   
        <RowDefinition/>
    </Grid.RowDefinitions>

    <TextBlock Grid.Row="0" Text="{Binding TextInControl, ElementName=mainInkStringView}" />
    <TextBlock Grid.Row="1" Text="I am row 1" />
</Grid>
Run Code Online (Sandbox Code Playgroud)

Code-Behind file:
namespace Nova5.UI.Views.Ink
{
public partial class InkStringView : UserControl
{
    public InkStringView()
    {
        InitializeComponent();
        this.DataContext = new InkStringViewModel();   <--THIS PREVENTS CORRECT BINDING, WHAT
    }                                                   --ELSE TO DO?????

    public String TextInControl
    {
        get { return (String)GetValue(TextInControlProperty); }
        set { SetValue(TextInControlProperty, value); }
    }

    public static readonly DependencyProperty TextInControlProperty =
        DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));

}
Run Code Online (Sandbox Code Playgroud)

}

Rac*_*hel 13

这是你不应该DataContext直接从UserControl自身设置的众多原因之一.

当你这样做时,你就不能再使用任何其他的DataContext了,因为UserControl DataContext被硬编码到只有UserControl有权访问的实例,这种类型会破坏WPF拥有单独的UI和数据层的最大优势之一.

UserControls在WPF中有两种主要的使用方法

  1. 独立的UserControl,可以在任何地方使用,无需特定DataContext的要求.

    这种类型UserControl通常会暴露DependencyProperties它需要的任何值,并将像这样使用:

    <v:InkStringView TextInControl="{Binding SomeValue}" />
    
    Run Code Online (Sandbox Code Playgroud)

    我能想到的典型例子是任何通用的例如Calendar控件或Popup控件.

  2. A UserControl表示与特定ModelViewModel仅使用.

    这些UserControls对我来说更常见,而且可能就是你在寻找的东西.我如何使用这样的UserControl的一个例子是:

    <v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
    
    Run Code Online (Sandbox Code Playgroud)

    或者更频繁地,它将与隐含一起使用DataTemplate.隐式DataTemplate是a DataTemplate和a DataType,否则KeyWPF会在想要呈现指定类型的对象时自动使用此模板.

    <Window.Resources>
        <DataTemplate DataType="{x:Type m:InkStringViewModel}">
            <v:InkStringView />
        </DataTemplate>
    <Window.Resources>
    
    <!-- Binding to a single ViewModel -->
    <ContentPresenter Content="{Binding MyInkStringViewModelProperty}" />
    
    <!-- Binding to a collection of ViewModels -->
    <ItemsControl ItemsSource="{Binding MyCollectionOfInkStringViewModels}" />
    
    Run Code Online (Sandbox Code Playgroud)

    使用此方法时不需要ContentPresenter.ItemTemplateItemsControl.ItemTemplate不需要.

不要混淆这两种方法,它不顺利:)


但无论如何,要更详细地解释您的具体问题

当您像这样创建UserControl时

<v:InkStringView TextInControl="{Binding text}"  />
Run Code Online (Sandbox Code Playgroud)

你基本上是在说

var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;
Run Code Online (Sandbox Code Playgroud)

vw.DataContext 未在XAML中的任何位置指定,因此它从父项继承,从而导致

vw.DataContext = Strings[x];
Run Code Online (Sandbox Code Playgroud)

所以你的绑定设置TextInControl = vw.DataContext.text是有效的,并在运行时解析得很好.

但是,当您在UserControl构造函数中运行它时

this.DataContext = new InkStringViewModel();
Run Code Online (Sandbox Code Playgroud)

DataContext其设置为值,因此不再自动从父级继承.

所以现在运行的代码如下所示:

var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;
Run Code Online (Sandbox Code Playgroud)

当然,InkStringViewModel没有调用的属性text,因此绑定在运行时失败.


小智 5

你快到了。问题是您正在为 UserControl 创建一个 ViewModel。这是一种气味。

从外部看,用户控件的外观和行为应该与任何其他控件一样。您正确地在控件上公开了属性,并将内部控件绑定到这些属性。这都是正确的。

失败的地方是尝试为所有内容创建一个 ViewModel。因此,抛弃那个愚蠢的 InkStringViewModel,让任何使用该控件的人将其视图模型绑定到它。

如果您想问“视图模型中的逻辑怎么样?如果我摆脱它,我将不得不将代码放在代码隐藏中!” 我回答说:“这是业务逻辑吗?无论如何,它都不应该嵌入到您的 UserControl 中。并且 MVVM!= 没有代码隐藏。为您的 UI 逻辑使用代码隐藏。这就是它所属的地方。”

  • @Pragmateek,有时牛会跳过月亮,但这也与问题或这个答案没有任何关系。 (2认同)
  • @Pragmateek Eh,我已经存在了足够长的时间,不用担心投票:)但是,当您发现自己创建专门供用户控件使用的视图模型时,我会认为它始终是一种气味。它只是无法正常工作。 (2认同)
  • @AlanWayne 如果它是 UI 代码,请将其放入代码隐藏中,正如我所说。您需要重构用户控制之外的业务逻辑,这并不意味着创建新的虚拟机。也许您的用户控件包含太多 UI?也许应该对其进行分解,以便您的业务逻辑能够很好地适应使用用户控件的页面/窗口的虚拟机?如果我有你的代码,很难准确地告诉你如何完成,但合理的指南是说“我的 UI 需要什么?添加一个属性,以便 VM 绑定可以将其提供给我。” (2认同)