Use Styles for an editable Silverlight ComboBox

Sometimes I think reading materials get overlooked in SDKs and we miss some hidden gems.  One such gem I’d like to bring to your attention is the ability to add some subtle styling to an AutoCompleteBox from the Silverlight Toolkit to provide you with a cheap version of an editable ComboBox.
Sure Silverlight 2 has a ComboBox as part of the core controls now, but as I’ve previously noted, the ComboBox in current form exhibits only DropDownList behaviors.  I’m sure this will likely change with future versions, but if you have a need for an editable ComboBox feel, here’s a start.
If you think about the AutoCompleteBox functionality, it provides a list of items that are filtered when the user starts typing in there.  Well, that’s one function of a ComboBox right?  The other exhibited behavior we need is to be able to click to activate the DropDownList functionality and select.  Here’s where styling comes in (with a little help from some properties on AutoCompleteBox as well).
Let’s start by adding the items we’ll be dealing with: AutoCompleteBox and a data class (I’ll use a local hard-coded data class for portability in this code).  To use the AutoCompleteBox, make sure you’ve downloaded the Silverlight Toolkit and then add a reference in your Silverlight project to Microsoft.Windows.Controls.dll.  Then add the xmlns decoration in your UserControl…here’s mine (I use “toolkit”):
   1: <UserControl x:Class="ACBEditCombo.Page"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:toolkit="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"
   5:     Width="400" Height="300">
   6:     <Grid x:Name="LayoutRoot" Background="White">
   7:         <toolkit:AutoCompleteBox MinimumPrefixLength="0" 
   8:                                  MinimumPopulateDelay="200" 
   9:                                  x:Name="FruitChoice"
  10:                                  Width="200" Height="25" />
  11:     Grid>
  12: UserControl>
Here’s the Fruit class I’ll be using:
   1: #region Fruit Class
   2:     public class Fruit
   3:     {
   4:         public string Name { get; set; }
   5:         public override string ToString()
   6:         {
   7:             return this.Name;
   8:         }
   9:  
  10:         public static List GetFruitChoices()
  11:         {
  12:             List choices = new List();
  13:  
  14:             choices.Add(new Fruit() { Name = "Apple" });
  15:             choices.Add(new Fruit() { Name = "Apricot" });
  16:             choices.Add(new Fruit() { Name = "Banana" });
  17:             choices.Add(new Fruit() { Name = "Orange" });
  18:             choices.Add(new Fruit() { Name = "Pineapple" });
  19:             choices.Add(new Fruit() { Name = "Mango" });
  20:             choices.Add(new Fruit() { Name = "Papaya" });
  21:             choices.Add(new Fruit() { Name = "Pumpkin" });
  22:  
  23:             return choices;
  24:         }
  25:     }
  26:     #endregion
As you can see it is simple, but I wanted to make sure I was using more than just a string (yes I know essentially it is an object with only a string, but think outside the box :-)).  Now we can wire up this code here to get our AutoCompleteBox behavior working:
   1: public partial class Page : UserControl
   2: {
   3:     List choices = Fruit.GetFruitChoices();
   4:  
   5:     public Page()
   6:     {
   7:         InitializeComponent();
   8:         Loaded += new RoutedEventHandler(Page_Loaded);
   9:     }
  10:  
  11:     void Page_Loaded(object sender, RoutedEventArgs e)
  12:     {
  13:         FruitChoice.ItemsSource = choices;
  14:         FruitChoice.ItemFilter = (prefix, item) =>
  15:             {
  16:                 if (string.IsNullOrEmpty(prefix))
  17:                 {
  18:                     return true;
  19:                 }
  20:  
  21:                 Fruit f = item as Fruit;
  22:  
  23:                 if (f == null)
  24:                 {
  25:                     return false;
  26:                 }
  27:  
  28:                 return f.Name.ToLower().Contains(prefix.ToLower());
  29:             };
  30:     }
  31: }
Here we are using the same ItemFilter method I noted previously about AutoCompleteBox.  Once we have this when we start typing we’ll get:

Now all we need to do is make it look and act also like a ComboBox.  Here we can take styles and apply them.  We’re going to do three things using Style resources:
    1. Modify the control template for our toolkit:AutoCompleteBox to include a ToggleButton
    2. Create a style for the ToggleButton that looks like an arrow to cue the user what to do with it
    3. Style the list displayed a little bit
