Rediscovering the Obvious

…stumbling in the footsteps of greatness

Silverlight AutoCompleteBox and MVVM

with 2 comments

First, a warning

This is not an introduction to the AutoCompleteBox. If you’re looking for a basic overview of the there’s nothing better than Jeff Wilcox’s intro at http://www.jeff.wilcox.name/2008/11/autocompletebox-missing-guide/. Read that, then come back. It’s where I learned by example.

There’s no direct anchor, but about half way down the page you’ll find a section titled “Building a custom search filter” and I’ll be picking up from there. His advice is “Here’s a sample item filter, set in the Loaded event of my page”, which is the first indicator that this isn’t the way to go if you’re doing MVVM… what follows is a way around that.

Next, the backstory

One pattern I’ve adopted in Silverlight development with MVVM is to introduce a helper object when I’m binding to a ListView or ItemsControl’s ItemsSource property. I call this object a “Shim” because it a slightly hackish way of solving what would otherwise be a much bigger problem creating a lot of rebuilding needs. Essentially, the Shim becomes the new DataContext that sits behind each bound item, becoming a mini-ViewModel that matches the mini-View defined by the DataTemplate/ItemTemplate. My typical need is that I need to accomplish two goals:

  1. Bind to the data in a DTO or Domain Object in some interesting way
  2. React to a user’s operation against that item in some way specific to the data instance.

Unfortunately, markup like this doesn’t work in the ItemTemplate because the DataContext is now set to your specific DTO instance rather than to the original ViewModel instance.

<HyperlinkButton Content="{Binding Value}" 
             command:Click.Command="{Binding ItemSelectedCommand}" 
             command:Click.CommandParameter="{Binding}" />

Instead, I introduce the Shim with an assignment something like this:

this.Clients = insurer.Clients.Select(client =>

    new ClientSelectedShim(client, SelectionChangedCommand)).ToObservableCollection();

and then bind as follows:

<HyperlinkButton Content="{Binding Value}" 
             command:Click.Command="{Binding ItemSelectedCommand}" 
             command:Click.CommandParameter="{Binding OriginalData}" 
             />

The Shim instance simply exposes the provided DelegateCommand as a property, along with the original DTO and often an intended display value. (Thus, I bind to “Value” instead of “OriginalData.Value”).

Overall, this feels like the cleanest of several different approaches I’ve tried to get around this binding issue in Silverlight. [1] I tend to do this using the Prism library, but it’s also applicable to other simple MVVM approaches. For example, the same would apply with using an EventTrigger and InvokeCommandAction to activate the command as such:

<i:Interaction.Triggers> 
    <i:EventTrigger EventName="Click"> 
        <i:InvokeCommandAction Command="{Binding ItemSelectedCommand}" /> 
    </i:EventTrigger> 
</i:Interaction.Triggers>

Finally, the Point

All this rambling is a lead up to the challenge I hit today: I added a Shim into the list of items used to populate an AutoCompleteBox, because I needed to respond to the user with some item-specific behaviors in the popup that contains the matching values. As soon as I added the Shim, I couldn’t use the simple FilterMode=”Contains”, because it was comparing against the type of the shim rather than the much simpler string I was giving it before. Because I suddenly had a Shim involved and couldn’t dodge the complexity of having a DTO by just yielding the display value any longer, I was forced to use FilterMode=”Custom”. {EDIT [3]} Problem is, MVVM strongly discourages me from writing code anywhere that looks like Jeff’s sample:

MyAutoCompleteControl.ItemFilter = (txt, i) => Stock.IsAutoCompleteSuggestion(txt, i);

The good news is that binding is incredibly powerful, so here’s what I can do instead:

<input:AutoCompleteBox  
   FilterMode="Custom" 
   MinimumPopulateDelay="250" 
   ItemsSource="{Binding Clients}" 
   SelectedItem="{Binding SelectedClient}" 
   ItemTemplate="{StaticResource ClientSelectionItem}" 
   ItemFilter="{Binding ShimFilter}" 
/>

<!—In Resources –> 

<DataTemplate x:Key="ClientSelectionItem">  
   <StackPanel Orientation="Horizontal" > 
      <HyperlinkButton Content="{Binding Value}" 
         command:Click.Command="{Binding ItemSelectedCommand}" 
         command:Click.CommandParameter="{Binding OriginalData}" 
      /> 
    </StackPanel> 
</DataTemplate>

And, in the ViewModel: [2]

public AutoCompleteFilterPredicate<object> ShimFilter 
{ 
    get 
    { 
        return new AutoCompleteFilterPredicate<object>( 
            (str, item) => { return IsFilterMatch( str, item ); }); 
    } 
}
private bool IsFilterMatch(string valueToCheck, object item) 
{

    ClientSelectedShim shim = item as ClientSelectedShim; 
    if (shim == null) 
        return false; 
    return shim.Value.Contains(valueToCheck); 
}


It wasn’t immediately obvious to me, but this definitely lets me put my comparison code in the ViewModel where it belongs while keeping the expression of the UI structure and behavior. Now I can wire the special effects!

 

[1] One of the most promising ways I’ve otherwise tried involved binding using ElementName to get “out of” the ItemTemplate and back to an element on at the parent level, typically the containing list itself. This will work as long as you don’t change the ItemsPanel to a non-default panel. Making that change hits some bug (feature?) in Silverlight I can’t properly characterize and causes lots of pain figuring out what happened. Be warned!

[2] The property has to stay templated to the <object> type or there will be a binding expression error when the View is first bound against the ViewModel… covariance doesn’t apply here. More info here. Second, it appears it must be an actual property, the binding failed when I tried to have a simple field initialized to the Predicate.

[3] After figuring this all out, I did of course come across the incredibly useful ValueMemberPath property, which allowed me to revert to a simpler design for my specific case. However, if you are doing custom filters (especially if you’re checking against multiple properties, which I’ve done in the past), this technique will still be quite valuable.

Written by erwilleke

October 24th, 2010 at 7:07 pm

Posted in Uncategorized

2 Responses to 'Silverlight AutoCompleteBox and MVVM'

Subscribe to comments with RSS or TrackBack to 'Silverlight AutoCompleteBox and MVVM'.

  1. this is very helpful , thanks sir :)

    Mahmoud

    26 Oct 11 at 2:48 pm

  2. Glad it was useful!

    erwilleke

    26 Oct 11 at 3:31 pm

Leave a Reply