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

时间:2014-09-04 17:54:25

标签: wpf mvvm data-binding user-controls dependency-properties

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

我想:

  1. 从调用ItemsControl和
  2. 设置依赖项属性
  3. 使该依赖项属性的值可用于被调用的usercontrol的ViewModel。
  4. 我还有很多东西需要学习,真诚地感谢任何帮助。

    这是最顶层的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>
    

    以下是具有依赖项属性的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>
    

    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));
    
    }
    

    }

4 个答案:

答案 0 :(得分:13)

这是您不应直接从DataContext本身设置UserControl的众多原因之一。

执行此操作时,您无法再使用任何其他DataContext,因为UserControl的DataContext被硬编码到只有UserControl有权访问的实例哪种方式击败了WPF在拥有单独的UI和数据层方面的最大优势之一。

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

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

    这种类型的UserControl通常会为DependencyProperties提供所需的任何值,并且会像这样使用:

    <v:InkStringView TextInControl="{Binding SomeValue}" />
    

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

  2. 仅适用于特定UserControlModel的{​​{1}}。

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

    UserControls

    或者更频繁地,它将与隐式<v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" /> 一起使用。隐式DataTemplateDataTemplateDataTemplate而不是DataType,WPF会在想要呈现指定类型的对象时自动使用此模板。

    Key

    使用此方法时不需要<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}" /> ContentPresenter.ItemTemplate

  3. 不要混用这两种方法,它不顺利:))


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

    当您像这样创建UserControl时

    ItemsControl.ItemTemplate
    你基本上是在说

    <v:InkStringView TextInControl="{Binding text}"  />
    
    未在XAML中的任何位置指定

    var vw = new InkStringView() vw.TextInControl = vw.DataContext.text; ,因此它从父项继承,从而导致

    vw.DataContext

    所以设置vw.DataContext = Strings[x]; 的绑定有效,并且在运行时解析得很好。

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

    TextInControl = vw.DataContext.text

    this.DataContext = new InkStringViewModel(); 设置为一个值,因此不再自动从父级继承。

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

    DataContext

    当然,var vw = new InkStringView() vw.DataContext = new InkStringViewModel(); vw.TextInControl = vw.DataContext.text; 没有名为InkStringViewModel的属性,因此绑定在运行时失败。

答案 1 :(得分:3)

好像你正在将父视图的模型与UC的模型混合在一起。

以下是与您的代码匹配的示例:

MainViewModel:

using System.Collections.Generic;

namespace UCItemsControl
{
    public class MyString
    {
        public string text { get; set; }
    }

    public class MainViewModel
    {
        public ObservableCollection<MyString> Strings { get; set; }

        public MainViewModel()
        {
            Strings = new ObservableCollection<MyString>
            {
                new MyString{ text = "First" },
                new MyString{ text = "Second" },
                new MyString{ text = "Third" }
            };
        }
    }
}

使用它的MainWindow:

<Window x:Class="UCItemsControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:v="clr-namespace:UCItemsControl"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <v:MainViewModel></v:MainViewModel>
    </Window.DataContext>
    <Grid>
        <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}"  />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

您的UC(没有DataContext集合):

public partial class InkStringView : UserControl
{
    public InkStringView()
    {
        InitializeComponent();
    }

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

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

(您的XAML没问题)

有了这个我可以获得我猜的预期结果,一个值列表:

First
I am row 1
Second
I am row 1
Third
I am row 1

答案 2 :(得分:2)

你需要在这里做两件事(我假设字符串是ObservableCollection<string>)。

1)从InkStringView构造函数中删除this.DataContext = new InkStringViewModel();。 DataContext将是Strings ObservableCollection的一个元素。

2)改变

<v:InkStringView TextInControl="{Binding text, ElementName=self}"  />

<v:InkStringView TextInControl="{Binding }" />

你拥有的xaml正在寻找一个&#34; Text&#34; ItemsControl上的属性将值TextInControl绑定到。我使用DataContext(恰好是一个字符串)来绑定TextInControl的xaml。如果Strings实际上是一个ObservableCollection,其中包含要绑定的字符串属性SomeProperty,则将其更改为此。

<v:InkStringView TextInControl="{Binding SomeProperty}" />

答案 3 :(得分:1)

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

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

失败的地方是尝试为所有内容创建ViewModel。所以抛弃了愚蠢的InkStringViewModel并让任何使用该控件的人将他们的视图模型绑定到它。

如果你很想问“视图模型中的逻辑怎么样?如果我摆脱它,我将不得不将代码置于代码隐藏中!”我回答说,“它是业务逻辑吗?不管怎么说都不应该嵌入你的UserControl中。而且MVVM!=没有代码隐藏。使用代码隐藏来实现你的UI逻辑。这就是它所属的地方。”