容器如何动态生成子控件?
-需求:
- 根据指定的集合数据,动态生成一系列控件。
- 通过对数据的修改,自动更新控件内容的显示。
比如: 在WrapPanel中,通过集合{1,2,3,4,5}中的每一个数据,生成一个TextBlock,并显示集合元素对应的值。
-分析:WrapPanel属性中,没有可以绑定数数据源的依赖属性,所以首先我们需要定义一个附加属性用于容器数据的绑定,再通过数据来生成对应的控件。依赖属性定义如下:
public class ContainerUtils
{
public static List<int> GetItemsSource(DependencyObject obj)
{
return (List<int>)obj.GetValue(ItemsSourceProperty);
}
public static void SetItemsSource(DependencyObject obj, List<int> value)
{
obj.SetValue(ItemsSourceProperty, value);
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.RegisterAttached("ItemsSource", typeof(List<int>), typeof(ContainerUtils), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyChanged)));
//当ItemsSourceProperty属性的值发生改变时触发(比如:数据绑定时,重新赋值时)
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//附加对象的目标(你给谁附加ItemsSource属性)
WrapPanel wrapPanel = d as WrapPanel;
//e.NewValue: ItemsSource属性的值发生改变,改变后的值
List<int> valueList = e.NewValue as List<int>;
if(valueList != null && valueList.Count > 0)
{
foreach (var iValue in valueList)
{
wrapPanel.Children.Add(new TextBlock
{
Text = iValue.ToString(),
Margin = new Thickness(5)
});
}
}
}
}
ViewModels数据定义:
public class ContainerTestViewModel
{
public List<int> Data { get; set; } = new List<int> { 1, 2, 3, 4, 5 };
}
xaml页面数据绑定:
<Window x:Class="WpfDemo.Views.ContainerTestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:extend="clr-namespace:WpfDemo.Extend"
xmlns:viewmodels="clr-namespace:WpfDemo.ViewModels"
mc:Ignorable="d" Height="450" Width="800">
<Window.DataContext>
<viewmodels:ContainerTestViewModel />
</Window.DataContext>
<WrapPanel extend:ContainerUtils.ItemsSource="{Binding Data}" />
</Window>
运行结果:
result.png
改变集合的数据,页面为什么没有变化?
我试图去改变集合的数据时,发现页面没有变化,为什么呢? 因为集合的数据更新通知注册方法OnPropertyChanged内部,我们对TextBlock的Text属性是直接赋值的,没有任何的数据绑定。当我试图更改TextBlock的生成代码,更改如下:
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//附加对象的目标(你给谁附加ItemsSource属性)
WrapPanel wrapPanel = d as WrapPanel;
//e.NewValue: ItemsSource属性的值发生改变,改变后的值
List<int> valueList = e.NewValue as List<int>;
if(valueList != null && valueList.Count > 0)
{
foreach (var iValue in valueList)
{
TextBlock tb = new TextBlock { Margin = new Thickness(5) };
//将TextBlock的Text属性,绑定到数据源 iValue上
tb.SetBinding(TextBlock.TextProperty, new Binding() { Source = iValue });
wrapPanel.Children.Add(tb);
}
}
}
并在viewmodel中,添加了一行更新数据源的第3个元素的值,代码如下:
public ContainerTestViewModel()
{
Task.Delay(2000).ContinueWith(t => Data[2] = 1888); //让页面先绑定数据源,延迟2秒后去执行修改数据源
}
为什么要用异步去等待修改数据源呢? 因为如果用同步修改,那可能会出现在页面绑定数据源之前,就先更改了数据源,并非是先绑定后修改的。
运行程序后,发现数据没有任何变化,仔细检查,发现问题, tb.SetBinding(TextBlock.TextProperty, new Binding() { Source = iValue }); 绑定的数据源是一个int值,int类型的数据,并没有实现INotifyPropertyChanged接口,因为它在变化时,不会有更新通知。我们将再次更改数据源如下:
public class DataModel : INotifyPropertyChanged
{
private int value;
public int Value
{
get { return value; }
set
{
this.value = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public List<DataModel> Data { get; set; } = new List<DataModel>
{
new DataModel{ Value = 1 },
new DataModel{ Value = 2 },
new DataModel{ Value = 3 },
new DataModel{ Value = 4 },
new DataModel{ Value = 5 }
};
我们将int数据包一层,将集合的元素更改为一个带属性变化通知的DataModel类型。数据源变更 ,我们的数据绑定也需要略做修改,数据源变化后,依赖属性的类型也要相应做调整,更改如下:
public class ContainerUtils
{
public static List<DataModel> GetItemsSource(DependencyObject obj)
{
return (List<DataModel>)obj.GetValue(ItemsSourceProperty);
}
public static void SetItemsSource(DependencyObject obj, List<DataModel> value)
{
obj.SetValue(ItemsSourceProperty, value);
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.RegisterAttached("ItemsSource", typeof(List<DataModel>), typeof(ContainerUtils), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyChanged)));
//当ItemsSourceProperty属性的值发生改变时触发(比如:数据绑定时,重新赋值时)
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//附加对象的目标(你给谁附加ItemsSource属性)
WrapPanel wrapPanel = d as WrapPanel;
//e.NewValue: ItemsSource属性的值发生改变,改变后的值
List<DataModel> valueList = e.NewValue as List<DataModel>;
if(valueList != null && valueList.Count > 0)
{
foreach (var iValue in valueList)
{
TextBlock tb = new TextBlock { Margin = new Thickness(5) };
//将TextBlock的Text属性,绑定到数据源 iValue的Value属性上
tb.SetBinding(TextBlock.TextProperty, new Binding("Value") { Source = iValue });
wrapPanel.Children.Add(tb);
}
}
}
}
运行后,果然在2秒后,界面变化如下:
result.png
试图向集合增删数据,页面没有变化?
其实是因为向集合添加/删除数据时,List并不会做更改通知,我们需要换成ObservableCollection集合,每次更新的时候,先清空容器的子控件,再逐个添加即可。修改后代码如下:
public class ContainerUtils
{
public static ObservableCollection<DataModel> GetItemsSource(DependencyObject obj)
{
return (ObservableCollection<DataModel>)obj.GetValue(ItemsSourceProperty);
}
public static void SetItemsSource(DependencyObject obj, ObservableCollection<DataModel> value)
{
obj.SetValue(ItemsSourceProperty, value);
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.RegisterAttached("ItemsSource", typeof(ObservableCollection<DataModel>), typeof(ContainerUtils), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyChanged)));
//当ItemsSourceProperty属性的值发生改变时触发(比如:数据绑定时,重新赋值时)
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//附加对象的目标(你给谁附加ItemsSource属性)
WrapPanel wrapPanel = d as WrapPanel;
//e.NewValue: ItemsSource属性的值发生改变,改变后的值
ObservableCollection<DataModel> valueList = e.NewValue as ObservableCollection<DataModel>;
//当集合数据发生变化时触发
valueList.CollectionChanged += (s, e) =>
{
App.Current.Dispatcher.Invoke(() =>
{
wrapPanel.Children.Clear();
if (valueList != null && valueList.Count > 0)
{
foreach (var iValue in valueList)
{
TextBlock tb = new TextBlock { Margin = new Thickness(5) };
//将TextBlock的Text属性,绑定到数据源 iValue的Value属性上
tb.SetBinding(TextBlock.TextProperty, new Binding("Value") { Source = iValue });
wrapPanel.Children.Add(tb);
}
}
});
};
wrapPanel.Children.Clear();
if (valueList != null && valueList.Count > 0)
{
foreach (var iValue in valueList)
{
TextBlock tb = new TextBlock { Margin = new Thickness(5) };
//将TextBlock的Text属性,绑定到数据源 iValue的Value属性上
tb.SetBinding(TextBlock.TextProperty, new Binding("Value") { Source = iValue });
wrapPanel.Children.Add(tb);
}
}
}
}
网友评论