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

问题描述 投票:10回答:4

我一直找不到一个干净、简单的例子,来说明如何进行 正确 用WPF实现一个用户控件,该控件具有以下功能 DependencyProperty 在MVVM框架内。我的下面的代码失败了,每当我给用户控制分配一个 DataContext.

我正在尝试。

  1. 设置... DependencyProperty 从调用的ItemsControl中获取,并且
  2. 使该值 DependencyProperty 的ViewModel中可用。

我还有很多东西要学,衷心感谢任何帮助。

这是 ItemsControl 的最上层用户控件中,调用的是 InkStringView 用户控制 DependencyProperty TextInControl 另一个问题的例子)。

<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 用户控制 DependencyProperty.

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>
</UserControl>

后台代码文件。

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

    }
}
wpf mvvm data-binding user-controls dependency-properties
4个回答
16
投票

这也是你永远不应该设置DependencyProperty的众多原因之一。DataContext 直接从 UserControl 本身。

当你这样做的时候,你不能再使用任何其他的 DataContext 因为UserControl的 DataContext 被硬编码到一个实例上,而这个实例只有 UserControl 的访问权限,这有点违背了WPF拥有独立UI和数据层的最大优势之一。

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

  1. 一个独立的 UserControl 可以在任何地方使用,不需要特定的 DataContext 被要求。

    这种类型的 UserControl 通常暴露 DependencyProperties 的任何值,并会像这样使用。

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

    我能想到的典型例子是任何通用的东西,比如日历控件或弹出式控件。

  2. A UserControl 是为了与特定的 ModelViewModel 只有。

    这些 UserControls 对我来说更为常见,也可能是你要找的东西。我如何使用这样的UserControl的一个例子是这样的。

    <v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
    

    或者更常见的是,它将与一个隐式的 DataTemplate. 隐性 DataTemplate 是一个 DataTemplate 附带 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}" />
    

    ContentPresenter.ItemTemplateItemsControl.ItemTemplate 在使用这种方法时是需要的。

不要把这两种方法混为一谈,这样做不好:)。)


但不管怎么说,再详细解释一下你的具体问题吧

当你像这样创建你的UserControl时

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

你基本上是说

var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;

vw.DataContext 在XAML中没有任何地方指定,所以它将从父项中继承,这将导致

vw.DataContext = Strings[x];

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

然而,当你在你的UserControl构造函数中运行这个函数时

this.DataContext = new InkStringViewModel();

DataContext 被设置为一个值,所以不再自动从父代继承。

所以现在被运行的代码是这样的。

var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;

自然而然地, InkStringViewModel 没有一个叫做 text所以在运行时绑定失败。


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
投票

你需要在这里做两件事 (我假设Strings是一个...)。ObservableCollection<string>).

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

2) 改变

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

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

你的xaml是在ItemsControl上寻找一个 "Text "属性来绑定TextInControl的值。我放的xaml使用DataContext(正好是一个字符串)来绑定TextInControl。如果Strings实际上是一个ObservableCollection,有一个字符串属性的SomeProperty,你想绑定到它,那就把它改成这个。

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

1
投票

你就快成功了。 问题是你正在为你的UserControl创建一个ViewModel。 这是一个气味。

UserControls的外观和行为应该和其他控件一样,从外部来看。你正确地在控件上暴露了属性,并将内部控件绑定到这些属性上。 这都是正确的。

你失败的地方是试图为所有的东西创建一个ViewModel。 所以抛弃那个愚蠢的InkStringViewModel,让使用控件的人将他们的视图模型绑定到它上面。

如果你很想问 "视图模型中的逻辑怎么办? 如果我把它去掉,我就得把代码放在codebehind里!" 我回答:"是业务逻辑吗? 那无论如何都不应该嵌入到你的UserControl中。 而MVVM !=没有codebehind。 用codebehind来处理你的UI逻辑。 这是属于它的地方。"

© www.soinside.com 2019 - 2024. All rights reserved.