在UserControl内部使组合框项目源通用,使其可用于多种类型的集合

本文关键字:用于 类型 集合 种类 组合 内部 UserControl 项目 | 更新日期: 2023-09-27 18:05:22

我正在开发一个WPF应用程序,最近遇到了制作可重用User Controls的需求。

我有两个User ControlsInputUCComboBoxUC。两者分别具有LabelTextBox以及LabelComboBox。我已经通过定义所需的依赖属性成功地实现了InputUC

我面临的问题是ComboBoxUC。我的应用程序中有一个场景,其中我必须在不同的地方显示CitiesCustomersSalesmen中的Collection和其他一些实体。显然,每个实体将为DisplayMemberPathSelectedValuePathSelectedValue属性提供不同的属性名称,并将不同类型的Collection作为ComboBoxItemsSource属性。

我也在网上搜索过,但没有找到任何相同的解决方案。

我正在尝试的代码是

ComboBoxUC.xaml 中的ComboBox控制

<ComboBox Name="valuesComboBox" 
          Grid.Column="1"
          ItemsSource="{Binding ComboBoxItems}" 
          DisplayMemberPath="{Binding ComboBoxDisplayMemberPath}"
          SelectedValuePath="{Binding ComboBoxSelectedValuePath}"
          SelectedValue="{Binding ComboBoxValue}"
          IsEnabled="{Binding ComboBoxIsEnabled}"
          Style="{StaticResource ComboBox-Base}">
</ComboBox>

