views:

149

answers:

2

Can I pass events like key strokes to another control in Silverlight?

Imagine I'm in a custom control that contains a Textbox and a Treeview.

I'm listening to a Key event for the TextBox. When the user pushes the Arrow Up or Arrow Down key, I want the Treeview to behave as if it's itself who received that event, i.e. it should move the current selection up or down. The user shouldn't lose focus on the TextBox though so that they can continue typing.

Is this possible? I don't want to set the selection manually on the Treeview because it has no easy MoveSelectionUp() or MoveSelectionDown() method, so I would have to duplicate that functionality which is not so trivial especially when the tree is databound and loads nodes on demand.

+1  A: 

Maybe you could consider using the Mediator Design Pattern to achieve this?

Page Brooks
Thank you for answering. I don't have an architectural problem though, what I want to know is if I can trigger some functionality in a Silverlight control that I can't access because it's hidden in private methods. It will however be executed when an event fires, so my question is if there is any magical way in that fancy DependencyObject framework that would enable me to forward an event from one control to another. Something like `myTextBox.KeyUp += (sender, e) => TreeView.KeyUpEvent.Trigger(myTreeView, e)`.
herzmeister der welten
A: 

In the meantime I solved my special scenario by creating MoveSelectionUp() and MoveSelectionDown() extension methods on the TreeView. I copied the implementation over from some private methods in the Toolkit's control code and made slight changes were private or protected methods were accessed. Thanks to all the extensions methods that are available in the toolkit now it's not so difficult anymore.

Because it is largely not mine, I hereby provide the code below if future visitors stumble upon the same issue.

I'd leave this question open however because I'd still like to know, in a more general fashion, if events can be forwarded in the DependencyObject framework.

TreeViewExtensions:

using System;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

static public class TreeViewExtensions
{
    static public void SetSelectedContainerIfValid(this TreeView self, TreeViewItem itm)
    {
        Contract.Requires(self != null);

        if (itm != null)
        {
            self.SetSelectedContainer(itm);
        }
    }

    static public void MoveSelectionUp(this TreeView self)
    {
        Contract.Requires(self != null);

        var itm = self.GetSelectedContainer();
        if (itm == null)
        {
            self.SetSelectedContainerIfValid(self.GetContainers().LastOrDefault());
        }
        else
        {
            self.SetSelectedContainerIfValid(itm.GetContainerAbove());
        }
    }

    static public void MoveSelectionDown(this TreeView self)
    {
        Contract.Requires(self != null);

        var itm = self.GetSelectedContainer();
        if (itm == null)
        {
            self.SetSelectedContainerIfValid(self.GetContainers().FirstOrDefault());
        }
        else
        {
            self.SetSelectedContainerIfValid(itm.GetContainerBelow());
        }
    }
}

TreeViewItemExtensions:

using System;
using System.Diagnostics.Contracts;
using System.Windows;
using System.Windows.Controls;

static public class TreeViewItemExtensions
{
    static public TreeViewItem GetContainerBelow(this TreeViewItem self)
    {
        return TreeViewItemExtensions.GetContainerBelow(self, true);
    }

    /// <summary>
    /// Find the next focusable TreeViewItem below this item.
    /// </summary>
    /// <param name="recurse">
    /// A value indicating whether the item should recurse into its child
    /// items when searching for the next focusable TreeViewItem.
    /// </param>
    /// <returns>The next focusable TreeViewItem below this item.</returns>
    static public TreeViewItem GetContainerBelow(this TreeViewItem self, bool recurse)
    {
        Contract.Requires(self != null);

        // Look for the next item in the children of this item (if allowed)
        if (recurse && self.IsExpanded && self.HasItems)
        {
            TreeViewItem item = self.ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem;
            if (item != null)
            {
                return item.IsEnabled ?
                    item :
                    item.GetContainerBelow(false);
            }
        }

        // Look for the next item in the siblings of this item
        ItemsControl parent = self.GetParentTreeViewItem() as ItemsControl ?? self.GetParentTreeView();
        if (parent != null)
        {
            // Get the index of this item relative to its siblings
            TreeViewItem item = null;
            int index = parent.ItemContainerGenerator.IndexFromContainer(self);
            int count = parent.Items.Count;

            // Check for any siblings below this item
            while (index++ < count)
            {
                item = parent.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
                if (item != null && item.IsEnabled)
                {
                    return item;
                }
            }

            // If nothing else was found, try to find the next sibling below
            // the parent of this item
            TreeViewItem parentItem = self.GetParentTreeViewItem();
            if (parentItem != null)
            {
                return parentItem.GetContainerBelow(false);
            }
        }

        return null;
    }

    /// <summary>
    /// Find the last focusable TreeViewItem contained by this item.
    /// </summary>
    /// <returns>
    /// The last focusable TreeViewItem contained by this item.
    /// </returns>
    static public TreeViewItem GetLastContainer(this TreeViewItem self)
    {
        Contract.Requires(self != null);

        TreeViewItem item = self;
        TreeViewItem lastItem = null;
        int index = -1;

        // Walk the children of the current item
        while (item != null)
        {
            // Ignore any disabled items
            if (item.IsEnabled)
            {
                // If the item has no children, it must be the last
                if (!item.IsExpanded || !item.HasItems)
                {
                    return item;
                }

                // If the item has children, mark it as the last known
                // focusable item so far and walk into its child items,
                // starting from the last item and moving toward the first
                lastItem = item;
                index = item.Items.Count - 1;
            }
            else if (index > 0)
            {
                // Try searching for the previous item's sibling
                index--;
            }
            else
            {
                // Stop searching if we've run out of children
                break;
            }

            // Move to the item's previous sibling
            item = lastItem.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
        }

        return lastItem;
    }

    /// <summary>
    /// Find the previous focusable TreeViewItem above this item.
    /// </summary>
    /// <returns>
    /// The previous focusable TreeViewItem above this item.
    /// </returns>
    static public TreeViewItem GetContainerAbove(this TreeViewItem self)
    {
        Contract.Requires(self != null);

        ItemsControl parent = self.GetParentTreeViewItem() as ItemsControl ?? self.GetParentTreeView();
        if (parent == null)
        {
            return null;
        }

        // Get the index of the current item relative to its siblings
        int index = parent.ItemContainerGenerator.IndexFromContainer(self);

        // Walk the previous siblings of the item to find a focusable item
        while (index-- > 0)
        {
            // Get the sibling
            TreeViewItem item = parent.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
            if (item != null && item.IsEnabled)
            {
                // Get the last focusable descendent of the sibling
                TreeViewItem last = item.GetLastContainer();
                if (last != null)
                {
                    return last;
                }
            }
        }

        return parent as TreeViewItem;
    }
}
herzmeister der welten