WPF中的自定义UserControl /列表视图控件

时间:2012-03-28 15:26:57

标签: wpf xaml user-controls

我正在开发一个自定义控件。我的自定义控件中有一个列表视图,它假设显示绑定EF实体的多个字段。输出将是这样的

enter image description here

现在listview的数据源是什么,因为我的所有实体都有不同的属性,所以我不确定绑定属性。

  

现在我的ListView有一个图像控件,两个文本块和一个   链接标签,我该如何确定哪个属性控件   应该绑定。 >

     

我喜欢在RecordListing屏幕中使用此控件,比如'Client   屏幕绑定到客户端实体'和'员工屏幕绑定到员工   实体。在我的控制下,一次应该有一个实体,

请指导我,我怎么能这样做是一种非常具有创造性和逻辑性的方式。

由于

2 个答案:

答案 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


编辑2

好的,所以事情变得比我想象的要复杂得多。当TextBlocks在DataTemplate中时,您无法通过在后面的代码中调用它们的名称来真正访问它们。

您需要做的是等待ListBox / ListView生成其项目'容器,然后使用visualtreehelper循环遍历listview的所有子项,以找到您要查找的特定控件,然后 绑定到它们。

这花了我很长时间才做,因为我无法找到控件,因为我将一个事件处理程序附加到ListView的ItemsSourceChanged事件,这意味着我看起来像ItemsSource属性发生了变化,但是之前生成了这些项目的容器。

最终我找到了解决方案:

XAML:

在您拥有控件的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:

您基本上需要修改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。

这是方法:

FindDescendant方法

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,因此原始方法只返回第一个。我们需要检查名称,以便我们可以对两者进行绑定。

PropertyCallBack方法:

因为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控件的人指定的。有意义吗?