Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Silverlight Auto Complete Treeview

0.00/5 (No votes)
18 Jun 2009 1  
Silverlight auto-complete treeview which takes hierarchical data as its datasource.

Introduction

I was developing a project in Silverlight in which there was a requirement to populate auto-complete data as a treeview as they were hierarchical data.

The data was like Country / State / City / Area... and user could select country or state or city or area. In short, data was hierarchical and the user could select data from any level from the treeview. It should work like an auto-complete control which is readily available in Silverlight 3.0.

First, I tried to identify any such code available on the net, but with no luck, and then I divided the problem and tried to solve it and get the output which I wanted.

Finally, when I was through, I felt I should share it with you people as it will not only serve as an auto-complete control, but also be useful in a number of other issues which I faced during the development of this control.

Step 1: Use the ViewModel Concept in Treeview

As our basic requirement is to select a particular record/object from a treeview which the user types, we need to make that object highlighted (selected) and also expand all the nodes so that the particular object is visible. To make this work, first, you need to use the concept of viewmodel, so we have to bind the TreeViewItem's "IsExpanded" and "IsSelected" properties with our data class. This we can do by modifying the control template as the popular method of setter does not work in WPF.

So, inherit TreeView as well the TreeViewItem class and override the GetContainerForItemOverride method.

public class SilverlightTreeView : TreeView
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        SilverlightTreeViewItem tvi = new SilverlightTreeViewItem();
        Binding expandedBinding = new Binding("IsExpanded");
        expandedBinding.Mode = BindingMode.TwoWay;
        tvi.SetBinding(SilverlightTreeViewItem.IsExpandedProperty, expandedBinding);
        Binding selectedBinding = new Binding("IsSelected");
        selectedBinding.Mode = BindingMode.TwoWay;
        tvi.SetBinding(SilverlightTreeViewItem.IsSelectedProperty, selectedBinding);
        return tvi;
    }
}

Step 2: Create a Hierarchical Data Class

Here, you have to create a data class which contains your data properties as well as the "IsExpanded" and "IsSelected" properties. Also, you need a self referencing ObservableCollection (or any other collection) property inside your class. Also implement the INotifyChanged interface so you can bind two way.

public class HierarchicalCity : INotifyPropertyChanged
private bool isSelected;
public bool IsSelected
{
    get { return isSelected; }
    set
    {
        isSelected = value;
        OnPropertyChanged("IsSelected");
    }
}

private ObservableCollection<hierarchicalcity> _SubClass = 
        new ObservableCollection<hierarchicalcity>();
public ObservableCollection<hierarchicalcity> SubClass
{
    get { return _SubClass; }
    set { _SubClass = value; OnPropertyChanged("SubClass"); }
}

Step 3: Select an Item in the TreeView Which is Similar to What the User Types in the Textbox

As soon as the user types in the textbox, we will search the typed text in the treeview in a recursive loop; here, I have used the "StartsWith" string function to check the "cityname" property with the user's data. After identifying the first match, we can stop searching further; I have just set the selected object in the "selectedTV" variable.

Now, we also need to expand all the parent nodes so our selected or searched node can be visible; for this, we are using the "ExpandAllParents" function. Inside it, I am again recursively looping the treeview and then setting the "ISExpanded" property to true; so due to the binding which we have seen earlier, it will expand the treeview node automatically. Also, set the IsSelected property to true for the selected object.

private void ReturnChildconditionsRecursively(
        ObservableCollection<HierarchicalCity> lst, string val)
{
    for (int j = 0; j < lst.Count; j++)
    {
        if (lst[j].CityName.StartsWith(val, 
                   StringComparison.OrdinalIgnoreCase))
        {
            selectedTV = lst[j];
            ExpandAllParents(lst[j]);
            break;
        }
        else if (lst[j].SubClass != null)
        {
            ReturnChildconditionsRecursively(lst[j].SubClass,val);
        }
    }

}

