background image
  logo  
Icon

Introducing TagBox

Tue Feb 16th 2010 by Brett
Recently I wanted to write a TO: box in a Silverlight that worked similar to the one Outlook has. It is a neat control that allows you to "resolve" items based on text matching. Unlike a standard AutoComplete box this control allows you to "resolve" multiple items inline. Once an item is resolved it acts as a cohesive element instead of individual letters - what I call a tag. The tag (a resolved email address in Outlook) can be clicked on which brings up more information about the address. A number of popular sites use this style of control (js based) including Hotmail (the To box obviously) and Facebook (their email function).

Effectively this control allows you to build one-to-many relationships between an email and its intended recipients in a very natural way. Obviously the key to this control working effectively is the excellent keyboardability (is that a work?) that it offers. Aside from the usage described above there are a number of cases where such a control would be useful. One example might be applying a set of categories to a blog post. In an online store example it might be used to define the areas of the store where an item might appear. This control is extremely effective when the relationship between the entities is simple - it can track the relationship and the order of the relationship but no additional data about the relationship.

Example

This example starts with our familiar case of an Email where the user would like to enter a number of email addresses. If you start typing an address it performs an async search for the email address based on name and address. A popup is displayed if matches are found without losing your position in the TagBox and you can presss up and down to navigate the matches. Also - if you enter a valid email format such as test@test.com, you will see it appear in the bound listbox to the right. This control was designed to be databound, and in particular work well in MVVM Patterns.

There are a few interesting properties on the Control:
  • ItemsSource – IList value represents the set of resolved matches in the control. It also supports INotifyCollectionChanged so it can be bound to ObservableCollection allowing external changes to be propagated to the control automatically.
  • DisplayMemberPath – I used a similar name here to what the ListBox control uses to determine which property is displayed when the results are shown either in the tags or the search results.
  • SearchMethod –a Action dependency property provides the logic for taking the text entered and execting a query/lookup that in turn returns SearchResults. This method can be synchronous or asynchronous, so long as the returned results are placed in SearchResults.
  • SearchResults – to support Synchronous or Async searching, when this property is populated search results are shown to the user. Make sure the class to which SearchResults is bound to implements INotifyPropertyChanged so TagBox is aware of SearchResult changes.


In an MVVM scenario the four important properties above you can simply bind these values to a ViewModel. In my example I have an EmailViewModel (implement INotifyPropertyChanged) which is bound as follows (position omitted for clarity).
<tsm:TagBox SearchMethod="{Binding SearchMethod}" SearchResults="{Binding SearchResults}"
          DisplayMemberPath="DisplayName" ItemsSource="{Binding To, Mode=TwoWay}" />
  • ItemsSource -> To (ObservableCollection)
  • DisplayMemberPath = “DisplayName” which is a property of Contact
  • SearchMethod -> SearchMethod(Action which points to a method FindMatchingContacts(string))
  • SearchResults -> SearchResults(IEnumerable)


The following code illustrates the implementation of SearchMethod in EmailViewModel.cs. You will note that although this is a synchronous method in this example SearchResults can be populated as a result of an async web service invokation.

        #region SearchMethod (INotifyPropertyChanged Property)
        private Action<string> _searchMethod;
        public Action<string> SearchMethod
        {
            get { return _searchMethod; }
            set
            {
                if (value == _searchMethod) return;
                _searchMethod = value;
                base.OnPropertyChanged("SearchMethod");
            }
        }
        #endregion

        public void FindMatchingContacts(string searchText)
        {
            if (searchText.Length >= 1)
            {
                var contacts = from cs in ContactCollection.Members
                               where cs.EmailAddress.StartsWith(searchText, StringComparison.InvariantCultureIgnoreCase) 
                                    | cs.DisplayName.ToLower().Contains(searchText.ToLower())
                               select cs;

                SearchResults = contacts.ToList();
            }
            else
                SearchResults = null;
        }
Fig 1. Code from EmailViewModel.cs demonstrating search method

The control does all the heavy lifting and it simply needs to be provided with search logic and search results. This fits well with MVVM and the ViewModel in my example could easily be bound to entirely different lookup UI not dependent on TagBox.

Validation

This aspect of the control probably gave me the most consternation. At a high level the control is dealing with a non homogenous collection of item types. In the example of an email TO: box we have:

  • Contacts: resolved items that were matched via search
  • Text Addresses: unresolved items that may have been entered by the user


In the email case – text addresses are perfectly fine, so long as they pass some kind of validation. So the question became – how do you deal with a non-homogenous collection of items without it being a mess on the ViewModel side (or the non-control side if not using MVVM). Initially – I dealt with the problem by creating two collections – one for resolved that was used for databinding – and another for unresolved that was readonly (trying to synch two collections to the UI was going to be awful). As I got into it I realized this was about as good an idea as a “Larry the Cable Guy” movie (e.g. bad idea).

I realized that ideally the collection would be homogenous, but how to convert text entries into strongly typed objects. I didn’t want to have to expose a special container class outside the control as it would corrupt the layer of separation I was trying to achieve. Ultimately I added an Func UnresolvedValidationMethod property to the TagBox where you can specify your own function for examining the text entered and returning it classed as your bound object type. In the email example I check the format of the address and return a Contact. The great thing about this is that on the ViewModel side you have a single collection that holds all the addresses, and ultimately you could decide how to deal with these newly created contacts. (for example – you could just send a message to them, or you could save them to your Contact repository when you detect that their Ids are 0)

