| Comments

This is part 5 in a series on getting started with Silverlight.  To view the index to the series click hereYou can download the completed project files for this sample application in C# or Visual Basic.

In our previous step we added better data binding and saved some data to our isolated storage area.  Let’s start integrating some other controls to make our experience a little better.


Remember the history data we save every time a search term is used?  Let’s help our users search better, by providing them a history of their searches in the TextBox while they type.  We’re going to use a control from the Silverlight Toolkit to accomplish this, AutoCompleteBox.

To do this we need to add a reference to the System.Windows.Controls.Input assembly.  Then add an xmlns to your Search.xaml file:

   1: xmlns:input="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input"

With this in place, change the control named SearchTerm from TextBox to input:AutoCompleteBox:

   1: <input:AutoCompleteBox x:Name="SearchTerm" FontSize="14.667" Margin="0,0,10,0" Width="275"
   2:      IsTextCompletionEnabled="True" />

Now we need to provide data for the AutoCompleteBox.  In our Helper.cs class I added the following function:

   1: internal static string[] GetSearchTermHistory()
   2: {
   3:     List<string> searchHistory = new List<string>();
   5:     foreach (var item in IsolatedStorageSettings.ApplicationSettings.Keys)
   6:     {
   7:         searchHistory.Add(item.ToString());
   8:     }
  10:     return searchHistory.ToArray();
  11: }

Then in the Loaded event handler for Search.xaml.cs I added a call to Helper.GetSearchTermHistory():

   1: void Search_Loaded(object sender, RoutedEventArgs e)
   2: {
   3:     SearchResults.ItemsSource = pcv; // bind the DataGrid
   4:     _timer.Start(); // start the timer
   5:     SearchForTweetsEx(); // do the initial search
   6:     SearchTerm.ItemsSource = Helper.GetSearchTermHistory(); // populate autocomplete
   7: }

The result of which now is that when the application loads it will give the user some hinting while they search:

AutoCompleteBox in action


Adding our History view

Now that we have our search history, we can provide some data in a new view called History.xaml.  You should have already created this in the Views folder in a previous step, but if you haven’t, do so now (using the Silverlight Page item template).  Here we’ll want to show perhaps a simple list of all the terms.  We can easily do this using a ListBox in our XAML like this:

   1: <StackPanel>
   2:     <TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}" 
   3:                    Text="Serach Term History"/>
   4:     <ListBox x:Name="SearchTermHistory" />
   5: </StackPanel>

Then using a function we already have in Helper.cs, we can bind new data to the listbox like this in History.xaml.cs:

   1: protected override void OnNavigatedTo(NavigationEventArgs e)
   2: {
   3:     SearchTermHistory.ItemsSource = Helper.GetSearchTermHistory();
   4: }

So we are able to make re-use of a function to display a complete history of our terms:

History pane view

Adding some more navigation functionality

Now that we have a couple of views in the application, notice the navigation framework working.  You can navigate using the buttons, but also the back/forward browser buttons will also trigger the same functionality!

We can actually take this to the next level with the History view now.  In the ListBox I’m adding a SelectionChanged event handler:

   1: <ListBox x:Name="SearchTermHistory" SelectionChanged="SearchTermHistory_SelectionChanged" />

The function looks like this in History.xaml.cs:

   1: private void SearchTermHistory_SelectionChanged(object sender, SelectionChangedEventArgs e)
   2: {
   3:     this.NavigationService.Navigate(new Uri(string.Format("/Search/{0}", 
   4:             SearchTermHistory.SelectedItem.ToString()), UriKind.Relative));
   5: }

Notice the URI format I’m using?  It will end up being /Search/{term}.  We need to instruct our Navigation Frame in the application to map that accordingly.  Go back to MainPage.xaml and find the UriMapper content and add this line:

   1: <uriMapper:UriMapping Uri="/Search/{term}" MappedUri="/Views/Search.xaml?term={term}" />