ComboBoxUC.xaml.csComboBoxUC的代码隐藏

    public string ComboBoxLabel
    {
        get { return (string)GetValue(LabelProperty); }
        set { SetValue(LabelProperty, value); }
    }
    public bool ComboBoxIsRequired
    {
        get { return (bool)GetValue(IsRequiredProperty); }
        set { SetValue(IsRequiredProperty, value); }
    }
    public long ComboBoxValue
    {
        get { return (long)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }
    public bool ComboBoxIsEnabled
    {
        get { return (bool)GetValue(ValueEnabledProperty); }
        set { SetValue(ValueEnabledProperty, value); }
    }
    public ObservableCollection<CityViewModel> ComboBoxItems
    {
        get { return (ObservableCollection<CityViewModel>)GetValue(ValueItems); }
        set { SetValue(ValueItems, value); }
    }
    public string ComboBoxDisplayMemberPath
    {
        get { return GetValue(ValueDisplayMemberPath).ToString(); }
        set { SetValue(ValueDisplayMemberPath, value); }
    }
    public string ComboBoxSelectedValuePath
    {
        get { return GetValue(ValueSelectedValuePath).ToString(); }
        set { SetValue(ValueSelectedValuePath, value); }
    }
    public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("ComboBoxLabel", typeof(string),
        typeof(ComboBoxUC), new PropertyMetadata(string.Empty));
    public static readonly DependencyProperty IsRequiredProperty = DependencyProperty.Register("ComboBoxIsRequired", typeof(bool),
            typeof(ComboBoxUC), new PropertyMetadata(false));
    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("ComboBoxValue", typeof(long),
            typeof(ComboBoxUC), new FrameworkPropertyMetadata { BindsTwoWayByDefault = true, DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
    public static readonly DependencyProperty ValueEnabledProperty = DependencyProperty.Register("ComboBoxIsEnabled", typeof(bool),
            typeof(ComboBoxUC), new FrameworkPropertyMetadata { BindsTwoWayByDefault = false, DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
    public static readonly DependencyProperty ValueItems = DependencyProperty.Register("ComboBoxItems", typeof(ObservableCollection<CityViewModel>),
            typeof(ComboBoxUC), new FrameworkPropertyMetadata { BindsTwoWayByDefault = false, DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
    public static readonly DependencyProperty ValueDisplayMemberPath = DependencyProperty.Register("ComboBoxDisplayMemberPath", typeof(string),
            typeof(ComboBoxUC), new FrameworkPropertyMetadata { BindsTwoWayByDefault = false, DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
    public static readonly DependencyProperty ValueSelectedValuePath = DependencyProperty.Register("ComboBoxSelectedValuePath", typeof(string),
            typeof(ComboBoxUC), new FrameworkPropertyMetadata { BindsTwoWayByDefault = true, DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
    public ComboBoxUC()
    {
        InitializeComponent();
    }

我使用UserControlPage控件的代码是

<local:ComboBoxUC ComboBoxLabel="City"
                  ComboBoxIsRequired="True"
                  ComboBoxValue="{Binding CustomerViewModel.customer_city_id}"
                  ComboBoxItems="{Binding Cities}"
                  ComboBoxDisplayMemberPath="city_name"
                  ComboBoxSelectedValuePath="city_id"
                  ComboBoxIsEnabled="{Binding Flags.AddOrUpdate}">
</local:ComboBoxUC>

现在,我将在应用程序的多个位置使用相同的xaml。在每种情况下可能会有所不同的是:

  • CustomerViewModel.customer_city_id
  • 城市
  • 城市名称
  • 城市id

我已经在ComboBoxUC.xaml中正确设置了DataContext,并且我的UserControl的当前代码对一种类型的Collection (CityViewModel)正确工作。我想对其他具有明显不同属性名称的实体(如CustomerViewModelSalesmanViewModel等(使用相同的代码。

我希望以下代码是通用的。

public ObservableCollection<CityViewModel> ComboBoxItems
{
     get { return (ObservableCollection<CityViewModel>)GetValue(ValueItems); }
     set { SetValue(ValueItems, value); }
}
public static readonly DependencyProperty ValueItems = DependencyProperty.Register("ComboBoxItems", typeof(ObservableCollection<CityViewModel>),
                typeof(ComboBoxUC), new FrameworkPropertyMetadata { BindsTwoWayByDefault = false, DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });

我也尝试过object类型的Collection,但当然object类型在我的实体中没有任何属性。

我们将感谢您的帮助,因为我陷入了困境,无法从这一点上继续发展。

在UserControl内部使组合框项目源通用,使其可用于多种类型的集合

与其试图通过使用户控件中的集合具有泛型来使它们具有强类型,不如使它们不那么具有强类型;请记住,组合框本身的ItemsSource只是"object"类型。

我建议您让您的ComboBoxUC公开类型为IEnumerable的DependencyProperty,并将其绑定到ComboBoxItemsSource。然后还公开DataTemplate类型的DependencyProperty,并将其绑定到ComboBox的ItemTemplate属性。当使用用户控件时,您可以提供一个简单的DataTemplate来显示所需的属性,而不是使用DisplayMember路径。例如,当你想在组合框UC中显示城市时,你可以这样做:

<local:ComboBoxUC ItemsSource="{Binding Cities}">
  <local.ComboBoxUC.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding city_name}"/>
    </DataTemplate>
  </local:ComboBoxUC.ItemTemplate>
</local:ComboBoxUC/>

然后,我会将ComboBox的SelectedItem公开为用户控件的DependencyProperty,如果您绝对必须绑定到SelectedValuePath而不是SelectedItem,请使用ValueConverter。

老实说,感觉这些用户控件有点OTT。如果你得到的只是一个标签和一些样式,那么在资源字典中重新模板化控件并将模板应用于你想要以这种方式使用的每个组合框也可以实现同样的效果。

正如@ibebbs所指出的,唯一需要更改的是ObservableCollectionIEnumerable的类型。

public IEnumerable ComboBoxItems
{
     get { return (IEnumerable)GetValue(ValueItems); }
     set { SetValue(ValueItems, value); }
}
public static readonly DependencyProperty ValueItems = DependencyProperty.Register("ComboBoxItems", typeof(IEnumerable),
                typeof(ComboBoxUC), new FrameworkPropertyMetadata { BindsTwoWayByDefault = false, DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });

这使我能够在应用程序上使用相同的UserControl,并使用不同类型的Collection,甚至使用不同的属性名称。