WPF中ItemTemplate无法设置自定义UserControl依赖属性问题
咱们来拆解你遇到的问题:你的PersonButton控件的FirstName和LastName依赖属性在DataTemplate里无法被正确赋值,按钮内容始终停留在构造时的初始值。核心原因有两个:
1. 依赖属性变更未触发按钮内容更新
你在PersonButton的构造函数里只给theButton.Content赋值了一次,但当DataTemplate通过绑定更新FirstName和LastName时,没有任何逻辑同步按钮的显示内容。另外要注意:WPF的绑定系统直接调用GetValue/SetValue操作依赖属性,不会走你写的C#属性setter,所以你在setter里加断点看不到绑定触发的更新是正常的。
2. DataTemplate里的属性赋值方式错误
你当前在DataTemplate里给PersonButton写的是硬编码值:
<local:PersonButton Grid.Column="0" x:Name="thePersonButton" FirstName="Ken" LastName="Jones"/>
这显然不是你想要的,需要把这些属性绑定到Person模型对应的字段上,才能让每个列表项显示对应的数据。
修复步骤
步骤1:让PersonButton响应依赖属性的变更
有两种实现方式,推荐第二种更符合WPF绑定思想的方案:
方式一:给依赖属性添加变更回调
修改PersonButton的依赖属性注册代码,添加回调函数同步按钮内容:
public static readonly DependencyProperty FirstNameProperty = DependencyProperty.Register("FirstName", typeof(string), typeof(PersonButton), new PropertyMetadata(string.Empty, OnNamePropertyChanged)); public static readonly DependencyProperty LastNameProperty = DependencyProperty.Register("LastName", typeof(string), typeof(PersonButton), new PropertyMetadata(string.Empty, OnNamePropertyChanged)); private static void OnNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var button = d as PersonButton; button?.UpdateButtonContent(); } private void UpdateButtonContent() { theButton.Content = $"{FirstName} {LastName}"; }
然后把构造函数里的内容赋值替换成调用更新方法:
public PersonButton() { InitializeComponent(); FirstName = "Ira"; LastName = "Jacobs"; UpdateButtonContent(); }
方式二:在XAML里直接绑定按钮Content
不用写后台回调,直接用WPF的绑定机制实现内容同步:
修改PersonButton.xaml,用MultiBinding组合两个属性:
<Grid> <Button Name="theButton" Padding="5,0,5,0"> <Button.Content> <MultiBinding StringFormat="{}{0} {1}"> <Binding Path="FirstName" RelativeSource="{RelativeSource AncestorType=local:PersonButton}"/> <Binding Path="LastName" RelativeSource="{RelativeSource AncestorType=local:PersonButton}"/> </MultiBinding> </Button.Content> </Button> </Grid>
这样当FirstName或LastName变化时,按钮内容会自动更新。
步骤2:修正DataTemplate里的绑定逻辑
把DataTemplate里的PersonButton属性改成绑定到Person模型:
<DataTemplate DataType="{x:Type model:Person}"> <Grid Name="theListBoxItemGrid"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <local:PersonButton Grid.Column="0" x:Name="thePersonButton" FirstName="{Binding FirstName}" LastName="{Binding LastName}"/> <TextBlock Grid.Column="1" Name="theFirstName" Text="{Binding FirstName}" Margin="5,0,5,0"/> <TextBlock Grid.Column="2" Name="theLastName" Text="{Binding LastName}" Margin="0,0,5,0"/> </Grid> </DataTemplate>
额外优化:支持模型变更通知
如果后续需要捕获模型中列表项的变更,要给Person模型实现INotifyPropertyChanged接口,这样模型属性更新时UI能自动同步:
namespace Model { public class Person : INotifyPropertyChanged { private string _firstName; private string _lastName; public string FirstName { get => _firstName; set { _firstName = value; OnPropertyChanged(); } } public string LastName { get => _lastName; set { _lastName = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } // 构造函数保持不变 public Person() { FirstName = "John"; LastName = "Doe"; } public Person(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } } public class People : ObservableCollection<Person> { } }
完成以上修改后,你的PersonButton就能正确绑定到模型数据,并且能响应模型的变更了。
内容的提问来源于stack exchange,提问作者harleygnuya

