代码之家  ›  专栏  ›  技术社区  ›  Helge Klein

wpftreeview:如何像在Explorer中那样实现键盘导航?

  •  7
  • Helge Klein  · 技术社区  · 14 年前

    我第一次使用wpftreeview,对它所做的所有基本事情都感到惊讶

    这就是它的工作原理:

    如果treeview有焦点并且我键入了(字母/数字),那么选择应该移动到当前所选项目下面的第一个可见(aka expanded)项目,该项目与我键入的字符串匹配,并将其带到视图中。如果在当前项下找到不匹配项,则应从顶部继续搜索。如果未找到匹配项,则所选项目不应更改。

    只要我继续键入,搜索字符串就会增长,搜索也会细化。如果我停止键入一段时间(2-5秒),搜索字符串将清空。

    4 回复  |  直到 14 年前
        1
  •  4
  •   Helge Klein    14 年前

    有趣的是,这似乎不是一个流行的话题。无论如何,在此期间,我已经找到了一个让我满意的解决方案:

    我将一个行为附加到TreeViewItems。在这种行为中,我处理KeyUp事件。在KeyUp事件处理程序中,我从上到下搜索显示的可视化树。如果我找到第一个匹配的节点(其名称以按下键上的字母开头),我就选择该节点。

        2
  •  3
  •   lars pehrsson    10 年前

    我知道这是一个老话题,但我想它对某些人来说仍然是相关的。我做了这个解决方案。它附加到wpftreeview上的KeyUp和TextInput事件。除了KeyUp之外,我还使用TextInput,因为我很难用KeyEventArgs将“national”字符翻译成真正的字符。使用TextInput会更加流畅。

    // <TreeView Name="treeView1" KeyUp="treeView1_KeyUp" TextInput="treeView1_TextInput"/>
    
        private bool searchdeep = true;             // Searches in subitems
        private bool searchstartfound = false;      // true when current selected item is found. Ensures that you don't seach backwards and that you only search on the current level (if not searchdeep is true)
        private string searchterm = "";             // what to search for
        private DateTime LastSearch = DateTime.Now; // resets searchterm if last input is older than 1 second.
    
        private void treeView1_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
        {  
            // reset searchterm if any "special" key is pressed
            if (e.Key < Key.A)
                searchterm = "";
    
        }
    
        private void treeView1_TextInput(object sender, TextCompositionEventArgs e)
        {
            if ((DateTime.Now - LastSearch).Seconds > 1)
                searchterm = "";
    
            LastSearch = DateTime.Now;
            searchterm += e.Text;
            searchstartfound = treeView1.SelectedItem == null;
    
            foreach (var t in treeView1.Items)
                if (SearchTreeView((TreeViewItem) t, searchterm.ToLower()))
                    break;
        }
    
       private bool SearchTreeView(TreeViewItem node, string searchterm)
        {
            if (node.IsSelected)
                searchstartfound = true;
    
            // Search current level first
            foreach (TreeViewItem subnode in node.Items)
            {
                // Search subnodes to the current node first
                if (subnode.IsSelected)
                {
                    searchstartfound = true;
                    if (subnode.IsExpanded)
                        foreach (TreeViewItem subsubnode in subnode.Items)
                            if (searchstartfound && subsubnode.Header.ToString().ToLower().StartsWith(searchterm))
                            {
                                subsubnode.IsSelected = true;
                                subsubnode.IsExpanded = true;
                                subsubnode.BringIntoView();
                                return true;
                            }
                }
                // Then search nodes on the same level
                if (searchstartfound && subnode.Header.ToString().ToLower().StartsWith(searchterm))
                {
                    subnode.IsSelected = true;
                    subnode.BringIntoView();
                    return true;
                }
            }
    
            // If not found, search subnodes
            foreach (TreeViewItem subnode in node.Items)
            {
                if (!searchstartfound || searchdeep)
                    if (SearchTreeView(subnode, searchterm))
                    {
                        node.IsExpanded = true;
                        return true;
                    }
            }
    
            return false;
        }
    
        3
  •  1
  •   Miri Pruzan    14 年前

    我也在寻找键盘导航,令人惊讶的是这个解决方案对于模板项目来说是多么的不明显。

    在ListView或TreeView中设置SelectedValuePath会产生此行为。 如果项目已模板化,则设置附加属性:text搜索.text路径对路径上的属性进行搜索也会起到一定的作用。

    希望这对我有帮助,它确实对我有用。

        4
  •  1
  •   DomF    4 年前

    因为这个问题在搜索时最突出,所以我想发布一个答案。 当我使用带有HierarchicalDataTemplate的数据绑定TreeView时,lars的上述文章对我不起作用,因为Items集合返回的是实际的数据绑定项,而不是TreeViewItem。

    下面是我的助手类:

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    
    namespace TreeViewHelpers
    {
        public static class TreeViewItemTextSearcher
        {
            private static bool checkIfMatchesText(TreeViewItem node, string searchterm, StringComparison comparison)
            {
                return node.Header.ToString().StartsWith(searchterm, comparison);
            }
    
            //https://stackoverflow.com/questions/26624982/get-parent-treeviewitem-of-a-selected-node-in-wpf
            public static TreeViewItem getParentItem(TreeViewItem item)
            {
                try
                {
                    var parent = VisualTreeHelper.GetParent(item as DependencyObject);
                    while ((parent as TreeViewItem) == null)
                    {
                        parent = VisualTreeHelper.GetParent(parent);
                    }
                    return parent as TreeViewItem;
                }
                catch (Exception e)
                {
                    //could not find a parent of type TreeViewItem
                    return null;
                }
            }
    
            private static bool tryFindChild(
                int startindex,
                TreeViewItem node,
                string searchterm,
                StringComparison comparison,
                out TreeViewItem foundnode
                )
            {
                foundnode = null;
                if (!node.IsExpanded) { return false; }
    
                for (int i = startindex; i < node.Items.Count; i++)
                {
                    object item = node.Items[i];
                    object tviobj = node.ItemContainerGenerator.ContainerFromItem(item);
                    if (tviobj is null)
                    {
                        return false;
                    }
    
                    TreeViewItem tvi = (TreeViewItem)tviobj;
                    if (checkIfMatchesText(tvi, searchterm, comparison))
                    {
                        foundnode = tvi;
                        return true;
                    }
    
                    //recurse:
                    if (tryFindChild(tvi, searchterm, comparison, out foundnode))
                    {
                        return true;
                    }
                }
    
                return false;
            }
            private static bool tryFindChild(TreeViewItem node, string searchterm, StringComparison comparison, out TreeViewItem foundnode)
            {
                return tryFindChild(0, node, searchterm, comparison, out foundnode);
            }
    
            public static bool SearchTreeView(TreeViewItem node, string searchterm, StringComparison comparison, out TreeViewItem found)
            {
                //search children:
                if (tryFindChild(node, searchterm, comparison, out found))
                {
                    return true;
                }
    
                //search nodes same level as this:
                TreeViewItem parent = getParentItem(node);
                object boundobj = node.DataContext;
                if (!(parent is null || boundobj is null))
                {
                    int startindex = parent.Items.IndexOf(boundobj);
                    if (tryFindChild(startindex + 1, parent, searchterm, comparison, out found))
                    {
                        return true;
                    }
                }
    
                found = null;
                return false;
            }
        }
    }
    

    我还保存最后选定的节点,如中所述 this post

    <TreeView ... TreeViewItem.Selected="TreeViewItemSelected" ... />
    private TreeViewItem lastSelectedTreeViewItem;
    private void TreeViewItemSelected(object sender, RoutedEventArgs e)
    {
        TreeViewItem tvi = e.OriginalSource as TreeViewItem;
        this.lastSelectedTreeViewItem = tvi;
    }
    

    下面是上面的TextInput,修改为使用这个类:

    private void treeView_TextInput(object sender, TextCompositionEventArgs e)
    {
        if ((DateTime.Now - LastSearch).Seconds > 1) { searchterm = ""; }
    
        LastSearch = DateTime.Now;
        searchterm += e.Text;
    
        if (lastSelectedTreeViewItem is null)
        {
            return;
        }
    
        TreeViewItem found;
        if (TreeViewHelpers.TreeViewItemTextSearcher.SearchTreeView(
                node: lastSelectedTreeViewItem,
                searchterm: searchterm,
                comparison: StringComparison.CurrentCultureIgnoreCase, 
                out found
            ))
        {
            found.IsSelected = true;
            found.BringIntoView();
        }
    }
    

    请注意,此解决方案与上面的有点不同,因为我只搜索所选节点的子节点,以及与所选节点处于同一级别的节点。

        5
  •  0
  •   digitguy    12 年前