我正在开发一个自定义控件。我的自定义控件中有一个列表视图,它假设显示绑定EF实体的多个字段。输出将是这样的
现在listview的数据源是什么,因为我的所有实体都有不同的属性,所以我不确定绑定属性。
现在我的ListView有一个图像控件,两个文本块和一个 链接标签,我该如何确定哪个属性控件 应该绑定。 >
我喜欢在RecordListing屏幕中使用此控件,比如'Client 屏幕绑定到客户端实体'和'员工屏幕绑定到员工 实体。在我的控制下,一次应该有一个实体,
请指导我,我怎么能这样做是一种非常具有创造性和逻辑性的方式。
由于
答案 0 :(得分:4)
像Foovanadil建议的那样,我会公开一个DataSource DependencyProperty。但除此之外,我还会公开另外5个字符串依赖属性。然后,控件的使用者将把他们想要的属性的名称放在特定的控件中。
让我更具体一点:
考虑一下Combobox如何工作,你可以在哪里绑定他们的DataSource,但你也可以提供一个DisplayMemberPath
和一个SelectedValuePath
来指定要使用的数据源中的哪些属性。
你可以用你的控件做同样的事情:
公开" ImagePath会员"属性。这将是属性的 Name ,其中包含图像控件中图像的路径
公开" LinkPath会员"属性。此属性将是包含链接路径的属性的名称
公开" LinkDisplayMember"属性。此属性将是属性的 Name ,其中包含链接的文本。
公开" TopTextBlock会员"属性。此属性将是包含顶部文本块
的文本的属性的名称公开" BottomTextBlock会员"属性。此属性将是包含底部文本块
的文本的属性的名称然后你只需在控件中使用反射来确定每个列表框项的值,然后将其绑定到listboxitem的图像控件,链接和2个文本块
希望有所帮助
u_u
好的,你要求一些代码指出正确的方向。
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);
}
}
我手工编写了这个代码所以它可能都是错误的。我也不担心绑定,我没有设置Source
,因为我认为它会绑定到集合中的项目,但我不确定这种行为。
所以我现在要发布这个,然后给它一个测试运行,并在需要的地方进行调整。
祝你好运
u_u
好的,所以事情变得比我想象的要复杂得多。当TextBlocks在DataTemplate中时,您无法通过在后面的代码中调用它们的名称来真正访问它们。
您需要做的是等待ListBox / ListView生成其项目'容器,然后使用visualtreehelper循环遍历listview的所有子项,以找到您要查找的特定控件,然后 绑定到它们。
这花了我很长时间才做,因为我无法找到控件,因为我将一个事件处理程序附加到ListView的ItemsSourceChanged事件,这意味着我看起来像ItemsSource属性发生了变化,但是之前生成了这些项目的容器。
最终我找到了解决方案:
在您拥有控件的ListView / ListBox模板中,您需要将它们命名为:
<ImageControl x:Name="MyImageControl" [...]></ImageControl>
您还需要为listbox / listview指定一个名称,如下所示(并将其ItemsSource绑定到您的DataSource属性):
<ListBox x:Name="listbox" ItemsSource="{Binding ElementName=me, Path=DataSource, UpdateSourceTrigger=PropertyChanged}" [...]></ListBox>
您将看到绑定具有ElementName=me
。这是因为我绑定到实际控件(即MyCustomControl)。我的UserControl
高于x:Name="me"
xmlns
,因为这样我就可以轻松地绑定到后面代码中的属性。
您基本上需要修改RecreateBindings方法。我在第一篇文章中犯了一个大错,因为它需要是一个静态方法才能在DependencyProperty的PropertyChangedCallBack中运行(我真的不应该手工完成代码)。
这就是我最终的结果:
//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);
}
}
}
}
}
如您所见,现在您需要一个RecreateBindings方法,用于listview / listbox中所有不同类型的控件。有一种更通用的方式,但你可以自己解决这个问题。我不能做所有的工作:P
代码正在做的是它遍历列表框中的项目并获取其容器。 ImageControl一旦生成,将成为该容器的子代。所以我们通过我改编自this post的FindDescendants方法来获取ImageControl。
这是方法:
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中有2个TextBlock,因此原始方法只返回第一个。我们需要检查名称,以便我们可以对两者进行绑定。
因为RecreateBindings方法被拆分了,我们需要更改PropertyChangedCallBacks以调用特定于每个属性的RecreateBindings方法。 datasource属性将包含所有的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);
}
请注意,MyCustomControl是您正在创建的此控件的类型。
最后,我们需要在构造函数中添加一行,为ListBox的ItemContainerGenerator添加一个事件处理程序,这样我们就可以检查项目容器的生成时间,并且我们可以附加我们的绑定。:
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
答案 1 :(得分:2)
如果这是一个真正的自定义控件,可以在具有不同数据源的多个位置重复使用,那么您可以将其留给控件使用者来设置DataSource。
您需要在名为DataSource的自定义控件上添加自定义依赖项属性。这将为控件消费者在使用控件时设置一些内容。
然后当有人使用你的控制时,他们会做这样的事情:
<SomeNamespace:YourCustomControl DataSource="{Binding ControlConsumerEFEntity}" />
同样,如果这是一个真正的自定义控件,它将不会直接为其内部元素设置它的DataSource。这将由控制消费者来决定。
考虑内置WPF ListBox的工作原理。如果你这样做:
<ListBox />
没有设置DataSource,但如果你这样做
<ListBox DataSource="{Binding MyCollection}" />
然后将为ListBox提供一个要使用的DataSource。这是由使用ListBox控件的人指定的。有意义吗?