WPF中自定义用户控件/列表视图控件

3
我正在开发一个自定义控件。我的自定义控件中有一个列表视图,应该显示绑定EF实体的多个字段。输出如下图所示:

enter image description here

现在对于所有不同属性的实体,我的数据源是什么,我不确定绑定属性是什么。 目前,我的ListView有一个图像控件、两个文本块和一个链接标签,我应该如何确定哪个控件应该绑定哪个属性?例如,“客户屏幕绑定到客户实体”和“员工屏幕绑定到员工实体”。我的控件中一次只能有一个实体,请指导我如何以通用且逻辑合理的方式完成这项任务。谢谢。
2个回答

6
像Foovanadil建议的那样,我会公开一个DataSource DependencyProperty。但除此之外,我还会公开5个字符串依赖属性。控件的消费者将在其中放置他们想要放入特定控件中的属性名称。
让我更具体一些:
想想ComboBox是如何工作的,您可以绑定它们的DataSource,但您也可以提供一个 DisplayMemberPath 和 SelectedValuePath,指定数据源中要使用哪些属性。
您可以通过自己的控件做同样的事情:
公开一个“ImagePathMember”属性。这将是包含要放入图像控件中的路径的属性的名称。
公开一个“LinkPathMember”属性。该属性将是包含链接路径的属性的名称。
公开一个“LinkDisplayMember”属性。该属性将是包含链接将呈现为的文本的属性的名称。
公开一个“TopTextBlockMember”属性。该属性将是包含顶部文本块文本的属性的名称。
公开一个“BottomTextBlockMember”属性。该属性将是包含底部文本块文本的属性的名称。
然后,您只需在控件中使用反射来确定每个列表框项的值,然后将其绑定到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的ItemsSourceChanged事件上,这意味着我在查看ItemsSource属性更改之前就查看了这些项的容器。

最终,我找到了一个解决方案:

XAML:

在ListView/ListBox的模板中,您需要像下面这样命名其中的控件:

      <ImageControl x:Name="MyImageControl" [...]></ImageControl>

您还需要为您的列表框/列表视图命名,如下所示(并将其ItemsSource绑定到您的DataSource属性):
      <ListBox x:Name="listbox"  ItemsSource="{Binding ElementName=me, Path=DataSource, UpdateSourceTrigger=PropertyChanged}" [...]></ListBox> 

您会看到绑定中有ElementName=me。这是因为我正在绑定到实际控件中(即MyCustomControl)。我的UserControlxmlns上方有x:Name="me",这样我就可以轻松地绑定到代码后台中的属性。

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);


                    }
                }

            }



        }

    }

如您所见,现在您需要为列表视图/列表框中的所有不同类型的控件创建重新绑定方法。可能有更通用的方法来完成这项工作,但这是您自己要解决的问题。我不能全部替您完成:P
代码正在执行的操作是遍历列表框中的项目并获取其容器。生成的图像控件将是该容器的子级。因此,我们通过从此帖子中收集的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中有两个TextBlocks,所以原始方法只会返回第一个TextBlock。我们需要检查名称,这样我们就可以对两个TextBlock进行绑定。

PropertyCallBack方法:

由于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);

    }

请注意,这里的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


谢谢你的回答,看起来非常有前途。请问你能否给我一些代码作为起点?那将是一个很大的帮助。 - user831174
@MSingh 在我贴些代码之前,我想了解一些情况。假设你将一个List绑定到控件的DataSource属性上,你是想要绑定一个特定实体列表,还是要绑定一个List<EntityObject>并同时在控件中显示多个不同的实体? - Jason Ridge
ExitMusic@ 我喜欢 Combobox 的例子,我会朝着那个方向前进,让我想清楚整件事。 - user831174
@MSingh,我很乐意帮忙,但是如果您想绑定不同类型的“EntityObjects”列表,这意味着我们需要为每个listboxitem拥有一个DependencyProperty,而如果它们是相似类型,则我们只需为整个控件拥有一个DependencyProperty。 - Jason Ridge
@MSingh,我又更新了我的答案。看一下并告诉我你的想法。 - Jason Ridge

3
如果这是一个真正意义上的自定义控件,旨在在多个位置重复使用,并且具有不同的数据源,那么您应该将DataSource的设置留给控件的消费者。
您需要在自定义控件上添加一个名为DataSource的自定义依赖属性。这将为控件的消费者提供设置的选项。
然后,当有人使用您的控件时,他们将执行以下操作:
<SomeNamespace:YourCustomControl DataSource="{Binding ControlConsumerEFEntity}" />

如果这是一个真正的自定义控件,它不会直接设置其内部元素的数据源。而是留给控件的消费者来设置。

想想内置的WPF ListBox是如何工作的。如果你只是这样做:

<ListBox />

当前未设置数据源,但如果您这样做

<ListBox DataSource="{Binding MyCollection}" />

然后将为ListBox提供一个数据源,该数据源由使用ListBox控件的人指定。有意义吗?


谢谢,我已经更新了我的问题“引用部分”,请在那个上下文中回答。 - user831174
+1 因为你在这个答案上像一只狗一样努力工作,而提问者甚至没有给你点赞。 - Levi Botelho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接