Sometimes we need to load very large amount of items in a ComboBox and let the user select one of them. Usually in those cases the ComboBox allows typing in order to auto-select the items, that contain the text in the control. Such scenarios are possible with RadComboBox for Silverlight and now I will show you a simple application, that demonstrates what I think is a better practice for the implementation.
We want to put all words from the English dictionary of RadSpell for ASP.NET in a RadComboBox for Silverlight and allow the user to select one of them, that will be displayed in a TextBlock above the ComboBox. First I created a WCF web service with a single public method GetWords(string prefix, int count), that will return an array containing the first [count] words, that start with the specified prefix:
public string[] GetWords(string prefix, int count) { bool emptyPrefix = string.IsNullOrEmpty(prefix); return (from w in Words where emptyPrefix || w.StartsWith(prefix) select w).Take(count).ToArray(); }
I added a simple performance optimization by caching the complete list of words:
private static List<string> Words { get { List<string> words = HostingEnvironment.Cache["WordsService.Words"] as List<string>; if (words == null) { words = LoadEnglishWords(); HostingEnvironment.Cache["WordsService.Words"] = words; } return words; } }
In the Silverlight application I created a class, named WordsDataSource, that implements INotifyPropertyChanged. We need this interface because we will bind the ComboBox to a WordsDataSource instance:
public class WordsDataSource : INotifyPropertyChanged { private string text; private WordsServiceClient client = new WordsServiceClient(); public WordsDataSource() { this.client.GetWordsCompleted += this.GetWordsCompleted; this.Words = new ObservableCollection<string>(); this.Text = string.Empty; } public ObservableCollection<string> Words { get; private set; } public string Text { get { return this.text; } set { if (this.text != value) { this.text = value; this.OnPropertyChanged("Text"); this.GetWords(); } } } private void GetWords() { this.client.GetWordsAsync(this.Text, MaxWords); } private void GetWordsCompleted(object sender, GetWordsCompletedEventArgs e) { ... } #region INotifyPropertyChanged ... #endregion }
The idea is simple: the Words collection will contain all strings that should be visible in the ComboBox and Text will contain the text of the ComboBox. When the Text property is changed, we will update the Words collection to reflect the new state. The GetWordsCompleted() method contains logic for adding/removing words the Words collection, when a new chunk of words arrives from the server.
Last, but not least, I added several TextBlock controls and a RadComboBox to Page.xaml:
<UserControl x:Class="AutocompleteCombo.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:telerik="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Input"> <Grid x:Name="LayoutRoot"> <TextBlock>The selected text is:</TextBlock> <TextBlock Margin="0,20,0,0" Text="{Binding Text, Mode=OneWay}" /> <TextBlock Margin="0,40,0,0">Type some text:</TextBlock> <telerik:RadComboBox Margin="0,60,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="200" ItemsSource="{Binding Words}" Text="{Binding Text, Mode=TwoWay}" IsTextSearchEnabled="False" IsEditable="True" GotFocus="RadComboBox_GotFocus" /> </Grid> </UserControl>
Since the built-in auto-complete in RadComboBox would mess with my logic for retrieving words from the web service, I disabled it by setting IsTextSearchEnabled="False". In order to make the bindings work I set the DataContext property of the page to an instance of WordsDataSource:
public Page() { InitializeComponent(); this.DataContext = new WordsDataSource(); }
And we are set. The source code could be downloaded from here: