Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF treeview: how to implement keyboard navigation like in Explorer?

I am using the WPF treeview for the first time and am astonished of all the basic things it does not do. One of those is keyboard navigation, implemented in any self-respecting treeview, e.g. in Windows Explorer or Regedit.

This is how it should work:

If the treeview has the focus and I type (letters/numbers) the selection should move to the first visible (aka expanded) item below the currently selected item that matches the string I typed and bring that into view. If not match is found below the current item the search should continue from the top. If no match is found, the selected item should not change.

As long as I continue typing, the search string grows and the search is refined. If I stop typing for a certain time (2-5 seconds), the search string is emptied.

I am prepared to program this "by hand" from scratch, but since this is so very basic I thought surely someone has already done exactly this.

like image 937
Helge Klein Avatar asked Sep 23 '10 15:09

Helge Klein


2 Answers

Funny, this does not seem to be a popular topic. Anyway, in the meantime I have developed a solution to the problem that satisfies me:

I attach a behavior to the TreeViewItems. In that behavior, I handle KeyUp events. In the KeyUp event handler, I search the visual tree top to bottom as it is displayed. If I find a first matching node (whose name starts with the letter on the key pressed) I select that node.

like image 61
Helge Klein Avatar answered Oct 02 '22 01:10

Helge Klein


I know that is an old topic, but I guess it is still relevant for some people. I made this solution. It is attached to the KeyUp and the TextInput event on a WPF TreeView. I'm using TextInput in addition to KeyUp as I had difficulty translating "national" chars to real chars with KeyEventArgs. That went much more smooth with 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;
    }
like image 30
lars pehrsson Avatar answered Oct 02 '22 00:10

lars pehrsson