private bool ApplyActionToSuperclasses(HierarchicalCity itemToLookFor,
        Action<HierarchicalCity> itemAction)
{
    if (itemToLookFor == this)
    {
        return true;
    }
    else
    {
        foreach (HierarchicalCity subclass in this.SubClass)
        {
            bool foundItem = subclass.ApplyActionToSuperclasses(itemToLookFor,
                                                                itemAction);
            if (foundItem)
            {
                itemAction(this);
                return true;
            }
        }
        return false;
    }
}

Step 4: On Press of Key (Down/Up), Move Within the Treeview

To do this, first we have to handle the KeyDown event of the textbox and then we have to shift our focus from the textbox to the treeview node.

For this, I require an object of the treeview node. If we are selecting an item by typing it in the textbox, it will be available easily, but if we are moving inside a treeview or directly shifting our focus to the treeview by using the mouse, then we will get the data object in the selected item changed event, so we need to find the container of the data object, i.e., the treeview node.

Now to do this, I have identified that extension methodz are readily available. I have just used one of them in my current solution, but other methods are also equally useful in other scenarios.

private static TreeViewItem ContainerFromItem(
        ItemContainerGenerator parentItemContainerGenerator,
        ItemCollection itemCollection, object item)
{
    foreach (object curChildItem in itemCollection)
    {
       TreeViewItem parentContainer = (
           TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);
        if (parentContainer == null)
            return null;
        TreeViewItem containerThatMightContainItem = (
           TreeViewItem)parentContainer.ItemContainerGenerator.ContainerFromItem(item);
        if (containerThatMightContainItem != null)
            return containerThatMightContainItem;
        TreeViewItem recursionResult = ContainerFromItem(
           parentContainer.ItemContainerGenerator, parentContainer.Items, item);
        if (recursionResult != null)
            return recursionResult;
    }
    return null;
}

Step 5: Close Treeview After Selecting Item or on Press of Esc Key

Similar to the function ExpandAllParents, I have a function "CollapseAll" which I use to collapse an entire treeview.

Also, I have used a popup control which is available in Silverlight 3.0.

private void ApplyActionToAllItems()
{
    Stack<HierarchicalCity> dataItemStack = new Stack<HierarchicalCity>();
    dataItemStack.Push(this);

   while (dataItemStack.Count != 0)
    {
        HierarchicalCity currentItem = dataItemStack.Pop();
        ActionCollapse(currentItem);//itemAction(currentItem);
        foreach (HierarchicalCity childItem in currentItem.SubClass)
        {
            dataItemStack.Push(childItem);
        }
    }
}

Step 6: Automatically Close Treeview if Focus Shifted to Any Other Control

If the user directly shifts focus from the mouse or by using tab to any other control available in the UI, it is required to close the treeview. For this, I have used a FocusManager and checked the next control which is going to get the focus. If it is not the treeview or our textbox and if the popup control is open, then hide it.

DependencyObject dp = ((
    DependencyObject)System.Windows.Input.FocusManager.GetFocusedElement());
' As I have also provided the double click facility to select item from
' treeview and close it, I have to put one condition in click event for treat
' it as double click.
if ((DateTime.Now.Ticks - LastTicks) < 2310000)
{
    //Code here 
}

Features

  • Supports hierarchical data.
  • User can select data from any level from a treeview.
  • On typing in the textbox, the treeview will populate and move to the first record starting from the same word.
  • Treeview will automatically close if the user typed data is not available in the treeview data.
  • City will be selected: on pressing the Enter key in the textbox, or on double click inside the treeview, or on pressing the Enter key in the treeview.
  • On pressing the Esc Key, we can move from the treeview to the textbox, same for pressing Enter key in the treeview.
  • On Key down or Key up in the textbox, we will move to the treeview.
  • If users move to any other control other than the treeview or textbox, the treeview will automatically close itself.

Conclusion

I hope this will help you people having similar requirements. I have tried to make this program as a one stop solution for several difficulties I have faced during this development. The code I am submitting is not well commented, and also can be improved at certain places, so forgive me for that.

Any suggestion /critics/ feedback are highly appreciated.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here