Lastly – there could exist a set of unresolved items that would not validate (if you entered ASDF as an email address for example). These are not included in ItemSource, but can be found in InvalidItems. The logic here is that if we cannot pass the Validation method then they are not robust enough to be included in that set. Instead they are decorated (a dashed underline OOB) and you can decide how to handle them. Common ways would be

  1. In an email scenario – do not allow the message to be sent until they are corrected – could present a custom UI for correcting them.
  2. Prompt and allow them to be discarded
  3. Discard them automatically


Validation Properties

  • ValidateUnresolved – boolean property indicates whether or not validation should be performed – if true an UnresolvedValidationMethod must be provided
  • UnresolvedValidationMethod – Function takes a text entry and converts it to an object compatible with the objects stored in ItemsSource. Once validated the text entry, now a correctly typed object, is placed in ItemsSource in the correct location.
  • VisualState “Invalid” – The UnresolvedTag control template contains an “Invalid” state that can be customized. It is set whenever text cannot be validated.

The following code demonstrates the UnresolvedValidationMethod as implmented in EmailViewModel.cs. Note - asynchronous validation is not supported at this point.

        public Contact IsValidEmailAddress(string email)
        {
            bool valid = Regex.IsMatch(email, @"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}" +
                                              @"\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\" +
                                              @".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$");
            Debug.WriteLine("Valid Email? " + email + " " + valid);
            if (!valid) return null;
            return new Contact() { DisplayName = email, EmailAddress = email };
        }
Fig 2. Code from EmailViewModel.cs demonstating text validation

Customization

TagBox is implemented as a CustomControl, meaning that its templates can be customized very easily to provided a dramatically different looking appearance. There are 3 main templates that can be customized

TagBox – this template is responsible for the actual border around the TagBox and the panels that hold the tags. OOB – it is essentially a ScrollViewer that contains a WrapPanel and a Popup (search results container)



ResolvedTag – after an item is resolved it gets translated into a ResolvedTag. OOB it has a rounded border with an X so you can remove an item from the tagbox with the mouse. A custom template could be used here to display more information about the tag if so desired. DataBinding occurs in the Content field by default.


UnresolvedTag – this holds the text entered prior to being resolved. It is a pretty simple template who’s most important component is a TextBox with very little styling.



The example below has been restyled to be more appropriate to a set of categories. (Templates are included in the sample project)



Licensing

Although I am not releasing the source code for this control, I am allowing folks to make use of the control for use in personal projects. I go back and forth on the free versus paid approach and my general rule of thumb is that if it takes me more than a week to build something, and I intend on supporting it in the future, it is worth a nominal fee. In this case I ended up putting a little under 3 weeks into the project and documenation and I think it has a lot of value.

Watermarked Control – you may use the control for non-commercial purposes so long as the "thesilvermethod.com" watermark is visible.

Business License - a non-watermarked version of the control is available. I haven't gotten my act together yet on setting up the online purchasing, but in the short term if you would like to use this, please contact me directly - bbalmer@setpowersoftware.com.

Updated 2-23-2010 You can now purchase developer licenses for TagBox here. After purchasing a license a link will be emailed to you that will allow you to download a non-watermarked version of the TagBox assembly.

Here are the details of the license.

Demo Code includes TagBox assembly and demo project shown in this blog post.

6 Comments     follow

Any chance you would consider adding support for SelectedItem? I have a scenario where I am performing a second layer of validation on the submission of my form. If I detect certain objects I would like to notify the user and have the focus set on that object. Sweet control BTW. Jim
posted by jWalker on Wed Feb 24th 2010 at 3:39 PM
Jim- that is an interesting idea and at first blush not that difficult to do. I hadn't really considered using SelectedItem for that (instead I raise events for selection) but I like the idea because it would support databinding for other use cases. For example suppose when an email address got focus that we would show relationship information or a picture to the right of the TagBox. Let me see what I can do.
posted by Brett on Wed Feb 24th 2010 at 3:48 PM
Jim - I just updated the release to v.1.0.0.3 which includes support for SelectedItem. The demo project in the download also has code demonstrating the use of hte property. It is two-way bindable so if you update SelectedItem it will be reflected by the focused item in TagBox.
posted by Brett on Wed Feb 24th 2010 at 6:19 PM
Hi Ken-
I would check to make sure that all the necessary properties are configured: ItemsSource,DisplayMemberPath,SearchMethod,SearchResults

feel free to followup with me via bbalmer at setpowersoftware.com
posted by Brett on Wed Feb 23th 2011 at 3:47 PM
The current version of this TagBox allows the SearchMethod property of a type ICommand insteaf of Action string...?
posted by jmartarelli on Wed Mar 30th 2011 at 6:29 PM
Hello, Im testing this control in my project and it really attends my needs but there is a bug in it. When I choose an item from a list and right after press enter again and again... it duplicates the item in the resolved list.

How do I solve it?
posted by jmartarelli on Wed Apr 6th 2011 at 1:07 PM
Login to post comments
 

Recent Posts

By Month