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.
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;
}
}
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"); }
}
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;
}
}
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;
}
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);
}
}
}
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
}
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.
| You must Sign In to use this message board. | ||||||
|
||||||
|
||||||
|
||||||