New to Telerik UI for WinForms? Download free 30-day trial

Dynamic DataSource in AutoCompleteBox/DropDownList

Environment

Product Version Product Author
2020.1.113 RadAutoCompleteBox/RadDropDownList for WinForms Desislava Yordanova

Description

This tutorial demonstrates a sample approach how to simulate server-side auto complete functionality.

autocompletelist-with-dynamic-datasource

Solution

As a starting point we will use the Server side auto complete for RadDropDownList KB article. Since it is relevant for RadDropDownList, this article will show you how to update the provided solution and adjust it for RadAutoCompleteBox.

The different auto-complete helpers are relevant for RadDropDownList and they are not available in RadAutoCompletebox. However, you can use the RadDropDownList and just insert a RadAutoCompleteBox in its editable area in order to obtain the tokens. Then, hide the arrow button and Voilà!


RadDropDownList autoCompleteList = new RadDropDownList();
RadAutoCompleteBoxElement acb = new RadAutoCompleteBoxElement();

public RadForm1()
{
    InitializeComponent();
    autoCompleteList.Parent = this;
    autoCompleteList.Dock = DockStyle.Top;

    autoCompleteList.DropDownListElement.EditableElement.TextBox.Visibility = Telerik.WinControls.ElementVisibility.Collapsed;
    autoCompleteList.DropDownListElement.EditableElement.Children.Add(acb);
    autoCompleteList.DropDownListElement.ArrowButton.Visibility = Telerik.WinControls.ElementVisibility.Collapsed;
    acb.TextChanged += acb_TextChanged;

    List<Item> items = new List<Item>();
    for (int i = 0; i < 100000; i++)
    {
        items.Add(new Item(i, Guid.NewGuid().ToString()));
    }
    autoCompleteList.AutoCompleteValueMember = "Title";

    autoCompleteList.DropDownListElement.AutoCompleteSuggest = new ServerAutoCompleteSuggestHelper<Item>(autoCompleteList.DropDownListElement, items); 
    autoCompleteList.DropDownListElement.AutoCompleteSuggest.DropDownList.SelectedIndexChanged += DropDownList_SelectedIndexChanged;
}

private void DropDownList_SelectedIndexChanged(object sender, Telerik.WinControls.UI.Data.PositionChangedEventArgs e)
{
    if (e.Position > -1)
    {
        autoCompleteList.DropDownListElement.AutoCompleteSuggest.DropDownList.SelectedIndexChanged -= DropDownList_SelectedIndexChanged;
        string selectedText = autoCompleteList.DropDownListElement.AutoCompleteSuggest.DropDownList.Items[e.Position].Text;
        acb.Text = acb.Text.Substring(0, Math.Max(0, acb.Text.LastIndexOf(";") + 1)) + selectedText + ";";
        autoCompleteList.DropDownListElement.AutoCompleteSuggest.DropDownList.SelectedIndexChanged += DropDownList_SelectedIndexChanged;
    }
}

private void acb_TextChanged(object sender, EventArgs e)
{
    string filterCriteria = acb.Text.Substring(Math.Max(0, acb.Text.LastIndexOf(";") + 1));
    autoCompleteList.DropDownListElement.EditableElement.TextBox.Text = filterCriteria;
    autoCompleteList.DropDownListElement.AutoCompleteSuggest.ApplyFilterToDropDown(filterCriteria);
    autoCompleteList.DropDownListElement.AutoCompleteSuggest.ShowDropDownList();
}

public class Item
{
    public int Id { get; set; }

    public string Title { get; set; }

    public Item(int id, string title)
    {
        this.Id = id;
        this.Title = title;
    }
}

public class ServerAutoCompleteSuggestHelper<T> : AutoCompleteSuggestHelper
        {
            public IQueryable<T> Data { get; private set; }

            public int MaxItems { get; set; }

            public ServerAutoCompleteSuggestHelper(RadDropDownListElement owner, IEnumerable<T> data,
                int maxItems = 1000) : base(owner)
            {
                this.Data = data.AsQueryable();
                this.MaxItems = maxItems;
                ExpressionBuilder.Instance.Optimize(this.Data);
            }

            public override void ApplyFilterToDropDown(string filter)
            {
                this.DropDownList.BeginUpdate();

                this.DropDownList.ListElement.Items.Clear();

                var dataItemsExp = ExpressionBuilder.Instance.BuildContainsExpression<T>(this.Owner.AutoCompleteValueMember, filter);
                var dataItemsQuery = this.Data.Where(dataItemsExp).Take(this.MaxItems);
                var dataItems = dataItemsQuery.ToList();

                var selectExp = ExpressionBuilder.Instance.BuildSelectExpression<T>(this.Owner.AutoCompleteValueMember);
                var displayItemsQuery = dataItemsQuery.Select(selectExp);
                var displayItems = displayItemsQuery.ToList();

                for (int i = 0; i < dataItems.Count; i++)
                {
                    var dataItem = dataItems[i];
                    var displayMember = displayItems[i];
                    this.DropDownList.ListElement.Items.Add(new RadListDataItem(displayMember, dataItem));
                }

                this.DropDownList.EndUpdate();
                this.Owner.SelectionLength = this.Owner.Text.Length;
            }
        }

public class ExpressionBuilder
        {
            private static ExpressionBuilder instance;
            private static readonly object syncRoot = new object();
            private HashSet<Type> optimizationCache = new HashSet<Type>();

            public static ExpressionBuilder Instance
            {
                get
                {
                    lock (syncRoot)
                    {
                        if (instance == null)
                        {
                            lock (syncRoot)
                            {
                                instance = new ExpressionBuilder();
                            }
                        }
                    }

                    return instance;
                }
            }

            public bool Optimize<T>(IQueryable<T> collection)
            {
                if (this.optimizationCache.Contains(typeof(T)))
                {
                    return false;
                }

                this.optimizationCache.Add(typeof(T));
                collection.ToList();
                return true;
            }

            public System.Linq.Expressions.Expression<Func<T, TResult>> BuildMethodCallExpression<T, TResult>(string parameter, string property, Type ownerType,
                string methodName, int parametersCount)
            {
                var param = Expression.Parameter(typeof(T));
                var constant = Expression.Constant(parameter);
                var prop = Expression.Property(param, property);
                var method = ownerType.GetMethods().First(x => x.Name == methodName && x.GetParameters().Length == parametersCount);
                var body = Expression.Call(prop, method, constant);

                return Expression.Lambda<Func<T, TResult>>(body, param);
            }

            public Expression<Func<T, bool>> BuildContainsExpression<T>(string property, string filter)
            {
                var dataItemsExp = this.BuildMethodCallExpression<T, bool>(filter, property, typeof(String), "Contains", 1);
                return dataItemsExp;
            }

            public Expression<Func<T, string>> BuildSelectExpression<T>(string property)
            {
                var param = Expression.Parameter(typeof(T));
                var prop = Expression.Property(param, property);
                var expression = Expression.Lambda<Func<T, string>>(prop, param);
                return expression;
            }

            public Expression<Func<T, bool>> BuildStartsWithExpression<T>(string property, string filter)
            {
                var lambda = this.BuildMethodCallExpression<T, bool>(filter, property, typeof(String), "StartsWith", 1);
                var newBody = Expression.And(lambda.Body,
                    Expression.NotEqual(Expression.Property(lambda.Parameters.First(), property), Expression.Constant(filter)));
                var newExpression = Expression.Lambda<Func<T, bool>>(newBody, lambda.Parameters);

                return newExpression;
            }
        }

Note that this is just a sample approach and it may not cover all possible cases. Feel free to modify and extend it in a way which suits your requirements best.