65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight 自动完成树状视图

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (7投票s)

2009 年 6 月 18 日

CPOL

5分钟阅读

viewsIcon

43410

downloadIcon

1620

Silverlight 自动完成树形视图,它以分层数据作为数据源。

引言

我正在开发一个 Silverlight 项目,其中有一个需求是将自动完成数据作为树形视图填充,因为它们是分层数据。

数据类似于国家/州/城市/地区... 用户可以选择国家、州、城市或地区。简而言之,数据是分层的,用户可以从树形视图的任何级别选择数据。它的工作方式应该类似于 Silverlight 3.0 中现有的自动完成控件。

首先,我尝试在网上查找任何可用的代码,但一无所获,然后我将问题分解并尝试解决它,得到了我想要的结果。

最后,当我完成时,我觉得我应该与大家分享,因为它不仅可以作为自动完成控件,而且在开发此控件过程中我遇到的许多其他问题中也很有用。

步骤 1:在 Treeview 中使用 ViewModel 概念

由于我们的基本需求是选择用户输入的树形视图中的特定记录/对象,我们需要高亮显示(选中)该对象,并展开所有节点,以便该对象可见。为了实现这一点,首先,您需要使用 ViewModel 的概念,因此我们必须将 TreeViewItem 的 "IsExpanded" 和 "IsSelected" 属性与我们的数据类绑定。我们可以通过修改控件模板来实现这一点,因为流行的 setter 方法在 WPF 中不起作用。

因此,继承 TreeViewTreeViewItem 类,并重写 GetContainerForItemOverride 方法。

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;
    }
}

步骤 2:创建分层数据类

在这里,您需要创建一个数据类,其中包含您的数据属性以及 "IsExpanded" 和 "IsSelected" 属性。此外,您的类中还需要一个自引用的 ObservableCollection(或其他任何集合)属性。还要实现 INotifyChanged 接口,以便您可以双向绑定。

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"); }
}

步骤 3:在 TreeView 中选择一个与用户在文本框中键入的内容相似的项

一旦用户在文本框中键入,我们将在树形视图中以递归循环的方式搜索键入的文本;在这里,我使用了 "StartsWith" 字符串函数来检查 "cityname" 属性是否与用户的数据匹配。在识别出第一个匹配项后,我们可以停止进一步搜索;我只是在 "selectedTV" 变量中设置了选定的对象。

现在,我们还需要展开所有父节点,以便可以看到选定或搜索到的节点;为此,我们使用 "ExpandAllParents" 函数。在其中,我再次递归地循环树形视图,然后将 "ISExpanded" 属性设置为 true;因此,由于我们之前看到的绑定,它将自动展开树形视图节点。同时,为选定的对象将 IsSelected 属性设置为 true

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;
    }
}

步骤 4:按键(下/上)时,在 Treeview 中移动

为此,我们首先必须处理文本框的 KeyDown 事件,然后我们必须将焦点从文本框转移到树形视图节点。

为此,我需要一个树形视图节点的对象。如果我们通过在文本框中键入来选择一个项,那么它很容易获得,但如果我们直接在树形视图中移动,或者直接通过鼠标将焦点转移到树形视图,那么我们将在 selected item changed 事件中获得数据对象,因此我们需要找到数据对象的容器,即树形视图节点。

现在,为了做到这一点,我已经发现扩展方法是现成的。我在当前的解决方案中只使用了一个,但在其他场景中,其他方法同样有用。

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;
}

步骤 5:选择项目后关闭 Treeview 或按 Esc 键

ExpandAllParents 函数类似,我有一个 "CollapseAll" 函数,我用它来折叠整个树形视图。

此外,我还使用了一个 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);
        }
    }
}

步骤 6:如果焦点转移到任何其他控件,则自动关闭 Treeview

如果用户通过鼠标或 tab 键将焦点直接转移到 UI 中的任何其他控件,则需要关闭树形视图。为此,我使用了一个 FocusManager 并检查即将获得焦点的下一个控件。如果它不是树形视图或我们的文本框,并且弹出控件已打开,则将其隐藏。

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 
}

特点

  • 支持分层数据。
  • 用户可以从树形视图的任何级别选择数据。
  • 在文本框中键入时,树形视图将填充并移动到从同一单词开始的第一个记录。
  • 如果用户键入的数据在树形视图数据中不可用,树形视图将自动关闭。
  • 城市将被选中:在文本框中按 Enter 键,或在树形视图中双击,或在树形视图中按 Enter 键。
  • 按 Esc 键,我们可以从树形视图移动到文本框,按树形视图中的 Enter 键也是如此。
  • 在文本框中按下向下键或向上键时,我们将移动到树形视图。
  • 如果用户移动到树形视图或文本框以外的任何其他控件,树形视图将自动关闭。

结论

我希望这能帮助到有类似需求的朋友们。我试图让这个程序成为我在此开发过程中遇到的各种困难的一站式解决方案。我提交的代码没有很好的注释,并且在某些地方可以改进,所以请原谅我。

任何建议/批评/反馈都非常受欢迎。

© . All rights reserved.