在 UserControl 中使 Combo Box ItemsSource 通用,使其可重用于多种类型的集合

Make Combo Box ItemsSource generic inside of UserControl to make it reusable for Collections of Multiple types

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

我有两个 User Controls InputUCComboBoxUC。两者分别有 LabelTextBoxLabelComboBox。我已经通过定义所需的依赖属性成功地实现了 InputUC

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

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

我正在尝试的代码是

ComboBox控制在ComboBoxUC.xaml

<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.cs

ComboBoxUC的代码隐藏
    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。 在每种情况下可能会有所不同的是:

我已经在 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 类型当然没有我在我的实体中拥有的任何属性。

帮助将不胜感激,因为我被困住了,无法从这一点开始进行开发。

与其尝试通过使它们成为泛型来使用户控件中的集合成为强类型的,不如让它们变得不那么强类型;请记住,组合框本身的 ItemsSource 只是 'object'.

类型

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

<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,甚至使用不同的 属性 名称。