Now we need to make our Search.xaml page understand this.  In Search.xaml.cs in the OnNavigatedTo I add this functionality:

   1: protected override void OnNavigatedTo(NavigationEventArgs e)
   2: {
   3:     if (this.NavigationContext.QueryString.ContainsKey("term"))
   4:     {
   5:         SearchTerm.Text = this.NavigationContext.QueryString["term"].ToString();
   6:         SearchForTweetsEx();
   7:     }
   8: }

Now when a user goes to the History page and selects an item, it automatically executes the search!


Integrating other controls and 3rd party controls like this will help define your user experience and hopefully provide an better experience for your users of the application.  There are a bunch of 3rd party control vendors producing great sets of components.  Visit my Silverlight Controls post for a list of them.  Be sure to check out the Silverlight Toolkit for others from Microsoft and a sample application showing all the controls.

We’ve got our application pretty well working, but the UI could use some more polish, in the next step in part 6 let’s show how we can better template this without affecting functionality.

| Comments

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:         }
  10:         public static List<Fruit> GetFruitChoices()
  11:         {
  12:             List<Fruit> choices = new List<Fruit>();
  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" });
  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<Fruit> choices = Fruit.GetFruitChoices();
   5:     public Page()
   6:     {
   7:         InitializeComponent();
   8:         Loaded += new RoutedEventHandler(Page_Loaded);
   9:     }
  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:                 }
  21:                 Fruit f = item as Fruit;
  23:                 if (f == null)
  24:                 {
  25:                     return false;
  26:                 }
  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.

| Comments

The other day I wrote a simple little Silverlight application using a DataGrid to help navigate the TechEd DVD contents.  My code was admittedly quick and dirty.  I loaded up some data, and based on some events re-filtered and re-bound that data.  After thinking about it I am not sure why I didn’t just use some existing controls to help me do that work.  I found that AutoCompleteBox from the new Silverlight Toolkit would do this for me.

One thing that the AutoCompleteBox does quickly is provide filtering for simple string data.  But what about custom types?  My data in the TechEd application has a List<TechEdSession> where TechEdSession is respresented as such:

   1: public class TechEdSession
   2: {
   3:     public string DiscNumber { get; set; }
   4:     public string SessionCode { get; set; }
   5:     public string Track { get; set; }
   6:     public string SessionTitle { get; set; }
   7:     public string Speakers { get; set; }
   8:     public string TechEdConf { get; set; }
   9: }

If you wire up the AutoCompleteBox.ItemsSource to my List<> nothing will happen.  Why?  Well the control doesn’t know how to understand the data exactly.  So we have to help it.  It knows the data is there but doesn’t know what to return based on the filter.

This is simply done using the ItemFilter property on the control.  We can easily provide a Lambda expression to help the control understand the filter.  After setting the ItemsSource property to the results of my LINQ query, I add the expression to help the control understand the data:

   1: SessionFilter.ItemsSource = sessions;
   2: SessionFilter.ItemFilter = (search, item) =>
   3:     {
   4:         TechEdSession session = item as TechEdSession;
   5:         if (session != null)
   6:         {
   7:             string filter = search.ToLower();
   8:             return ((session.SessionCode.ToLower().Contains(filter) || 
   9:                 session.SessionTitle.ToLower().Contains(filter) || 
  10:                 session.Speakers.ToLower().Contains(filter)) &&
  11:                 session.TechEdConf.ToLower().Contains(((ComboBoxItem)ConfSelector.SelectedItem).Content.ToString().ToLower()));
  12:         }
  14:         return false;
  15:     };

That’s it.  I’ve now defined the filter for the control so as the user types data it does the appropriate filtering logic and displays the results.  I’ve chosen to still display the results as a DataGrid for easy reading in this application, but could have also used an ItemTemplate if I desired.  The ItemFilter expects a search predicate to be passed to it.

So the resulting application has the same capabilities, but a lot less code for me to write.  Here’s the ending result:

So if you need to implement AutoCompleteBox and have it search/filter your custom types, be sure to supply your own ItemFilter to help the control know where to look!