public static DependencyProperty ImagePathMemberProperty = DependencyProperty.Register("ImagePathMember", typeof(string), typeof(MyCustomControl), new PropertyMetadata("",ImagePathMemberPropertyChanged));
public static DependencyProperty DataSourceProperty = DependencyProperty.Register("DataSource", typeof(string), typeof(MyCustomControl), new PropertyMetadata("",DataSourceChanged));
public string ImagePathMember
{
get
{
return (string)GetValue(ImagePathMemberProperty);
}
set
{
SetValue(ImagePathMemberProperty, value);
}
}
public string DataSource
{
get
{
return (string)GetValue(DataSourceProperty);
}
set
{
SetValue(DataSourceProperty, value);
}
}
事实证明,我们甚至不需要反射。当在代码中使用数据绑定时,实际上是提供属性的字符串名称,这很方便,因为我们创建的依赖属性实际上就是属性的字符串名称。我们真是太幸运了!
private void DataSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
RecreateBindings();
}
//Repeat this for all the dependencyproperties
private void ImagePathMemberPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
RecreateBindings();
}
private void RecreateBindings()
{
//Repeat this for all dependency properties
if(ImagePathMember!=null)
{
Binding ImagePathBinding= new Binding(ImagePathMember);
ImagePathBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
MyImageControl.SetBinding(ImageControl.ImagePathProperty, ImagePathBinding);
}
}
好吧,事情比我想象的要复杂得多。当TextBlocks在DataTemplate中时,你不能仅通过在代码后台中调用其名称来访问它们。
你必须等待ListBox/ListView生成其项目容器,然后使用VisualTreeHelper遍历列表视图的所有子项以查找你要查找的具体控件,然后进行绑定。
这花费了我很长时间来完成,因为我找不到控件,因为我将事件处理程序附加到ListView的ItemsSourceChanged事件上,这意味着我在查看ItemsSource属性更改之前就查看了这些项的容器。
最终,我找到了一个解决方案:
在ListView/ListBox的模板中,您需要像下面这样命名其中的控件:
<ImageControl x:Name="MyImageControl" [...]></ImageControl>
<ListBox x:Name="listbox" ItemsSource="{Binding ElementName=me, Path=DataSource, UpdateSourceTrigger=PropertyChanged}" [...]></ListBox>
ElementName=me
。这是因为我正在绑定到实际控件中(即MyCustomControl)。我的UserControl
在xmlns
上方有x:Name="me"
,这样我就可以轻松地绑定到代码后台中的属性。
//Repeat this for all types of controls in your listbox.
private static void RecreateImageControlBindings(ListBox listbox, string controlName, string newPropertyName)
{
if (!string.IsNullOrEmpty(newPropertyName))
{
if (listbox.Items.Count > 0)
{
for (int i = 0; i < listbox.Items.Count; i++)
{
ListBoxItem item = listbox.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem;
if (item != null)
{
Binding imageControlBinding = new Binding(newPropertyName);
imageControlBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
ImageControl t = FindDescendant<ImageControl>(item, controlName);
if (t != null)
BindingOperations.SetBinding(t, ImageControl.ImagePath, imageControlBinding);
}
}
}
}
}
public static T FindDescendant<T>(DependencyObject obj,string objectName) where T : FrameworkElement
{
// Check if this object is the specified type
if (obj is T && ((T)obj).Name == objectName)
return obj as T;
// Check for children
int childrenCount = VisualTreeHelper.GetChildrenCount(obj);
if (childrenCount < 1)
return null;
// First check all the children
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child is T && ((T)child).Name == objectName)
return child as T;
}
// Then check the childrens children
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = FindDescendant<T>(VisualTreeHelper.GetChild(obj, i), objectName);
if (child != null && child is T && ((T)child).Name == objectName)
return child as T;
}
return null;
}
我唯一做的改动是添加了控件名称的检查。在我们的ListBoxItem中有两个TextBlocks,所以原始方法只会返回第一个TextBlock。我们需要检查名称,这样我们就可以对两个TextBlock进行绑定。
由于RecreateBindings方法被拆分,我们需要更改PropertyChangedCallBacks的调用,以调用每个属性特定的RecreateBindings方法。数据源属性将包含所有的RecreateBindings方法。
private static void DataSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
//Put the RecreateBindings for all the properties here:
RecreateImageControlBindings(((MyCustomControl)sender).listbox, "MyImageControl", ((MyCustomControl)sender).ImagePathMember);
}
//Repeat this for all the dependencyproperties
private void ImagePathMemberPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
RecreateImageControlBindings(((MyCustomControl)sender).listbox, "MyImageControl", ((MyCustomControl)sender).ImagePathMember);
}
public MyCustomControl()
{
InitializeComponent();
listview.ItemContainerGenerator.StatusChanged += new EventHandler(ItemContainerGenerator_StatusChanged);
}
void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (listview.ItemContainerGenerator.Status
== System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
{
//Do this for all the different bindings we want
RecreateImageControlBindings(listview, "MyImageControl", ImagePathMember);
}
}
应该就是这样了。如果需要任何帮助或有任何问题,请让我知道。
u_u
<SomeNamespace:YourCustomControl DataSource="{Binding ControlConsumerEFEntity}" />
如果这是一个真正的自定义控件,它不会直接设置其内部元素的数据源。而是留给控件的消费者来设置。
想想内置的WPF ListBox是如何工作的。如果你只是这样做:
<ListBox />
当前未设置数据源,但如果您这样做
<ListBox DataSource="{Binding MyCollection}" />
然后将为ListBox提供一个数据源,该数据源由使用ListBox控件的人指定。有意义吗?
List
绑定到控件的DataSource
属性上,你是想要绑定一个特定实体列表,还是要绑定一个List<EntityObject>
并同时在控件中显示多个不同的实体? - Jason Ridge