Best of all – we’re not going to change any of our logic code.  None.  First let’s modify the control template.  I’m putting these in my App.xaml resources so my app could use them globally.  Here’s the full style for the new control template which has new content in there and a ToggleButton added (I’ll keep it collapsed here for the sake of making this post less verbose than it already is):
   1: <Style x:Key="EditableComboStyle" TargetType="toolkit:AutoCompleteBox">
   2:             <Setter Property="SearchMode" Value="StartsWith" />
   3:             <Setter Property="Background" Value="#FF1F3B53"/>
   4:             <Setter Property="IsTabStop" Value="False" />
   5:             <Setter Property="HorizontalContentAlignment" Value="Left"/>
   6:             <Setter Property="BorderBrush">
   7:                 <Setter.Value>
   8:                     <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
   9:                         <GradientStop Color="#FFA3AEB9" Offset="0"/>
  10:                         <GradientStop Color="#FF8399A9" Offset="0.375"/>
  11:                         <GradientStop Color="#FF718597" Offset="0.375"/>
  12:                         <GradientStop Color="#FF617584" Offset="1"/>
  13:                     LinearGradientBrush>
  14:                 Setter.Value>
  15:             Setter>
  16:             <Setter Property="Template">
  17:                 <Setter.Value>
  18:                     <ControlTemplate TargetType="toolkit:AutoCompleteBox">
  19:                         <Grid Margin="{TemplateBinding Padding}">
  20:                             <VisualStateManager.VisualStateGroups>
  21:                                 <VisualStateGroup x:Name="PopupStates">
  22:                                     <VisualStateGroup.Transitions>
  23:                                         <VisualTransition GeneratedDuration="0:0:0.1" To="PopupOpened" />
  24:                                         <VisualTransition GeneratedDuration="0:0:0.2" To="PopupClosed" />
  25:                                     VisualStateGroup.Transitions>
  26:                                     <VisualState x:Name="PopupOpened">
  27:                                         <Storyboard>
  28:                                             <DoubleAnimation Storyboard.TargetName="PopupBorder" Storyboard.TargetProperty="Opacity" To="1.0" />
  29:                                         Storyboard>
  30:                                     VisualState>
  31:                                     <VisualState x:Name="PopupClosed">
  32:                                         <Storyboard>
  33:                                             <DoubleAnimation Storyboard.TargetName="PopupBorder" Storyboard.TargetProperty="Opacity" To="0.0" />
  34:                                         Storyboard>
  35:                                     VisualState>
  36:                                 VisualStateGroup>
  37:                             VisualStateManager.VisualStateGroups>
  38:                             <TextBox IsTabStop="True" x:Name="Text" Style="{TemplateBinding TextBoxStyle}" Margin="0" />
  39:                             <ToggleButton 
  40:                                 x:Name="DropDownToggle" 
  41:                                 HorizontalAlignment="Right"
  42:                                 VerticalAlignment="Center"
  43:                                 Style="{StaticResource ComboToggleButton}"
  44:                                 Margin="0"
  45:                                 HorizontalContentAlignment="Center" 
  46:                                 Background="{TemplateBinding Background}" 
  47:                                 BorderThickness="0" 
  48:                                 Height="16" Width="16"
  49:                                 >
  50:                                 <ToggleButton.Content>
  51:                                     <Path x:Name="BtnArrow" Height="4" Width="8" Stretch="Uniform" Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z " 
  52:                                           Margin="0,0,6,0" HorizontalAlignment="Right">
  53:                                         <Path.Fill>
  54:                                             <SolidColorBrush x:Name="BtnArrowColor" Color="#FF333333"/>
  55:                                         Path.Fill>
  56:                                     Path>
  57:                                 ToggleButton.Content>
  58:                             ToggleButton>
  59:                             <Popup x:Name="Popup">
  60:                                 <Border x:Name="PopupBorder" HorizontalAlignment="Stretch" Opacity="0.0" BorderThickness="0" CornerRadius="3">
  61:                                     <Border.RenderTransform>
  62:                                         <TranslateTransform X="2" Y="2" />
  63:                                     Border.RenderTransform>
  64:                                     <Border.Background>
  65:                                         <SolidColorBrush Color="#11000000" />
  66:                                     Border.Background>
  67:                                     <Border HorizontalAlignment="Stretch" BorderThickness="0" CornerRadius="3">
  68:                                         <Border.Background>
  69:                                             <SolidColorBrush Color="#11000000" />
  70:                                         Border.Background>
  71:                                         <Border.RenderTransform>
  72:                                             <TransformGroup>
  73:                                                 <ScaleTransform />
  74:                                                 <SkewTransform />
  75:                                                 <RotateTransform />
  76:                                                 <TranslateTransform X="-1" Y="-1" />
  77:                                             TransformGroup>
  78:                                         Border.RenderTransform>
  79:                                         <Border HorizontalAlignment="Stretch" Opacity="1.0" Padding="2" BorderThickness="2" BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="3">
  80:                                             <Border.RenderTransform>
  81:                                                 <TransformGroup>
  82:                                                     <ScaleTransform />
  83:                                                     <SkewTransform />
  84:                                                     <RotateTransform />
  85:                                                     <TranslateTransform X="-2" Y="-2" />
  86:                                                 TransformGroup>
  87:                                             Border.RenderTransform>
  88:                                             <Border.Background>
  89:                                                 <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  90:                                                     <GradientStop Color="#FFDDDDDD" Offset="0"/>
  91:                                                     <GradientStop Color="#AADDDDDD" Offset="1"/>
  92:                                                 LinearGradientBrush>
  93:                                             Border.Background>
  94:                                             <ListBox x:Name="SelectionAdapter" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto" ItemTemplate="{TemplateBinding ItemTemplate}" />
  95:                                         Border>
  96:                                     Border>
  97:                                 Border>
  98:                             Popup>
  99:                         Grid>
 100:                     ControlTemplate>
 101:                 Setter.Value>
 102:             Setter>
 103:         Style>
Now if you look you’ll see that the ToggleButton itself has a Style attribute, so here’s the Style we use for that:
   1: <Style x:Name="ComboToggleButton" TargetType="ToggleButton">
   2:             <Setter Property="Foreground" Value="#FF333333"/>
   3:             <Setter Property="Background" Value="#FF1F3B53"/>
   4:             <Setter Property="Padding" Value="0"/>
   5:             <Setter Property="Template">
   6:                 <Setter.Value>
   7:                     <ControlTemplate TargetType="ToggleButton">
   8:                         <Grid>
   9:                             <Rectangle Fill="Transparent" />
  10:                             <ContentPresenter
  11:                             x:Name="contentPresenter"
  12:                             Content="{TemplateBinding Content}"
  13:                             ContentTemplate="{TemplateBinding ContentTemplate}"
  14:                             HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
  15:                             VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
  16:                             Margin="{TemplateBinding Padding}"/>
  17:                         Grid>
  18:                     ControlTemplate>
  19:                 Setter.Value>
  20:             Setter>
  21:         Style>
Now the only thing we need to do is add the Style attribute for our AutoCompleteBox as reflected here (notice the added Style attribute):
   1: <UserControl x:Class="ACBEditCombo.Page"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:toolkit="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"
   5:     Width="400" Height="300">
   6:     <Grid x:Name="LayoutRoot" Background="White">
   7:         <toolkit:AutoCompleteBox MinimumPrefixLength="0" 
   8:                                  MinimumPopulateDelay="200" 
   9:                                  x:Name="FruitChoice"
  10:                                  Width="200" Height="25"
  11:                                  Style="{StaticResource EditableComboStyle}"/>
  12:     Grid>
  13: UserControl>
And now when we look at our application running we have what looks to be an editable ComboBox:

The ComboBox behavior is enabled now via the ToggleButton and the MinimumPrefixLength attribute on the AutoCompleteBox (try changing it to 1 and you’ll see that you don’t get exactly the same behavior).
It’s little gems like this that should cause you to pay attention to the finer details of SDKs, samples, etc.  I hope you found this helpful…if you have, please consider subscribing for more samples like this (and no I’m not planning on just emitting all the SDK samples :-)).  You can download my code for this above in a complete solution here: ACBEditCombo.zip.
Use Styles for an editable Silverlight ComboBox Use Styles for an editable Silverlight ComboBox Reviewed by BloggerSri on 11:23 AM Rating: 5

No comments:

Powered by Blogger.