Click here to Skip to main content
15,894,539 members
Articles / Desktop Programming / WPF

How to create stock charts using the Silverlight Toolkit

Rate me:
Please Sign up or sign in to vote.
4.70/5 (15 votes)
16 Feb 2009CPOL2 min read 142.3K   2.7K   65  
An article on how to create a Candlestick stock chart using the Silverlight Toolkit.
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using Microsoft.Windows.Controls.Automation.Peers;

namespace Microsoft.Windows.Controls
{
    /// <summary>
    /// Represents a control that displays hierarchical data in a tree structure
    /// that has items that can expand and collapse.
    /// </summary>
    /// <QualityBand>Stable</QualityBand>
    [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(TreeViewItem))]
    public partial class TreeView : ItemsControl
    {
        /// <summary>
        /// A value indicating whether a read-only dependency property change
        /// handler should allow the value to be set.  This is used to ensure
        /// that read-only properties cannot be changed via SetValue, etc.
        /// </summary>
        private bool _allowWrite;

        /// <summary>
        /// A value indicating whether a dependency property change handler
        /// should ignore the next change notification.  This is used to reset
        /// the value of properties without performing any of the actions in
        /// their change handlers.
        /// </summary>
        private bool _ignorePropertyChange;

        #region public object SelectedItem
        /// <summary>
        /// Gets the selected item in a TreeView.
        /// </summary>
        /// <remarks>
        /// The default value is null.
        /// </remarks>
        public object SelectedItem
        {
            get { return GetValue(SelectedItemProperty); }
            private set
            {
                try
                {
                    _allowWrite = true;
                    SetValue(SelectedItemProperty, value);
                }
                finally
                {
                    _allowWrite = false;
                }
            }
        }

        /// <summary>
        /// Identifies the SelectedItem dependency property.
        /// </summary>
        public static readonly DependencyProperty SelectedItemProperty =
            DependencyProperty.Register(
                "SelectedItem",
                typeof(object),
                typeof(TreeView),
                new PropertyMetadata(null, OnSelectedItemPropertyChanged));

        /// <summary>
        /// SelectedItemProperty property changed handler.
        /// </summary>
        /// <param name="d">TreeView that changed its SelectedItem.</param>
        /// <param name="e">Event arguments.</param>
        private static void OnSelectedItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TreeView source = d as TreeView;

            // Ignore the change if requested
            if (source._ignorePropertyChange)
            {
                source._ignorePropertyChange = false;
                return;
            }

            // Ensure the property is only written when expected
            if (!source._allowWrite)
            {
                // Reset the old value before it was incorrectly written
                source._ignorePropertyChange = true;
                source.SetValue(SelectedItemProperty, e.OldValue);

                throw new InvalidOperationException(
                    Properties.Resources.TreeView_OnSelectedItemPropertyChanged_InvalidWrite);
            }

            source.UpdateSelectedValue(e.NewValue);
        }
        #endregion public object SelectedItem

        #region public object SelectedValue
        /// <summary>
        /// Gets the object that is at the specified SelectedValuePath of the
        /// SelectedItem.
        /// </summary>
        /// <remarks>
        /// The default value is null.
        /// </remarks>
        public object SelectedValue
        {
            get { return GetValue(SelectedValueProperty); }
            private set
            {
                try
                {
                    _allowWrite = true;
                    SetValue(SelectedValueProperty, value);
                }
                finally
                {
                    _allowWrite = false;
                }
            }
        }

        /// <summary>
        /// Identifies the SelectedValue dependency property.
        /// </summary>
        public static readonly DependencyProperty SelectedValueProperty =
            DependencyProperty.Register(
                "SelectedValue",
                typeof(object),
                typeof(TreeView),
                new PropertyMetadata(null, OnSelectedValuePropertyChanged));

        /// <summary>
        /// SelectedValueProperty property changed handler.
        /// </summary>
        /// <param name="d">TreeView that changed its SelectedValue.</param>
        /// <param name="e">Event arguments.</param>
        private static void OnSelectedValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TreeView source = d as TreeView;

            // Ignore the change if requested
            if (source._ignorePropertyChange)
            {
                source._ignorePropertyChange = false;
                return;
            }

            // Ensure the property is only written when expected
            if (!source._allowWrite)
            {
                // Reset the old value before it was incorrectly written
                source._ignorePropertyChange = true;
                source.SetValue(SelectedValueProperty, e.OldValue);

                throw new InvalidOperationException(
                    Properties.Resources.TreeView_OnSelectedValuePropertyChanged_InvalidWrite);
            }
        }
        #endregion public object SelectedValue

        #region public string SelectedValuePath
        /// <summary>
        /// Gets or sets the path that is used to get the SelectedValue of the
        /// SelectedItem in a TreeView.
        /// </summary>
        /// <remarks>
        /// The default value is String.Empty.
        /// </remarks>
        public string SelectedValuePath
        {
            get { return GetValue(SelectedValuePathProperty) as string; }
            set { SetValue(SelectedValuePathProperty, value); }
        }

        /// <summary>
        /// Identifies the SelectedValuePath dependency property.
        /// </summary>
        public static readonly DependencyProperty SelectedValuePathProperty =
            DependencyProperty.Register(
                "SelectedValuePath",
                typeof(string),
                typeof(TreeView),
                new PropertyMetadata(string.Empty, OnSelectedValuePathPropertyChanged));

        /// <summary>
        /// SelectedValuePathProperty property changed handler.
        /// </summary>
        /// <param name="d">TreeView that changed its SelectedValuePath.</param>
        /// <param name="e">Event arguments.</param>
        private static void OnSelectedValuePathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TreeView source = d as TreeView;
            source.UpdateSelectedValue(source.SelectedItem);
        }
        #endregion public string SelectedValuePath

        #region public Style ItemContainerStyle
        /// <summary>
        /// Gets or sets the Style that is applied to the container element
        /// generated for each item.
        /// </summary>
        public Style ItemContainerStyle
        {
            get { return GetValue(ItemContainerStyleProperty) as Style; }
            set { SetValue(ItemContainerStyleProperty, value); }
        }

        /// <summary>
        /// Identifies the ItemContainerStyle dependency property.
        /// </summary>
        public static readonly DependencyProperty ItemContainerStyleProperty =
            DependencyProperty.Register(
                "ItemContainerStyle",
                typeof(Style),
                typeof(TreeView),
                new PropertyMetadata(null, OnItemContainerStylePropertyChanged));

        /// <summary>
        /// ItemContainerStyleProperty property changed handler.
        /// </summary>
        /// <param name="d">
        /// TreeView that changed its ItemContainerStyle.
        /// </param>
        /// <param name="e">Event arguments.</param>
        private static void OnItemContainerStylePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TreeView source = d as TreeView;
            Style value = e.NewValue as Style;
            source.ItemContainerGenerator.UpdateItemContainerStyle(value);
        }
        #endregion public Style ItemContainerStyle

        /// <summary>
        /// Gets the currently selected TreeViewItem container.
        /// </summary>
        internal TreeViewItem SelectedContainer { get; private set; }

        /// <summary>
        /// Gets a value indicating whether the currently selected TreeViewItem
        /// container is properly hooked up to the TreeView.
        /// </summary>
        internal bool IsSelectedContainerHookedUp
        {
            get { return SelectedContainer != null && SelectedContainer.ParentTreeView == this; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the selected item is
        /// currently being changed.
        /// </summary>
        internal bool IsSelectionChangeActive { get; set; }

        /// <summary>
        /// Gets the ItemContainerGenerator that is associated with this
        /// control.
        /// </summary>
        public ItemContainerGenerator ItemContainerGenerator { get; private set; }

        /// <summary>
        /// Gets a value indicating whether the Control key is currently
        /// pressed.
        /// </summary>
        internal static bool IsControlKeyDown
        {
            get { return (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control; }
        }

        /// <summary>
        /// Gets a value indicating whether the Shift key is currently pressed.
        /// </summary>
        internal static bool IsShiftKeyDown
        {
            get { return (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift; }
        }

        /// <summary>
        /// Occurs when the SelectedItem changes.
        /// </summary>
        public event RoutedPropertyChangedEventHandler<object> SelectedItemChanged;

        /// <summary>
        /// Initializes a new instance of the TreeView class.
        /// </summary>
        public TreeView()
        {
            DefaultStyleKey = typeof(TreeView);
            ItemContainerGenerator = new ItemContainerGenerator(this);
        }

        /// <summary>
        /// Returns a TreeViewAutomationPeer for use by the Silverlight
        /// automation infrastructure.
        /// </summary>
        /// <returns>
        /// A TreeViewAutomationPeer for the TreeView control.
        /// </returns>
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new TreeViewAutomationPeer(this);
        }

        /// <summary>
        /// Builds the visual tree for the TreeView control when a new control
        /// template is applied.
        /// </summary>
        public override void OnApplyTemplate()
        {
            ItemContainerGenerator.OnApplyTemplate();
            base.OnApplyTemplate();
        }

        #region ItemsControl
        /// <summary>
        /// Creates a TreeViewItem to use to display content.
        /// </summary>
        /// <returns>
        /// A new TreeViewItem to use as a container for content.
        /// </returns>
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new TreeViewItem();
        }

        /// <summary>
        /// Determines whether the specified item is its own container or can be
        /// its own container.
        /// </summary>
        /// <param name="item">The object to evaluate.</param>
        /// <returns>
        /// A value indicating whether the item is a TreeViewItem or not.
        /// </returns>
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is TreeViewItem;
        }

        /// <summary>
        /// Prepares the specified container to display the specified item.
        /// </summary>
        /// <param name="element">
        /// Container element used to display the specified item.
        /// </param>
        /// <param name="item">Specified item to display.</param>
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            TreeViewItem node = element as TreeViewItem;
            if (node != null)
            {
                // Associate the Parent ItemsControl
                node.ParentItemsControl = this;
            }

            ItemContainerGenerator.PrepareContainerForItemOverride(element, item, ItemContainerStyle);
            base.PrepareContainerForItemOverride(element, item);
        }

        /// <summary>
        /// Undoes the effects of PrepareContainerForItemOverride.
        /// </summary>
        /// <param name="element">The container element.</param>
        /// <param name="item">The contained item.</param>
        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            // Remove the association with the Parent ItemsControl
            TreeViewItem node = element as TreeViewItem;
            if (node != null)
            {
                node.ParentItemsControl = null;
            }

            ItemContainerGenerator.ClearContainerForItemOverride(element, item);
            base.ClearContainerForItemOverride(element, item);
        }

        /// <summary>
        /// Provides class handling for an ItemsChanged event that occurs when
        /// there is a change in the Items collection.
        /// </summary>
        /// <param name="e">Event arguments.</param>
        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnItemsChanged(e);

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    Stack<TreeViewItem> addedItems = new Stack<TreeViewItem>(e.NewItems.OfType<TreeViewItem>());
                    if (addedItems.Count > 0)
                    {
                        CheckForSelectedDescendents(addedItems);
                    }
                    break;

                case NotifyCollectionChangedAction.Remove:
                case NotifyCollectionChangedAction.Reset:
                    if (SelectedItem == null || IsSelectedContainerHookedUp)
                    {
                        break;
                    }
                    SelectFirstItem();
                    break;

                case NotifyCollectionChangedAction.Replace:
                    object selectedItem = SelectedItem;
                    if (selectedItem == null || !object.Equals(selectedItem, e.OldItems[0]))
                    {
                        break;
                    }
                    ChangeSelection(selectedItem, SelectedContainer, false);
                    break;
            }
        }

        /// <summary>
        /// Select any descendents when adding TreeViewItems to a TreeView.
        /// </summary>
        /// <param name="items">The added items.</param>
        internal void CheckForSelectedDescendents(Stack<TreeViewItem> items)
        {
            Debug.Assert(items != null, "items should not be null!");
            Debug.Assert(items.Count > 0, "items should not be empty!");

            // Recurse into subtree of each added item to ensure none of
            // its descendents are selected.
            while (items.Count > 0)
            {
                TreeViewItem item = items.Pop();
                if (item.IsSelected)
                {
                    // Make IsSelected false so that its property changed
                    // handler will be fired when it's set to true in
                    // ChangeSelection
                    item.IgnorePropertyChange = true;
                    item.IsSelected = false;

                    ChangeSelection(item, item, true);

                    // If the item is not in the visual tree, we will make sure
                    // every check for ContainsSelection will try and update the
                    // sequence of ContainsSelection flags for the
                    // SelectedContainer.
                    if (SelectedContainer.ParentItemsControl == null)
                    {
                        SelectedContainer.RequiresContainsSelectionUpdate = true;
                    }
                }
                foreach (TreeViewItem nestedItem in item.Items.OfType<TreeViewItem>())
                {
                    items.Push(nestedItem);
                }
            }
        }

        /// <summary>
        /// Get the ItemContainerGenerator for a TreeView or TreeViewItem.
        /// </summary>
        /// <param name="control">TreeView or TreeViewItem.</param>
        /// <returns>
        /// The ItemContainerGenerator for a TreeView or TreeViewItem.
        /// </returns>
        internal static ItemContainerGenerator GetGenerator(ItemsControl control)
        {
            TreeViewItem item = control as TreeViewItem;
            if (item != null)
            {
                return item.ItemContainerGenerator;
            }
            else
            {
                TreeView view = control as TreeView;
                if (view != null)
                {
                    return view.ItemContainerGenerator;
                }
            }
            return null;
        }
        #endregion ItemsControl

        #region Input Events
        /// <summary>
        /// Propagate OnKeyDown messages from the root TreeViewItems to their
        /// TreeView.
        /// </summary>
        /// <param name="e">Event arguments.</param>
        /// <remarks>
        /// Because Silverlight's ScrollViewer swallows many useful key events
        /// (which it can ignore on WPF if you override HandlesScrolling or use
        /// an internal only variable in Silverlight), the root TreeViewItems
        /// explicitly propagate KeyDown events to their parent TreeView.
        /// </remarks>
        internal void PropagateKeyDown(KeyEventArgs e)
        {
            OnKeyDown(e);
        }

        /// <summary>
        /// Provides handling for the KeyDown event.
        /// </summary>
        /// <param name="e">Event arguments.</param>
        [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Complexity metric is inflated by the switch statements")]
        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);

            if (e == null)
            {
                throw new ArgumentNullException("e");
            }
            if (e.Handled)
            {
                return;
            }

            // The Control key modifier is used to scroll the viewer instead of
            // the selection
            if (IsControlKeyDown)
            {
                switch (e.Key)
                {
                    case Key.Home:
                    case Key.End:
                    case Key.PageUp:
                    case Key.PageDown:
                    case Key.Left:
                    case Key.Right:
                    case Key.Up:
                    case Key.Down:
                        if (HandleScrollKeys(e.Key))
                        {
                            e.Handled = true;
                        }
                        break;
                }
            }
            else
            {
                switch (e.Key)
                {
                    case Key.PageUp:
                    case Key.PageDown:
                        if (SelectedContainer != null)
                        {
                            if (HandleScrollByPage(e.Key == Key.PageUp))
                            {
                                e.Handled = true;
                            }
                            break;
                        }
                        if (FocusFirstItem())
                        {
                            e.Handled = true;
                        }
                        break;
                    case Key.Home:
                        if (FocusFirstItem())
                        {
                            e.Handled = true;
                        }
                        break;
                    case Key.End:
                        if (FocusLastItem())
                        {
                            e.Handled = true;
                        }
                        break;
                    case Key.Up:
                    case Key.Down:
                        if (SelectedContainer == null && FocusFirstItem())
                        {
                            e.Handled = true;
                        }
                        break;
                }
            }
        }

        /// <summary>
        /// Handle keys related to scrolling.
        /// </summary>
        /// <param name="key">The key to handle.</param>
        /// <returns>A value indicating whether the key was handled.</returns>
        private bool HandleScrollKeys(Key key)
        {
            ScrollViewer scrollHost = ItemContainerGenerator.ScrollHost;
            if (scrollHost != null)
            {
                switch (key)
                {
                    case Key.PageUp:
                        // Move horizontally if we've run out of room vertically
                        if (!NumericExtensions.IsGreaterThan(scrollHost.ExtentHeight, scrollHost.ViewportHeight))
                        {
                            scrollHost.PageLeft();
                        }
                        else
                        {
                            scrollHost.PageUp();
                        }
                        return true;
                    case Key.PageDown:
                        // Move horizontally if we've run out of room vertically
                        if (!NumericExtensions.IsGreaterThan(scrollHost.ExtentHeight, scrollHost.ViewportHeight))
                        {
                            scrollHost.PageRight();
                        }
                        else
                        {
                            scrollHost.PageDown();
                        }
                        return true;
                    case Key.Home:
                        scrollHost.ScrollToTop();
                        return true;
                    case Key.End:
                        scrollHost.ScrollToBottom();
                        return true;
                    case Key.Left:
                        scrollHost.LineLeft();
                        return true;
                    case Key.Right:
                        scrollHost.LineRight();
                        return true;
                    case Key.Up:
                        scrollHost.LineUp();
                        return true;
                    case Key.Down:
                        scrollHost.LineDown();
                        return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Handle scrolling a page up or down.
        /// </summary>
        /// <param name="up">
        /// A value indicating whether the page should be scrolled up.
        /// </param>
        /// <returns>
        /// A value indicating whether the scroll was handled.
        /// </returns>
        [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Necessary complexity")]
        private bool HandleScrollByPage(bool up)
        {
            // NOTE: This implementation assumes that items are laid out
            // vertically and the Headers of the TreeViewItems appear above
            // their ItemsPresenter.  The same assumptions are made in WPF.

            ScrollViewer scrollHost = ItemContainerGenerator.ScrollHost;
            if (scrollHost != null)
            {
                double viewportHeight = scrollHost.ViewportHeight;
                
                double top;
                double bottom;
                SelectedContainer.GetTopAndBottom(scrollHost, out top, out bottom);

                TreeViewItem selected = null;
                TreeViewItem next = SelectedContainer;
                ItemsControl parent = SelectedContainer.ParentItemsControl;

                if (parent != null)
                {
                    // We need to start at the root TreeViewItem if we're
                    // scrolling up, but can start at the SelectedItem if
                    // scrolling down.
                    if (up)
                    {
                        while (parent != this)
                        {
                            TreeViewItem parentItem = parent as TreeViewItem;
                            if (parentItem == null)
                            {
                                break;
                            }

                            ItemsControl grandparent = parentItem.ParentItemsControl;
                            if (grandparent == null)
                            {
                                break;
                            }

                            next = parentItem;
                            parent = grandparent;
                        }
                    }

                    int index = GetGenerator(parent).IndexFromContainer(next);
                    int count = parent.Items.Count;
                    while (parent != null && next != null)
                    {
                        if (next.IsEnabled)
                        {
                            double delta;
                            if (next.HandleScrollByPage(up, scrollHost, viewportHeight, top, bottom, out delta))
                            {
                                // This item or one of its children was focused
                                return true;
                            }
                            else if (NumericExtensions.IsGreaterThan(delta, viewportHeight))
                            {
                                // If the item doesn't fit on the page but it's
                                // already selected, we'll select the next item
                                // even though it doesn't completely fit into
                                // the current view
                                if (selected == SelectedContainer || selected == null)
                                {
                                    return up ?
                                        SelectedContainer.HandleUpKey() :
                                        SelectedContainer.HandleDownKey();
                                }
                                break;
                            }
                            else
                            {
                                selected = next;
                            }
                        }

                        index += up ? -1 : 1;
                        if (0 <= index && index < count)
                        {
                            next = GetGenerator(parent).ContainerFromIndex(index) as TreeViewItem;
                        }
                        else if (parent == this)
                        {
                            // We just finished with the last item in the
                            // TreeView
                            next = null;
                        }
                        else
                        {
                            // Move up the parent chain to the next item
                            while (parent != null)
                            {
                                TreeViewItem oldParent = parent as TreeViewItem;
                                parent = oldParent.ParentItemsControl;
                                if (parent != null)
                                {
                                    count = parent.Items.Count;
                                    ItemContainerGenerator parentGenerator = GetGenerator(parent);
                                    index = parentGenerator.IndexFromContainer(oldParent) + (up ? -1 : 1);
                                    if (0 <= index && index < count)
                                    {
                                        next = parentGenerator.ContainerFromIndex(index) as TreeViewItem;
                                        break;
                                    }
                                    else if (parent == this)
                                    {
                                        next = null;
                                        parent = null;
                                    }
                                }
                            }
                        }
                    }
                }

                if (selected != null)
                {
                    if (up)
                    {
                        if (selected != SelectedContainer)
                        {
                            return selected.Focus();
                        }
                    }
                    else
                    {
                        selected.FocusInto();
                    }
                }
            }
            
            return false;
        }

        /// <summary>
        /// Provides handling for the MouseLeftButtonDown event.
        /// </summary>
        /// <param name="e">Event arguments.</param>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            if (e == null)
            {
                throw new ArgumentNullException("e");
            }

            if (!e.Handled && HandleMouseButtonDown())
            {
                e.Handled = true;
            }
            
            base.OnMouseLeftButtonDown(e);
        }

        /// <summary>
        /// Provides handling for mouse button events.
        /// </summary>
        /// <returns>A value indicating whether the event was handled.</returns>
        internal bool HandleMouseButtonDown()
        {
            if (SelectedContainer != null)
            {
                if (SelectedContainer != FocusManager.GetFocusedElement())
                {
                    SelectedContainer.Focus();
                }
                return true;
            }

            return false;
        }
        #endregion Input Events

        #region Selection
        /// <summary>
        /// Raises the SelectedItemChanged event when the SelectedItem property
        /// value changes.
        /// </summary>
        /// <param name="e">
        /// Provides the item that was previously selected and the item that is
        /// currently selected for the SelectedItemChanged event.
        /// </param>
        protected virtual void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
        {
            RoutedPropertyChangedEventHandler<object> handler = SelectedItemChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Change whether a TreeViewItem is selected.
        /// </summary>
        /// <param name="itemOrContainer">
        /// Item whose selection is changing.
        /// </param>
        /// <param name="container">
        /// Container of the item whose selection is changing.
        /// </param>
        /// <param name="selected">
        /// A value indicating whether the TreeViewItem is selected.
        /// </param>
        internal void ChangeSelection(object itemOrContainer, TreeViewItem container, bool selected)
        {
            // Ignore any change notifications if we're alread in the middle of
            // changing the selection
            if (IsSelectionChangeActive)
            {
                return;
            }

            object oldValue = null;
            object newValue = null;
            bool raiseSelectionChanged = false;
            TreeViewItem element = SelectedContainer;

            // Start changing the selection
            IsSelectionChangeActive = true;
            try
            {
                if (selected && container != SelectedContainer)
                {
                    // Unselect the old value
                    oldValue = SelectedItem;
                    if (SelectedContainer != null)
                    {
                        SelectedContainer.IsSelected = false;
                        SelectedContainer.UpdateContainsSelection(false);
                    }

                    // Select the new value
                    newValue = itemOrContainer;
                    SelectedContainer = container;
                    SelectedContainer.UpdateContainsSelection(true);
                    SelectedItem = itemOrContainer;
                    UpdateSelectedValue(itemOrContainer);
                    raiseSelectionChanged = true;

                    // Scroll the selected item into view.  We only want to
                    // scroll the header into view, if possible, because an
                    // expanded TreeViewItem contains all of its child items
                    // as well.
                    ItemContainerGenerator.ScrollIntoView(container.HeaderElement ?? container);
                }
                else if (!selected && container == SelectedContainer)
                {
                    // Unselect the old value
                    SelectedContainer.UpdateContainsSelection(false);
                    SelectedContainer = null;
                    SelectedItem = null;
                    SelectedValue = null;
                    oldValue = itemOrContainer;
                    raiseSelectionChanged = true;
                }

                container.IsSelected = selected;
            }
            finally
            {
                // Finish changing the selection
                IsSelectionChangeActive = false;
            }

            // Notify when the selection changes
            if (raiseSelectionChanged)
            {
                if (SelectedContainer != null && AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementSelected))
                {
                    AutomationPeer peer = FrameworkElementAutomationPeer.CreatePeerForElement(SelectedContainer);
                    if (peer != null)
                    {
                        peer.RaiseAutomationEvent(AutomationEvents.SelectionItemPatternOnElementSelected);
                    }
                }
                if (element != null && AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection))
                {
                    AutomationPeer peer = FrameworkElementAutomationPeer.CreatePeerForElement(element);
                    if (peer != null)
                    {
                        peer.RaiseAutomationEvent(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection);
                    }
                }

                OnSelectedItemChanged(new RoutedPropertyChangedEventArgs<object>(oldValue, newValue));
            }
        }

        /// <summary>
        /// Update the selected value of the of the TreeView based on the value
        /// of the currently selected TreeViewItem and the SelectedValuePath.
        /// </summary>
        /// <param name="item">
        /// Value of the currently selected TreeViewItem.
        /// </param>
        private void UpdateSelectedValue(object item)
        {
            if (item != null)
            {
                string path = SelectedValuePath;
                if (string.IsNullOrEmpty(path))
                {
                    SelectedValue = item;
                }
                else
                {
                    // Since we don't have the ability to evaluate a
                    // BindingExpression, we'll just create a new temporary
                    // control to bind the value to which we can then copy out
                    Binding binding = new Binding(path) { Source = item };
                    ContentControl temp = new ContentControl();
                    temp.SetBinding(ContentControl.ContentProperty, binding);
                    SelectedValue = temp.Content;

                    // Remove the Binding once we have the value (this is
                    // especially important if the value is a UIElement because
                    // it should not exist in the visual tree once we've
                    // finished)
                    temp.ClearValue(ContentControl.ContentProperty);
                }
            }
            else
            {
                ClearValue(SelectedValueProperty);
            }
        }

        /// <summary>
        /// Select the first item of the TreeView.
        /// </summary>
        private void SelectFirstItem()
        {
            TreeViewItem container = ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem;
            bool found = container != null;
            if (!found)
            {
                container = SelectedContainer;
            }

            object item = found ?
                ItemContainerGenerator.ItemFromContainer(container) :
                SelectedItem;

            ChangeSelection(item, container, found);
        }
        #endregion Selection

        #region Focus Navigation
        /// <summary>
        /// Focus the first item in the TreeView.
        /// </summary>
        /// <returns>A value indicating whether the item was focused.</returns>
        private bool FocusFirstItem()
        {
            // Get the first item in the TreeView.
            TreeViewItem item = ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem;
            return (item != null) ?
                (item.IsEnabled && item.Focus()) || item.FocusDown() :
                false;
        }

        /// <summary>
        /// Focus the last item in the TreeView.
        /// </summary>
        /// <returns>A value indicating whether the item was focused.</returns>
        private bool FocusLastItem()
        {
            for (int i = Items.Count - 1; i >= 0; i--)
            {
                TreeViewItem item = ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
                if (item != null && item.IsEnabled)
                {
                    return item.FocusInto();
                }
            }
            return false;
        }
        #endregion Focus Navigation
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
South Africa South Africa
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions