This custom UserControl will appear as a regular combobox, but unlike the built-in ComboBox object, it can show the user a default string of text if they have not made a selection yet.
In order to accomplish this, our UserControl will be made up of two Controls. Obviously we need an actual ComboBox, but we will also use a regular Label to show the default text.
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>
As you can see, this single UserControl is actually group of two individual Controls. This allows us some flexibility that is not available in a single ComboBox alone.
Here are several important things to note:
x:Name
set. This is because we want to bind to properties that are located in the code-behind, which means it needs some way to reference itself.ElementName
. This is so that the UserControl knows to look at itself to locate bindings.IsHitTestVisible=false
, we disallow the user from hovering over or clicking on the Label - all input is passed through it to the ComboBox below.InverseNullVisibility
converter to determine whether it should show itself or not. You can find the code for this at the bottom of this example.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); }
}
}
In the code-behind, we're simply exposing which properties we want to be available to the programmer using this UserControl. Unfortunately, because we don't have direct access to the ComboBox from outside this class, we need to expose duplicate properties (MyItemsSource
for the ComboBox's ItemsSource
, for example). However, this is a minor tradeoff considering that we can now use this similarly to a native control.
Here's how the CustomComboBox
UserControl might be used:
<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>
And the end result:
Here's the InverseNullVisibilityConverter needed for the Label on the UserControl, which is just a slight variation on lll's version:
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();
}
}