带有自定义默认文本的 ComboBox

此自定义 UserControl 将显示为常规组合框,但与内置 ComboBox 对象不同,它可以向用户显示默认文本字符串(如果尚未进行选择)。

为了实现这一点,我们的 UserControl 将由两个控件组成。显然我们需要一个实际的 ComboBox,但我们也会使用常规 Label 来显示默认文本。

CustomComboBox.xaml

<UserControl x:Class="UserControlDemo.CustomComboBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:cnvrt="clr-namespace:UserControlDemo"
             x:Name="customComboBox">
    <UserControl.Resources>
        <cnvrt:InverseNullVisibilityConverter x:Key="invNullVisibleConverter" />
    </UserControl.Resources>
    <Grid>
        <ComboBox x:Name="comboBox"
                  ItemsSource="{Binding ElementName=customComboBox, Path=MyItemsSource}"
                  SelectedItem="{Binding ElementName=customComboBox, Path=MySelectedItem}"
                  HorizontalContentAlignment="Left" VerticalContentAlignment="Center"/>
        
        <Label HorizontalAlignment="Left" VerticalAlignment="Center"
               Margin="0,2,20,2" IsHitTestVisible="False"
               Content="{Binding ElementName=customComboBox, Path=DefaultText}"
               Visibility="{Binding ElementName=comboBox, Path=SelectedItem, Converter={StaticResource invNullVisibleConverter}}"/>
    </Grid>
</UserControl>

如你所见,这个单一 UserControl 实际上是两个独立控件的组合。这使我们具有一些单独的 ComboBox 无法提供的灵活性。

以下是一些需要注意的重要事项:

  • UserControl 本身有一个 x:Name 集。这是因为我们想要绑定到代码隐藏中的属性,这意味着它需要某种方式来引用自身。
  • ComboBox 上的每个绑定都将 UserControl 的名称作为 ElementName。这样,UserControl 就会知道自己查找绑定。
  • 标签不是可见的命中测试。这是为了给用户一种错觉,即 Label 是 ComboBox 的一部分。通过设置 IsHitTestVisible=false,我们禁止用户悬停或单击 Label - 所有输入都通过它传递到下面的 ComboBox。
  • Label 使用 InverseNullVisibility 转换器来确定它是否应该显示自己。你可以在此示例的底部找到此代码。

CustomComboBox.xaml.cs

public partial class CustomComboBox : UserControl
{
    public CustomComboBox()
    {
        InitializeComponent();
    }

    public static DependencyProperty DefaultTextProperty =
        DependencyProperty.Register("DefaultText", typeof(string), typeof(CustomComboBox));

    public static DependencyProperty MyItemsSourceProperty = 
        DependencyProperty.Register("MyItemsSource", typeof(IEnumerable), typeof(CustomComboBox));

    public static DependencyProperty SelectedItemProperty =
        DependencyProperty.Register("SelectedItem", typeof(object), typeof(CustomComboBox));

    public string DefaultText
    {
        get { return (string)GetValue(DefaultTextProperty); }
        set { SetValue(DefaultTextProperty, value); }
    }

    public IEnumerable MyItemsSource
    {
        get { return (IEnumerable)GetValue(MyItemsSourceProperty); }
        set { SetValue(MyItemsSourceProperty, value); }
    }

    public object MySelectedItem
    {
        get { return GetValue(MySelectedItemProperty); }
        set { SetValue(MySelectedItemProperty, value); }
    }
}

在代码隐藏中,我们只是使用此 UserControl 公开我们希望程序员可以使用哪些属性。不幸的是,因为我们没有从这个类的外部直接访问 ComboBox,所以我们需要公开重复的属性(例如,为 ComboBox 的 ItemsSource 提供 MyItemsSource)。然而,考虑到我们现在可以类似于本机控件使用它,这是一个小的权衡。

以下是 CustomComboBox UserControl 的使用方法:

<Window x:Class="UserControlDemo.UserControlDemo"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cntrls="clr-namespace:UserControlDemo"
        Title="UserControlDemo" Height="240" Width=200>
    <Grid>
        <cntrls:CustomComboBox HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="165"
                               MyItemsSource="{Binding Options}"
                               MySelectedItem="{Binding SelectedOption, Mode=TwoWay}"
                               DefaultText="Select an option..."/>
    <Grid>
</Window>

最终结果是:

StackOverflow 文档 StackOverflow 文档 StackOverflow 文档

这是 UserControl 上 Label 所需的 InverseNullVisibilityConverter,这只是 lll 版本的一个小变化 :

public class InverseNullVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value == null ? Visibility.Visible : Visibility.Hidden;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}