WPF中ItemTemplate绑定错误:为何绑定父元素而非ItemsSource项?
问题描述
我的应用由包含多个Product的Category组成,给定一个Category,我希望为其每个Product显示对应的元素,编写的CategoryControl代码如下:
CategoryControl XAML
<UserControl x:Class="MyApp.CategoryControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MyApp" > <StackPanel> <TextBlock>Products:</TextBlock> <ItemsControl ItemsSource="{Binding Path=Products}"> <ItemsControl.ItemTemplate> <DataTemplate> <local:ProductControl Product="{Binding}" /> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </UserControl>
CategoryControl C#
public partial class CategoryControl : UserControl { public Product[] Products { get; set; } }
其中ProductControl用于展示Product的部分信息,并在点击Product名称时显示/折叠额外信息,代码如下:
ProductControl XAML
<UserControl x:Class="MyApp.ProductControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MyApp" > <StackPanel> <TextBlock MouseDown="ProductName_Click" Text="{Binding Path=Product.Name}" /> <TextBlock Text="{Binding Path=Product.Description}" Visibility="{Binding Path=IsCollapsed, Converter={local:BooleanToVisiblity}}" /> </StackPanel> </UserControl>
ProductControl C#
public partial class ProductControl : UserControl { public bool IsCollapsed { get; set; } = true; public Product Product { get => (Product)GetValue(ProductProperty); set => SetValue(ProductProperty, value); } public static readonly DependencyProperty ProductProperty = DependencyProperty.Register( "Product", typeof(Product), typeof(FrameworkElement) ); public ProductControl () { DataContext = this; InitializeComponent(); } public void ProductName_Click(object sender, EventArgs e) { IsCollapsed = !IsCollapsed; PropertyChanged("IsCollapsed"); } }
运行时每个Product都会出现以下错误:
System.Windows.Data Error: 1 : Cannot create default converter to perform 'one-way' conversions between types 'MyApp.ProductControl' and 'MyApp.Product'. Consider using Converter property of Binding. BindingExpression:Path=; DataItem='ProductControl' (Name=''); target element is 'ProductControl' (Name=''); target property is 'Product' (type 'Product') System.Windows.Data Error: 5 : Value produced by BindingExpression is not valid for target property.; Value='MyApp.ProductControl' BindingExpression:Path=; DataItem='ProductControl' (Name=''); target element is 'ProductControl' (Name=''); target property is 'Product' (type 'Product')
请问哪里出错了?
问题原因与修复方案
核心问题
错误的根源是**ProductControl构造函数里强制设置了DataContext = this**,直接打断了父控件的数据传递逻辑:
- 在
CategoryControl的ItemsControl中,DataTemplate里的{Binding}原本应该指向当前迭代的Product实例; - 但
ProductControl把自身设为DataContext后,{Binding}就变成了绑定ProductControl对象本身,自然会出现类型不匹配的转换错误(无法把ProductControl转换成Product)。
修复步骤
1. 移除DataContext = this的强制赋值
删掉ProductControl构造函数里的这行代码:
public ProductControl () { // 删掉这一行:DataContext = this; InitializeComponent(); }
2. 调整控件内部绑定,明确指向自身
因为不再把DataContext设为自身,需要通过ElementName或RelativeSource来绑定控件的属性:
<UserControl x:Class="MyApp.ProductControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MyApp" x:Name="Self" <!-- 给控件命名,方便内部绑定 --> > <StackPanel> <TextBlock MouseDown="ProductName_Click" Text="{Binding ElementName=Self, Path=Product.Name}" /> <TextBlock Text="{Binding ElementName=Self, Path=Product.Description}" Visibility="{Binding ElementName=Self, Path=IsCollapsed, Converter={local:BooleanToVisiblity}}" /> </StackPanel> </UserControl>
3. 修复IsCollapsed的属性通知机制
当前IsCollapsed是普通CLR属性,修改后不会触发UI更新,需要实现INotifyPropertyChanged接口:
public partial class ProductControl : UserControl, INotifyPropertyChanged { private bool _isCollapsed = true; public bool IsCollapsed { get => _isCollapsed; set { if (_isCollapsed != value) { _isCollapsed = value; OnPropertyChanged(nameof(IsCollapsed)); } } } // 修正依赖属性的所有者类型(之前用了FrameworkElement,应该改为ProductControl) public Product Product { get => (Product)GetValue(ProductProperty); set => SetValue(ProductProperty, value); } public static readonly DependencyProperty ProductProperty = DependencyProperty.Register( "Product", typeof(Product), typeof(ProductControl) ); public ProductControl () { InitializeComponent(); } public void ProductName_Click(object sender, EventArgs e) { IsCollapsed = !IsCollapsed; } // INotifyPropertyChanged接口实现 public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
4. 修正依赖属性的所有者类型
之前注册ProductProperty时,第三个参数错误地用了typeof(FrameworkElement),必须改为控件自身的类型typeof(ProductControl),否则依赖属性的关联逻辑会异常。
补充说明
- 不要随意修改UserControl的
DataContext,父控件通常会通过DataContext传递绑定数据,强制覆盖会打断这个传递链; - 对于UserControl内部的属性绑定,优先用
ElementName或RelativeSource={RelativeSource Self}来明确绑定控件自身,避免和外部传入的DataContext冲突。
内容的提问来源于stack exchange,提问作者RobertAKARobin
相关产品推荐
相关产品推荐

