帶有自定義預設文字的 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();
    }
}