wpf 如何在 MVVM 框架中正确绑定到用户控件的依赖属性
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25672037/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to correctly bind to a dependency property of a usercontrol in a MVVM framework
提问by Alan Wayne
I have been unable to find a clean, simple, example of how to correctlyimplement a usercontrol with WPF that has a DependencyPropertywithin the MVVM framework. My code below fails whenever I assign the usercontrol a DataContext.
我一直无法找到一个干净、简单的示例,说明如何使用 MVVM 框架内的WPF正确实现用户控件DependencyProperty。每当我分配 usercontrol a 时,我下面的代码就会失败DataContext。
I am trying to:
我在尝试着:
- Set the
DependencyPropertyfrom the calling ItemsControl , and - Make the value of that
DependencyPropertyavailable to the ViewModel of the called usercontrol.
- 将
DependencyProperty来自主叫的ItemsControl,并 - 使该值
DependencyProperty可用于被调用用户控件的 ViewModel。
I still have a lot to learn and sincerely appreciate any help.
我还有很多东西要学,真诚地感谢任何帮助。
This is the ItemsControlin the topmost usercontrol that is making the call to the InkStringViewusercontrol with the DependencyPropertyTextInControl(example from another question).
这是ItemsControl最顶层的用户控件,它InkStringView使用DependencyPropertyTextInControl(来自另一个问题的示例)调用用户控件。
<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>
Here is the InkStringViewusercontrol with the DependencyProperty.
这是InkStringView带有DependencyProperty.
XAML:
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>
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));
}
}
回答by Rachel
That is one of the many reasons you should never set the DataContextdirectly from the UserControlitself.
这是您永远不应该DataContext直接从UserControl本身设置 的众多原因之一。
When you do so, you can no longer use any other DataContextwith it because the UserControl's DataContextis hardcoded to an instance that only the UserControlhas access to, which kind of defeats one of WPF's biggest advantages of having separate UI and data layers.
当你这样做时,你不能再使用DataContext它,因为 UserControlDataContext被硬编码到一个只有它UserControl可以访问的实例,这违背了 WPF 具有独立 UI 和数据层的最大优势之一。
There are two main ways of using UserControlsin WPF
UserControlsWPF中主要有两种使用方式
A standalone
UserControlthat can be used anywhere without a specificDataContextbeing required.This type of
UserControlnormally exposesDependencyPropertiesfor any values it needs, and would be used like this:<v:InkStringView TextInControl="{Binding SomeValue}" />Typical examples I can think of would be anything generic such as a Calendar control or Popup control.
A
UserControlthat is meant to be used with a specificModelorViewModelonly.These
UserControlsare far more common for me, and is probably what you are looking for in your case. An example of how I would use such a UserControl would be this:<v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />Or more frequently, it would be used with an implicit
DataTemplate. An implicitDataTemplateis aDataTemplatewith aDataTypeand noKey, and WPF will automatically use this template anytime it wants to render an object of the specified type.<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}" />No
ContentPresenter.ItemTemplateorItemsControl.ItemTemplateis needed when using this method.
一个独立的
UserControl,可以在任何地方使用而无需特定的DataContext。这种类型
UserControl通常会DependencyProperties为其需要的任何值公开,并且可以这样使用:<v:InkStringView TextInControl="{Binding SomeValue}" />我能想到的典型例子是任何通用的东西,比如日历控件或弹出控件。
UserControl旨在与特定Model或ViewModel仅一起使用的A。这些
UserControls对我来说更为常见,并且可能是您正在寻找的案例。我将如何使用这样的 UserControl 的一个例子是:<v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />或者更频繁地,它将与隐式
DataTemplate. 隐式DataTemplate是DataTemplate带有 aDataType和 no 的 aKey,并且 WPF 将在想要呈现指定类型的对象的任何时候自动使用此模板。<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或ItemsControl.ItemTemplate不需要。
Don't mix these two methods up, it doesn't go well :)
不要把这两种方法混在一起,效果不好:)
But anyways, to explain your specific problem in a bit more detail
但无论如何,更详细地解释您的具体问题
When you create your UserControl like this
当您像这样创建 UserControl 时
<v:InkStringView TextInControl="{Binding text}" />
you are basically saying
你基本上是在说
var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;
vw.DataContextis not specified anywhere in the XAML, so it gets inherited from the parent item, which results in
vw.DataContext未在 XAML 中的任何位置指定,因此它从父项继承,从而导致
vw.DataContext = Strings[x];
so your binding that sets TextInControl = vw.DataContext.textis valid and resolves just fine at runtime.
所以你设置的绑定TextInControl = vw.DataContext.text是有效的,并且在运行时解析得很好。
However when you run this in your UserControl constructor
但是,当您在 UserControl 构造函数中运行它时
this.DataContext = new InkStringViewModel();
the DataContextis set to a value, so no longer gets automatically inherited from the parent.
将DataContext被设置为一个值,所以不再被自动继承自父。
So now the code that gets run looks like this:
所以现在运行的代码如下所示:
var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;
and naturally, InkStringViewModeldoes not have a property called text, so the binding fails at runtime.
自然地,InkStringViewModel没有名为 的属性text,因此绑定在运行时失败。
回答by Pragmateek
Seems like you are mixing the model of the parent view with the model of the UC.
似乎您正在将父视图的模型与 UC 的模型混合在一起。
Here is a sample that matches your code:
这是一个与您的代码匹配的示例:
The 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" }
};
}
}
}
The MainWindow that uses it:
使用它的 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>
Your UC (no set of DataContext):
您的 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));
}
(Your XAML is OK)
(你的 XAML 没问题)
With that I can obtain what I guess is the expected result, a list of values:
有了它,我可以获得我猜测的预期结果,一个值列表:
First
I am row 1
Second
I am row 1
Third
I am row 1
回答by Lee O.
You need to do 2 things here (I'm assuming Strings is an ObservableCollection<string>).
你需要在这里做两件事(我假设字符串是一个ObservableCollection<string>)。
1) Remove this.DataContext = new InkStringViewModel();from the InkStringView constructor. The DataContext will be one element of the Strings ObservableCollection.
1)this.DataContext = new InkStringViewModel();从 InkStringView 构造函数中删除。DataContext 将是 Strings ObservableCollection 的一个元素。
2) Change
2) 改变
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
to
到
<v:InkStringView TextInControl="{Binding }" />
The xaml you have is looking for a "Text" property on the ItemsControl to bind the value TextInControl to. The xaml I put using the DataContext (which happens to be a string) to bind TextInControl to. If Strings is actually an ObservableCollection with a string Property of SomeProperty that you want to bind to then change it to this instead.
您拥有的 xaml 正在寻找 ItemsControl 上的“Text”属性以将值 TextInControl 绑定到。我使用 DataContext(恰好是一个字符串)放置的 xaml 将 TextInControl 绑定到。如果 Strings 实际上是一个 ObservableCollection 并且你想绑定一个字符串属性 SomeProperty 然后将它改为 this 。
<v:InkStringView TextInControl="{Binding SomeProperty}" />
回答by Lee O.
You're almost there. The problem is that you're creating a ViewModel for your UserControl. This is a smell.
您快到了。问题是您正在为 UserControl 创建一个 ViewModel。这是一种气味。
UserControls should look and behave just like any other control, as viewed from the outside. You correctly have exposed properties on the control, and are binding inner controls to these properties. That's all correct.
从外部看,UserControls 的外观和行为应该与任何其他控件一样。您正确地在控件上公开了属性,并将内部控件绑定到这些属性。那都是正确的。
Where you fail is trying to create a ViewModel for everything. So ditch that stupid InkStringViewModel and let whoever is using the control to bind their view model to it.
你失败的地方是试图为所有东西创建一个 ViewModel。所以抛弃那个愚蠢的 InkStringViewModel 并让任何使用控件的人将他们的视图模型绑定到它。
If you are tempted to ask "what about the logic in the view model? If I get rid of it I'll have to put code in the codebehind!" I answer, "is it business logic? That shouldn't be embedded in your UserControl anyhow. And MVVM != no codebehind. Use codebehind for your UI logic. It's where it belongs."
如果你想问“视图模型中的逻辑呢?如果我摆脱它,我将不得不将代码放在代码隐藏中!” 我回答说,“这是业务逻辑吗?无论如何,它不应该嵌入到您的 UserControl 中。而且 MVVM != 没有代码隐藏。为您的 UI 逻辑使用代码隐藏。它属于它。”

