Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Framework / System / Windows / Controls / TreeView.cs / 1 / TreeView.cs
//----------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using MS.Internal;
using MS.Internal.Data;
using MS.Internal.KnownBoxes;
namespace System.Windows.Controls
{
///
/// A control that presents items in a tree structure.
///
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(TreeViewItem))]
public class TreeView : ItemsControl
{
#region Constructors
static TreeView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeView), new FrameworkPropertyMetadata(typeof(TreeView)));
VirtualizingStackPanel.IsVirtualizingProperty.OverrideMetadata(typeof(TreeView), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
_dType = DependencyObjectType.FromSystemTypeInternal(typeof(TreeView));
KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(TreeView), new FrameworkPropertyMetadata(KeyboardNavigationMode.Contained));
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(TreeView), new FrameworkPropertyMetadata(KeyboardNavigationMode.None));
}
///
/// Creates an instance of this control.
///
public TreeView()
{
_focusEnterMainFocusScopeEventHandler = new EventHandler(OnFocusEnterMainFocusScope);
KeyboardNavigation.Current.FocusEnterMainFocusScope += _focusEnterMainFocusScopeEventHandler;
}
#endregion
#region Public Properties
private static readonly DependencyPropertyKey SelectedItemPropertyKey =
DependencyProperty.RegisterReadOnly("SelectedItem", typeof(object), typeof(TreeView), new FrameworkPropertyMetadata((object)null));
///
/// The DependencyProperty for the property.
/// Default Value: null
///
public static readonly DependencyProperty SelectedItemProperty = SelectedItemPropertyKey.DependencyProperty;
///
/// Specifies the selected item.
///
[Bindable(true), Category("Appearance"), ReadOnly(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public object SelectedItem
{
get
{
return GetValue(SelectedItemProperty);
}
}
private void SetSelectedItem(object data)
{
if (SelectedItem != data)
{
SetValue(SelectedItemPropertyKey, data);
}
}
private static readonly DependencyPropertyKey SelectedValuePropertyKey =
DependencyProperty.RegisterReadOnly("SelectedValue", typeof(object), typeof(TreeView), new FrameworkPropertyMetadata((object)null));
///
/// The DependencyProperty for the property.
/// Default Value: null
///
public static readonly DependencyProperty SelectedValueProperty = SelectedValuePropertyKey.DependencyProperty;
///
/// Specifies the a value on the selected item as defined by .
///
[Bindable(true), Category("Appearance"), ReadOnly(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public object SelectedValue
{
get
{
return GetValue(SelectedValueProperty);
}
}
private void SetSelectedValue(object data)
{
if (SelectedValue != data)
{
SetValue(SelectedValuePropertyKey, data);
}
}
///
/// The DependencyProperty for the property.
/// Default Value: String.Empty
///
public static readonly DependencyProperty SelectedValuePathProperty =
DependencyProperty.Register(
"SelectedValuePath",
typeof(string),
typeof(TreeView),
new FrameworkPropertyMetadata(
String.Empty,
new PropertyChangedCallback(OnSelectedValuePathChanged)));
///
/// Specifies the path to query on to calculate .
///
[Bindable(true), Category("Appearance")]
public string SelectedValuePath
{
get { return (string) GetValue(SelectedValuePathProperty); }
set { SetValue(SelectedValuePathProperty, value); }
}
private static void OnSelectedValuePathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TreeView tree = (TreeView)d;
SelectedValuePathBindingExpression.ClearValue(tree);
tree.UpdateSelectedValue(tree.SelectedItem);
}
#endregion
#region Public Events
///
/// Event fired when changes.
///
public static readonly RoutedEvent SelectedItemChangedEvent = EventManager.RegisterRoutedEvent("SelectedItemChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler), typeof(TreeView));
///
/// Event fired when changes.
///
[Category("Behavior")]
public event RoutedPropertyChangedEventHandler SelectedItemChanged
{
add
{
AddHandler(SelectedItemChangedEvent, value);
}
remove
{
RemoveHandler(SelectedItemChangedEvent, value);
}
}
///
/// Called when changes.
/// Default implementation fires the event.
///
/// Event arguments.
protected virtual void OnSelectedItemChanged(RoutedPropertyChangedEventArgs e)
{
//
RaiseEvent(e);
}
#endregion
#region Implementation
#region Selection
internal void ChangeSelection(object data, TreeViewItem container, bool selected)
{
if (IsSelectionChangeActive)
{
return;
}
object oldValue = null;
object newValue = null;
bool changed = false;
TreeViewItem oldContainer = _selectedContainer; // Saved for the automation event
IsSelectionChangeActive = true;
try
{
if (selected)
{
if (container != _selectedContainer)
{
oldValue = SelectedItem;
newValue = data;
if (_selectedContainer != null)
{
_selectedContainer.IsSelected = false;
_selectedContainer.UpdateContainsSelection(false);
}
_selectedContainer = container;
_selectedContainer.UpdateContainsSelection(true);
SetSelectedItem(data);
UpdateSelectedValue(data);
changed = true;
}
}
else
{
if (container == _selectedContainer)
{
_selectedContainer.UpdateContainsSelection(false);
_selectedContainer = null;
SetSelectedItem(null);
oldValue = data;
changed = true;
}
}
if (container.IsSelected != selected)
{
container.IsSelected = selected;
}
}
finally
{
IsSelectionChangeActive = false;
}
if (changed)
{
if ( _selectedContainer != null
&& AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementSelected) )
{
AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(_selectedContainer);
if (peer != null)
peer.RaiseAutomationEvent(AutomationEvents.SelectionItemPatternOnElementSelected);
}
if ( oldContainer != null
&& AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection) )
{
AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(oldContainer);
if (peer != null)
peer.RaiseAutomationEvent(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection);
}
RoutedPropertyChangedEventArgs e = new RoutedPropertyChangedEventArgs(oldValue, newValue, SelectedItemChangedEvent);
OnSelectedItemChanged(e);
}
}
internal bool IsSelectionChangeActive
{
get { return _bits[(int)Bits.IsSelectionChangeActive]; }
set { _bits[(int)Bits.IsSelectionChangeActive] = value; }
}
private void UpdateSelectedValue(object selectedItem)
{
BindingExpression expression = PrepareSelectedValuePathBindingExpression(selectedItem);
if (expression != null)
{
expression.Activate(selectedItem);
object selectedValue = expression.Value;
expression.Deactivate();
SetValue(SelectedValuePropertyKey, selectedValue);
}
else
{
ClearValue(SelectedValuePropertyKey);
}
}
private BindingExpression PrepareSelectedValuePathBindingExpression(object item)
{
if (item == null)
{
return null;
}
Binding binding;
bool useXml = XmlHelper.IsXmlNode(item);
BindingExpression bindingExpr = SelectedValuePathBindingExpression.GetValue(this);
// replace existing binding if it's the wrong kind
if (bindingExpr != null)
{
binding = bindingExpr.ParentBinding;
bool usesXml = (binding.XPath != null);
if (usesXml != useXml)
{
bindingExpr = null;
}
}
if (bindingExpr == null)
{
// create the binding
binding = new Binding();
binding.Source = item;
if (useXml)
{
binding.XPath = SelectedValuePath;
binding.Path = new PropertyPath("/InnerText");
}
else
{
binding.Path = new PropertyPath(SelectedValuePath);
}
bindingExpr = (BindingExpression)BindingExpression.CreateUntargetedBindingExpression(this, binding);
SelectedValuePathBindingExpression.SetValue(this, bindingExpr);
}
return bindingExpr;
}
internal void HandleSelectionAndCollapsed(TreeViewItem collapsed)
{
if ((_selectedContainer != null) && (_selectedContainer != collapsed))
{
// Check if current selection is under the collapsed element
TreeViewItem current = _selectedContainer;
do
{
current = current.ParentTreeViewItem;
if (current == collapsed)
{
TreeViewItem oldContainer = _selectedContainer;
ChangeSelection(collapsed.ParentItemsControl.ItemContainerGenerator.ItemFromContainer(collapsed), collapsed, true);
if (oldContainer.IsKeyboardFocusWithin)
{
// If the oldContainer had focus then move focus to the newContainer instead
_selectedContainer.Focus();
}
break;
}
}
while (current != null);
}
}
// This method is called when MouseButonDown on TreeViewItem and also listen for handled events too
// The purpose is to restore focus on TreeView when mouse is clicked and focus was outside the TreeView
// Focus goes either to selected item (if any) or treeview itself
internal void HandleMouseButtonDown()
{
if (!this.IsKeyboardFocusWithin)
{
if (_selectedContainer != null)
{
if (!_selectedContainer.IsKeyboardFocused)
_selectedContainer.Focus();
}
else
{
// If we don't have a selection - just focus the treeview
this.Focus();
}
}
}
#endregion
#region Containers
///
/// Returns true if the item is or should be its own container.
///
/// The item to test.
/// true if its type matches the container type.
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeViewItem;
}
///
/// Create or identify the element used to display the given item.
///
/// The container.
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeViewItem();
}
///
/// This method is invoked when the Items property changes.
///
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Reset:
if ((SelectedItem != null) && !IsSelectedContainerHookedUp)
{
SelectFirstItem();
}
break;
case NotifyCollectionChangedAction.Replace:
{
// If old item is selected - remove the selection
// Revisit the condition when we support duplicate items in Items collection: if e.OldItems[0] is the same as selected items we will unselect the selected item
object selectedItem = SelectedItem;
if ((selectedItem != null) && selectedItem.Equals(e.OldItems[0]))
{
ChangeSelection(selectedItem, _selectedContainer, false);
}
}
break;
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Move:
break;
default:
throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, e.Action));
}
}
///
/// Send down the IsVirtualizing property if it's set on this element.
///
///
///
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
TreeViewItem.IsVirtualizingPropagationHelper(this, element);
}
private void SelectFirstItem()
{
object item;
TreeViewItem container;
bool selected = GetFirstItem(out item, out container);
if (!selected)
{
item = SelectedItem;
container = _selectedContainer;
}
ChangeSelection(item, container, selected);
}
private bool GetFirstItem(out object item, out TreeViewItem container)
{
if (HasItems)
{
item = Items[0];
container = ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem;
return ((item != null) && (container != null));
}
else
{
item = null;
container = null;
return false;
}
}
internal bool IsSelectedContainerHookedUp
{
get
{
return (_selectedContainer != null) && (_selectedContainer.ParentTreeView == this);
}
}
internal TreeViewItem SelectedContainer
{
get
{
return _selectedContainer;
}
}
#endregion
#region Input
///
/// If control has a scrollviewer in its style and has a custom keyboard scrolling behavior when HandlesScrolling should return true.
/// Then ScrollViewer will not handle keyboard input and leave it up to the control.
///
protected internal override bool HandlesScrolling
{
get { return true; }
}
///
/// Called when a keyboard key is pressed down.
///
/// Event Arguments
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (!e.Handled)
{
if (IsControlKeyDown)
{
switch (e.Key)
{
case Key.Up:
case Key.Down:
case Key.Left:
case Key.Right:
case Key.Home:
case Key.End:
case Key.PageUp:
case Key.PageDown:
if (HandleScrollKeys(e.Key))
{
e.Handled = true;
}
break;
}
}
else
{
switch (e.Key)
{
case Key.Up:
case Key.Down:
if ((_selectedContainer == null) && 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.PageUp:
case Key.PageDown:
if (_selectedContainer == null)
{
if (FocusFirstItem())
{
e.Handled = true;
}
}
else if (HandleScrollByPage(e.Key == Key.PageUp))
{
e.Handled = true;
}
break;
case Key.Tab:
if (IsShiftKeyDown && IsKeyboardFocusWithin)
{
// SHIFT-TAB behavior for KeyboardNavigation needs to happen at the TreeView level
if (MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous)))
{
e.Handled = true;
}
}
break;
}
}
}
}
private static bool IsControlKeyDown
{
get
{
return ((Keyboard.Modifiers & ModifierKeys.Control) == (ModifierKeys.Control));
}
}
private static bool IsShiftKeyDown
{
get
{
return ((Keyboard.Modifiers & ModifierKeys.Shift) == (ModifierKeys.Shift));
}
}
private bool FocusFirstItem()
{
if (IsVirtualizing)
{
ScrollToEdge(/*toEnd = */ false);
}
TreeViewItem item = ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem;
if (item != null)
{
if (item.IsEnabled && item.Focus())
{
return true;
}
else
{
return item.FocusDown();
}
}
return false;
}
private bool FocusLastItem()
{
//
// If virtualizing first scroll to the end so that the last item will be generated.
//
if (IsVirtualizing)
{
ScrollToEdge(/* toEnd = */ true);
}
int index = Items.Count - 1;
while (index >= 0)
{
TreeViewItem item = ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
if ((item != null) && item.IsEnabled)
{
return TreeViewItem.FocusIntoItem(item);
}
index--;
}
return false;
}
private bool HandleScrollKeys(Key key)
{
ScrollViewer scroller = ScrollHost;
if (scroller != null)
{
bool invert = (FlowDirection == FlowDirection.RightToLeft);
switch (key)
{
case Key.Up:
scroller.LineUp();
return true;
case Key.Down:
scroller.LineDown();
return true;
case Key.Left:
if (invert)
{
scroller.LineRight();
}
else
{
scroller.LineLeft();
}
return true;
case Key.Right:
if (invert)
{
scroller.LineLeft();
}
else
{
scroller.LineRight();
}
return true;
case Key.Home:
scroller.ScrollToTop();
return true;
case Key.End:
scroller.ScrollToBottom();
return true;
case Key.PageUp:
//if vertically scrollable - go vertical, otherwise horizontal
if(DoubleUtil.GreaterThan(scroller.ExtentHeight, scroller.ViewportHeight))
{
scroller.PageUp();
}
else
{
scroller.PageLeft();
}
return true;
case Key.PageDown:
//if vertically scrollable - go vertical, otherwise horizontal
if(DoubleUtil.GreaterThan(scroller.ExtentHeight, scroller.ViewportHeight))
{
scroller.PageDown();
}
else
{
scroller.PageRight();
}
return true;
}
}
return false;
}
// Note the two assumptions TreeView is making here
// 1.) that everything is laid out vertically and
// 2.) that the headers of TreeViewItems always appear above the ItemsPresenter.
private bool HandleScrollByPage(bool up)
{
ScrollViewer scroller = ScrollHost;
if (scroller != null)
{
double viewportHeight = scroller.ViewportHeight;
double startTop, startBottom;
if (VirtualizingStackPanel.GetIsVirtualizing(this))
{
PreScrollByPage(scroller, up);
}
_selectedContainer.GetTopAndBottom(scroller, out startTop, out startBottom);
TreeViewItem select = null;
TreeViewItem next = _selectedContainer;
ItemsControl parent = _selectedContainer.ParentItemsControl;
if (parent != null)
{
if (up)
{
// When going up, we need to start at the first level of TreeViewItems.
// When going down, we need to start at the selected container.
while (parent != this)
{
ItemsControl nextParent = ItemsControl.ItemsControlFromItemContainer(parent);
if (nextParent == null)
{
break;
}
else
{
next = (TreeViewItem)parent;
parent = nextParent;
}
}
}
int index = parent.ItemContainerGenerator.IndexFromContainer(next);
int count = parent.Items.Count;
while ((parent != null) && (next != null))
{
if (next.IsEnabled)
{
double delta;
if (next.HandleScrollByPage(up, scroller, viewportHeight, startTop, startBottom, out delta))
{
// This item or one of its children was focused
return true;
}
else if (DoubleUtil.GreaterThan(delta, viewportHeight))
{
// This item does not fit
// If select target is already the same element as _selectedContainer - there is no point to select it again
// In this case we select the next item although it cannot completely fit into view
if (select == _selectedContainer || select == null)
return up ? _selectedContainer.HandleUpKey() : _selectedContainer.HandleDownKey();
break;
}
else
{
// This item does fit, but we should continue searching
select = next;
}
}
index = index + (up ? -1 : 1);
if ((0 <= index) && (index < count))
{
next = parent.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
}
else if (parent == this)
{
// That was the last item in the TreeView
next = null;
}
else
{
// Go up the parent chain to a parent with another item
while (parent != null)
{
ItemsControl oldParent = parent;
parent = ItemsControl.ItemsControlFromItemContainer(parent);
if (parent != null)
{
count = parent.Items.Count;
index = parent.ItemContainerGenerator.IndexFromContainer(oldParent) + (up ? -1 : 1);
if ((0 <= index) && (index < count))
{
next = parent.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
break;
}
else if (parent == this)
{
// That was the last item in the TreeView
parent = next = null;
}
}
}
}
}
if (select != null)
{
// Earlier we found an item that fit but didn't focus it at that time
if (up)
{
if (select != _selectedContainer)
{
return select.Focus();
}
}
else
{
return TreeViewItem.FocusIntoItem(select);
}
}
}
}
return false;
}
///
/// Used when virtualization is on. Moves the viewport so that HandleScrollByPage will work.
///
private void PreScrollByPage(ScrollViewer scroller, bool up)
{
double startTop, startBottom; // offset of the top and bottom of the selected element from the top of the viewport
double adjustmentOffset = 0d; // offset we'll have to move the viewport in order to do the 'classic' page up / down algorithm
double distance = 0d; // distance from the furthest edge of either the selected item or the item we're about to select to the nearest edge of the viewport;
double viewportHeight = scroller.ViewportHeight;
_selectedContainer.GetTopAndBottom(scroller, out startTop, out startBottom);
//
// What we're doing here:
// The TreeView page down algorithm walks the tree looking for the item a page up / down from the currently selected item.
// When virtualized that algorithm breaks down since some items aren't generated.
//
// VSP maintains one page of containers above and below the viewport.
// Here we move the viewport so that the selected item or newly selected item, whichever is further,
// is only 1 page away from the top or bottom of the viewport. That allows us to use the existing
// non-virtualization algorithm.
//
if (startBottom < 0)
{
// The selected item is above the viewport; if we page up the furthest distance will be the soon-to-be selected item
distance = startBottom;
if (up)
{
distance -= viewportHeight;
}
}
else if (startTop > 0)
{
// The selected item is below the viewport; if we page down the furthest distance will be the soon-to-be selected item
distance = startTop - viewportHeight;
if (!up)
{
distance += viewportHeight;
}
}
if (Math.Abs(distance) > viewportHeight)
{
// The selected or soon-to-be selected item is more than one page away from the nearest edge of the viewport.
// Create the adjustment offset. Distance is delta between the viewport edge and the furthest item;
// we have to adjust by the viewport height to have the furthest item 1 page from the viewport
adjustmentOffset = distance < 0 ? distance + viewportHeight : distance - viewportHeight;
//
// Set the offset and update layout to ensure that all containers on the path from the selected item to the
// item we want to select are generated.
//
scroller.ScrollToVerticalOffset(scroller.VerticalOffset + adjustmentOffset);
ContextLayoutManager layoutManager = ContextLayoutManager.From(Dispatcher);
layoutManager.UpdateLayout();
}
}
///
/// Scrolls to either the start or end of the viewport. Similar to the code in ItemsControl.NavigateToItem
///
///
///
///
private void ScrollToEdge(bool toEnd)
{
double oldOffset;
double newOffset = 0.0;
ScrollViewer scrollViewer = ScrollHost;
bool isHorizontal = IsLogicalHorizontal;
if (scrollViewer != null)
{
if (toEnd)
{
newOffset = isHorizontal ? scrollViewer.ExtentWidth : scrollViewer.ExtentHeight;
}
//
// This loop ensures that if the extent changes (new items may be generated as we move the viewport)
// that we'll try again until the viewport really reaches the offset.
//
while (MakeVisible((int)newOffset, false, false))
{
oldOffset = isHorizontal ? scrollViewer.HorizontalOffset : scrollViewer.VerticalOffset;
scrollViewer.UpdateLayout();
// If offset does not change - exit the loop
if (DoubleUtil.AreClose(oldOffset, isHorizontal ? scrollViewer.HorizontalOffset : scrollViewer.VerticalOffset))
break;
if (toEnd)
{
newOffset = isHorizontal ? scrollViewer.ExtentWidth : scrollViewer.ExtentHeight;
}
}
}
}
#endregion
#region IsSelectionActive
///
/// An event reporting that the IsKeyboardFocusWithin property changed.
///
protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
{
base.OnIsKeyboardFocusWithinChanged(e);
// When focus within changes we need to update the value of IsSelectionActive.
bool isSelectionActive = false;
bool isKeyboardFocusWithin = IsKeyboardFocusWithin;
if (isKeyboardFocusWithin)
{
// Keyboard focus is within the control, selection should appear active.
isSelectionActive = true;
}
else
{
DependencyObject currentFocus = Keyboard.FocusedElement as DependencyObject;
if (currentFocus != null)
{
UIElement root = KeyboardNavigation.GetVisualRoot(this) as UIElement;
if (root != null && root.IsKeyboardFocusWithin)
{
if (FocusManager.GetFocusScope(currentFocus) != root)
{
isSelectionActive = true;
}
}
}
}
if ((bool)GetValue(Selector.IsSelectionActiveProperty) != isSelectionActive)
{
// The value changed, set the new value.
SetValue(Selector.IsSelectionActivePropertyKey, BooleanBoxes.Box(isSelectionActive));
}
if (isKeyboardFocusWithin && IsKeyboardFocused && (_selectedContainer != null) && !_selectedContainer.IsKeyboardFocusWithin)
{
_selectedContainer.Focus();
}
}
private void OnFocusEnterMainFocusScope(object sender, EventArgs e)
{
// When KeyboardFocus comes back to the main focus scope and the TreeView does not have focus within- clear IsSelectionActivePrivateProperty
if (!IsKeyboardFocusWithin)
{
ClearValue(Selector.IsSelectionActivePropertyKey);
}
}
private static DependencyObject FindParent(DependencyObject o)
{
Visual v = o as Visual;
ContentElement ce = (v == null) ? o as ContentElement : null;
if (ce != null)
{
o = ContentOperations.GetParent(ce);
if (o != null)
{
return o;
}
else
{
FrameworkContentElement fce = ce as FrameworkContentElement;
if (fce != null)
{
return fce.Parent;
}
}
}
else if (v != null)
{
return VisualTreeHelper.GetParent(v);
}
return null;
}
#endregion
#region Automation
///
/// Creates AutomationPeer ( )
///
protected override AutomationPeer OnCreateAutomationPeer()
{
return new TreeViewAutomationPeer(this);
}
#endregion
#region DTypeThemeStyleKey
// Returns the DependencyObjectType for the registered ThemeStyleKey's default
// value. Controls will override this method to return approriate types.
internal override DependencyObjectType DTypeThemeStyleKey
{
get { return _dType; }
}
private static DependencyObjectType _dType;
#endregion DTypeThemeStyleKey
#region Container Size Estimate
//
// Helper methods for TreeViewItem to compute an estimated container size. This is used by VSP when virtualizing.
//
internal Size CurrentContainerSizeEstimate
{
get
{
double estimate = ContainerSizeEstimateField.GetValue(this);
return new Size(estimate, estimate);
}
}
///
/// TreeViewItem calls into this to provide its size in the stacking direction. We use this to compute
/// the most common size.
///
///
internal void RegisterContainerSize(double containerSize)
{
bool found = false;
ContainerSize newSize = new ContainerSize(containerSize);
ContainerSize mostCommon = newSize;
List sizes = EnsureContainerSizeCount();
for (int i = 0; i < sizes.Count; i++)
{
ContainerSize size = sizes[i];
if (size.IsCloseTo(newSize) && size.NumContainers < uint.MaxValue)
{
size.NumContainers++;
found = true;
}
if (size.NumContainers > mostCommon.NumContainers)
{
mostCommon = size;
}
}
// Only track the first 5 sizes we see. Container size could have a long tail and that ought to be sufficient.
if (!found && sizes.Count < 5)
{
sizes.Add(newSize);
}
// Update the estimate
ContainerSizeEstimateField.SetValue(this, mostCommon.Size);
}
private struct ContainerSize
{
public ContainerSize(double size)
{
Size = size;
NumContainers = 1;
}
///
/// We'll say two container sizes are close if they're within a half pixel.
///
///
///
public bool IsCloseTo(ContainerSize size)
{
return Math.Abs(Size - size.Size) < 0.5;
}
public double Size;
public uint NumContainers;
}
private List EnsureContainerSizeCount()
{
List sizes = ContainerSizeCountField.GetValue(this);
if (sizes == null)
{
sizes = new List();
ContainerSizeCountField.SetValue(this, sizes);
}
return sizes;
}
#endregion
#endregion
#region Data
private enum Bits
{
IsSelectionChangeActive = 0x1,
}
// Packed boolean information
private BitVector32 _bits = new BitVector32(0);
private TreeViewItem _selectedContainer;
// Used to retrieve the value of an item, according to the SelectedValuePath
private static readonly BindingExpressionUncommonField SelectedValuePathBindingExpression = new BindingExpressionUncommonField();
private EventHandler _focusEnterMainFocusScopeEventHandler;
// Used to estimate the most common container size
private static UncommonField> ContainerSizeCountField = new UncommonField>();
private static UncommonField ContainerSizeEstimateField = new UncommonField();
#endregion
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using MS.Internal;
using MS.Internal.Data;
using MS.Internal.KnownBoxes;
namespace System.Windows.Controls
{
///
/// A control that presents items in a tree structure.
///
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(TreeViewItem))]
public class TreeView : ItemsControl
{
#region Constructors
static TreeView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeView), new FrameworkPropertyMetadata(typeof(TreeView)));
VirtualizingStackPanel.IsVirtualizingProperty.OverrideMetadata(typeof(TreeView), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
_dType = DependencyObjectType.FromSystemTypeInternal(typeof(TreeView));
KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(TreeView), new FrameworkPropertyMetadata(KeyboardNavigationMode.Contained));
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(TreeView), new FrameworkPropertyMetadata(KeyboardNavigationMode.None));
}
///
/// Creates an instance of this control.
///
public TreeView()
{
_focusEnterMainFocusScopeEventHandler = new EventHandler(OnFocusEnterMainFocusScope);
KeyboardNavigation.Current.FocusEnterMainFocusScope += _focusEnterMainFocusScopeEventHandler;
}
#endregion
#region Public Properties
private static readonly DependencyPropertyKey SelectedItemPropertyKey =
DependencyProperty.RegisterReadOnly("SelectedItem", typeof(object), typeof(TreeView), new FrameworkPropertyMetadata((object)null));
///
/// The DependencyProperty for the property.
/// Default Value: null
///
public static readonly DependencyProperty SelectedItemProperty = SelectedItemPropertyKey.DependencyProperty;
///
/// Specifies the selected item.
///
[Bindable(true), Category("Appearance"), ReadOnly(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public object SelectedItem
{
get
{
return GetValue(SelectedItemProperty);
}
}
private void SetSelectedItem(object data)
{
if (SelectedItem != data)
{
SetValue(SelectedItemPropertyKey, data);
}
}
private static readonly DependencyPropertyKey SelectedValuePropertyKey =
DependencyProperty.RegisterReadOnly("SelectedValue", typeof(object), typeof(TreeView), new FrameworkPropertyMetadata((object)null));
///
/// The DependencyProperty for the property.
/// Default Value: null
///
public static readonly DependencyProperty SelectedValueProperty = SelectedValuePropertyKey.DependencyProperty;
///
/// Specifies the a value on the selected item as defined by .
///
[Bindable(true), Category("Appearance"), ReadOnly(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public object SelectedValue
{
get
{
return GetValue(SelectedValueProperty);
}
}
private void SetSelectedValue(object data)
{
if (SelectedValue != data)
{
SetValue(SelectedValuePropertyKey, data);
}
}
///
/// The DependencyProperty for the property.
/// Default Value: String.Empty
///
public static readonly DependencyProperty SelectedValuePathProperty =
DependencyProperty.Register(
"SelectedValuePath",
typeof(string),
typeof(TreeView),
new FrameworkPropertyMetadata(
String.Empty,
new PropertyChangedCallback(OnSelectedValuePathChanged)));
///
/// Specifies the path to query on to calculate .
///
[Bindable(true), Category("Appearance")]
public string SelectedValuePath
{
get { return (string) GetValue(SelectedValuePathProperty); }
set { SetValue(SelectedValuePathProperty, value); }
}
private static void OnSelectedValuePathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TreeView tree = (TreeView)d;
SelectedValuePathBindingExpression.ClearValue(tree);
tree.UpdateSelectedValue(tree.SelectedItem);
}
#endregion
#region Public Events
///
/// Event fired when changes.
///
public static readonly RoutedEvent SelectedItemChangedEvent = EventManager.RegisterRoutedEvent("SelectedItemChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler), typeof(TreeView));
///
/// Event fired when changes.
///
[Category("Behavior")]
public event RoutedPropertyChangedEventHandler SelectedItemChanged
{
add
{
AddHandler(SelectedItemChangedEvent, value);
}
remove
{
RemoveHandler(SelectedItemChangedEvent, value);
}
}
///
/// Called when changes.
/// Default implementation fires the event.
///
/// Event arguments.
protected virtual void OnSelectedItemChanged(RoutedPropertyChangedEventArgs e)
{
//
RaiseEvent(e);
}
#endregion
#region Implementation
#region Selection
internal void ChangeSelection(object data, TreeViewItem container, bool selected)
{
if (IsSelectionChangeActive)
{
return;
}
object oldValue = null;
object newValue = null;
bool changed = false;
TreeViewItem oldContainer = _selectedContainer; // Saved for the automation event
IsSelectionChangeActive = true;
try
{
if (selected)
{
if (container != _selectedContainer)
{
oldValue = SelectedItem;
newValue = data;
if (_selectedContainer != null)
{
_selectedContainer.IsSelected = false;
_selectedContainer.UpdateContainsSelection(false);
}
_selectedContainer = container;
_selectedContainer.UpdateContainsSelection(true);
SetSelectedItem(data);
UpdateSelectedValue(data);
changed = true;
}
}
else
{
if (container == _selectedContainer)
{
_selectedContainer.UpdateContainsSelection(false);
_selectedContainer = null;
SetSelectedItem(null);
oldValue = data;
changed = true;
}
}
if (container.IsSelected != selected)
{
container.IsSelected = selected;
}
}
finally
{
IsSelectionChangeActive = false;
}
if (changed)
{
if ( _selectedContainer != null
&& AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementSelected) )
{
AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(_selectedContainer);
if (peer != null)
peer.RaiseAutomationEvent(AutomationEvents.SelectionItemPatternOnElementSelected);
}
if ( oldContainer != null
&& AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection) )
{
AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(oldContainer);
if (peer != null)
peer.RaiseAutomationEvent(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection);
}
RoutedPropertyChangedEventArgs e = new RoutedPropertyChangedEventArgs(oldValue, newValue, SelectedItemChangedEvent);
OnSelectedItemChanged(e);
}
}
internal bool IsSelectionChangeActive
{
get { return _bits[(int)Bits.IsSelectionChangeActive]; }
set { _bits[(int)Bits.IsSelectionChangeActive] = value; }
}
private void UpdateSelectedValue(object selectedItem)
{
BindingExpression expression = PrepareSelectedValuePathBindingExpression(selectedItem);
if (expression != null)
{
expression.Activate(selectedItem);
object selectedValue = expression.Value;
expression.Deactivate();
SetValue(SelectedValuePropertyKey, selectedValue);
}
else
{
ClearValue(SelectedValuePropertyKey);
}
}
private BindingExpression PrepareSelectedValuePathBindingExpression(object item)
{
if (item == null)
{
return null;
}
Binding binding;
bool useXml = XmlHelper.IsXmlNode(item);
BindingExpression bindingExpr = SelectedValuePathBindingExpression.GetValue(this);
// replace existing binding if it's the wrong kind
if (bindingExpr != null)
{
binding = bindingExpr.ParentBinding;
bool usesXml = (binding.XPath != null);
if (usesXml != useXml)
{
bindingExpr = null;
}
}
if (bindingExpr == null)
{
// create the binding
binding = new Binding();
binding.Source = item;
if (useXml)
{
binding.XPath = SelectedValuePath;
binding.Path = new PropertyPath("/InnerText");
}
else
{
binding.Path = new PropertyPath(SelectedValuePath);
}
bindingExpr = (BindingExpression)BindingExpression.CreateUntargetedBindingExpression(this, binding);
SelectedValuePathBindingExpression.SetValue(this, bindingExpr);
}
return bindingExpr;
}
internal void HandleSelectionAndCollapsed(TreeViewItem collapsed)
{
if ((_selectedContainer != null) && (_selectedContainer != collapsed))
{
// Check if current selection is under the collapsed element
TreeViewItem current = _selectedContainer;
do
{
current = current.ParentTreeViewItem;
if (current == collapsed)
{
TreeViewItem oldContainer = _selectedContainer;
ChangeSelection(collapsed.ParentItemsControl.ItemContainerGenerator.ItemFromContainer(collapsed), collapsed, true);
if (oldContainer.IsKeyboardFocusWithin)
{
// If the oldContainer had focus then move focus to the newContainer instead
_selectedContainer.Focus();
}
break;
}
}
while (current != null);
}
}
// This method is called when MouseButonDown on TreeViewItem and also listen for handled events too
// The purpose is to restore focus on TreeView when mouse is clicked and focus was outside the TreeView
// Focus goes either to selected item (if any) or treeview itself
internal void HandleMouseButtonDown()
{
if (!this.IsKeyboardFocusWithin)
{
if (_selectedContainer != null)
{
if (!_selectedContainer.IsKeyboardFocused)
_selectedContainer.Focus();
}
else
{
// If we don't have a selection - just focus the treeview
this.Focus();
}
}
}
#endregion
#region Containers
///
/// Returns true if the item is or should be its own container.
///
/// The item to test.
/// true if its type matches the container type.
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeViewItem;
}
///
/// Create or identify the element used to display the given item.
///
/// The container.
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeViewItem();
}
///
/// This method is invoked when the Items property changes.
///
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Reset:
if ((SelectedItem != null) && !IsSelectedContainerHookedUp)
{
SelectFirstItem();
}
break;
case NotifyCollectionChangedAction.Replace:
{
// If old item is selected - remove the selection
// Revisit the condition when we support duplicate items in Items collection: if e.OldItems[0] is the same as selected items we will unselect the selected item
object selectedItem = SelectedItem;
if ((selectedItem != null) && selectedItem.Equals(e.OldItems[0]))
{
ChangeSelection(selectedItem, _selectedContainer, false);
}
}
break;
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Move:
break;
default:
throw new NotSupportedException(SR.Get(SRID.UnexpectedCollectionChangeAction, e.Action));
}
}
///
/// Send down the IsVirtualizing property if it's set on this element.
///
///
///
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
TreeViewItem.IsVirtualizingPropagationHelper(this, element);
}
private void SelectFirstItem()
{
object item;
TreeViewItem container;
bool selected = GetFirstItem(out item, out container);
if (!selected)
{
item = SelectedItem;
container = _selectedContainer;
}
ChangeSelection(item, container, selected);
}
private bool GetFirstItem(out object item, out TreeViewItem container)
{
if (HasItems)
{
item = Items[0];
container = ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem;
return ((item != null) && (container != null));
}
else
{
item = null;
container = null;
return false;
}
}
internal bool IsSelectedContainerHookedUp
{
get
{
return (_selectedContainer != null) && (_selectedContainer.ParentTreeView == this);
}
}
internal TreeViewItem SelectedContainer
{
get
{
return _selectedContainer;
}
}
#endregion
#region Input
///
/// If control has a scrollviewer in its style and has a custom keyboard scrolling behavior when HandlesScrolling should return true.
/// Then ScrollViewer will not handle keyboard input and leave it up to the control.
///
protected internal override bool HandlesScrolling
{
get { return true; }
}
///
/// Called when a keyboard key is pressed down.
///
/// Event Arguments
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (!e.Handled)
{
if (IsControlKeyDown)
{
switch (e.Key)
{
case Key.Up:
case Key.Down:
case Key.Left:
case Key.Right:
case Key.Home:
case Key.End:
case Key.PageUp:
case Key.PageDown:
if (HandleScrollKeys(e.Key))
{
e.Handled = true;
}
break;
}
}
else
{
switch (e.Key)
{
case Key.Up:
case Key.Down:
if ((_selectedContainer == null) && 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.PageUp:
case Key.PageDown:
if (_selectedContainer == null)
{
if (FocusFirstItem())
{
e.Handled = true;
}
}
else if (HandleScrollByPage(e.Key == Key.PageUp))
{
e.Handled = true;
}
break;
case Key.Tab:
if (IsShiftKeyDown && IsKeyboardFocusWithin)
{
// SHIFT-TAB behavior for KeyboardNavigation needs to happen at the TreeView level
if (MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous)))
{
e.Handled = true;
}
}
break;
}
}
}
}
private static bool IsControlKeyDown
{
get
{
return ((Keyboard.Modifiers & ModifierKeys.Control) == (ModifierKeys.Control));
}
}
private static bool IsShiftKeyDown
{
get
{
return ((Keyboard.Modifiers & ModifierKeys.Shift) == (ModifierKeys.Shift));
}
}
private bool FocusFirstItem()
{
if (IsVirtualizing)
{
ScrollToEdge(/*toEnd = */ false);
}
TreeViewItem item = ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem;
if (item != null)
{
if (item.IsEnabled && item.Focus())
{
return true;
}
else
{
return item.FocusDown();
}
}
return false;
}
private bool FocusLastItem()
{
//
// If virtualizing first scroll to the end so that the last item will be generated.
//
if (IsVirtualizing)
{
ScrollToEdge(/* toEnd = */ true);
}
int index = Items.Count - 1;
while (index >= 0)
{
TreeViewItem item = ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
if ((item != null) && item.IsEnabled)
{
return TreeViewItem.FocusIntoItem(item);
}
index--;
}
return false;
}
private bool HandleScrollKeys(Key key)
{
ScrollViewer scroller = ScrollHost;
if (scroller != null)
{
bool invert = (FlowDirection == FlowDirection.RightToLeft);
switch (key)
{
case Key.Up:
scroller.LineUp();
return true;
case Key.Down:
scroller.LineDown();
return true;
case Key.Left:
if (invert)
{
scroller.LineRight();
}
else
{
scroller.LineLeft();
}
return true;
case Key.Right:
if (invert)
{
scroller.LineLeft();
}
else
{
scroller.LineRight();
}
return true;
case Key.Home:
scroller.ScrollToTop();
return true;
case Key.End:
scroller.ScrollToBottom();
return true;
case Key.PageUp:
//if vertically scrollable - go vertical, otherwise horizontal
if(DoubleUtil.GreaterThan(scroller.ExtentHeight, scroller.ViewportHeight))
{
scroller.PageUp();
}
else
{
scroller.PageLeft();
}
return true;
case Key.PageDown:
//if vertically scrollable - go vertical, otherwise horizontal
if(DoubleUtil.GreaterThan(scroller.ExtentHeight, scroller.ViewportHeight))
{
scroller.PageDown();
}
else
{
scroller.PageRight();
}
return true;
}
}
return false;
}
// Note the two assumptions TreeView is making here
// 1.) that everything is laid out vertically and
// 2.) that the headers of TreeViewItems always appear above the ItemsPresenter.
private bool HandleScrollByPage(bool up)
{
ScrollViewer scroller = ScrollHost;
if (scroller != null)
{
double viewportHeight = scroller.ViewportHeight;
double startTop, startBottom;
if (VirtualizingStackPanel.GetIsVirtualizing(this))
{
PreScrollByPage(scroller, up);
}
_selectedContainer.GetTopAndBottom(scroller, out startTop, out startBottom);
TreeViewItem select = null;
TreeViewItem next = _selectedContainer;
ItemsControl parent = _selectedContainer.ParentItemsControl;
if (parent != null)
{
if (up)
{
// When going up, we need to start at the first level of TreeViewItems.
// When going down, we need to start at the selected container.
while (parent != this)
{
ItemsControl nextParent = ItemsControl.ItemsControlFromItemContainer(parent);
if (nextParent == null)
{
break;
}
else
{
next = (TreeViewItem)parent;
parent = nextParent;
}
}
}
int index = parent.ItemContainerGenerator.IndexFromContainer(next);
int count = parent.Items.Count;
while ((parent != null) && (next != null))
{
if (next.IsEnabled)
{
double delta;
if (next.HandleScrollByPage(up, scroller, viewportHeight, startTop, startBottom, out delta))
{
// This item or one of its children was focused
return true;
}
else if (DoubleUtil.GreaterThan(delta, viewportHeight))
{
// This item does not fit
// If select target is already the same element as _selectedContainer - there is no point to select it again
// In this case we select the next item although it cannot completely fit into view
if (select == _selectedContainer || select == null)
return up ? _selectedContainer.HandleUpKey() : _selectedContainer.HandleDownKey();
break;
}
else
{
// This item does fit, but we should continue searching
select = next;
}
}
index = index + (up ? -1 : 1);
if ((0 <= index) && (index < count))
{
next = parent.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
}
else if (parent == this)
{
// That was the last item in the TreeView
next = null;
}
else
{
// Go up the parent chain to a parent with another item
while (parent != null)
{
ItemsControl oldParent = parent;
parent = ItemsControl.ItemsControlFromItemContainer(parent);
if (parent != null)
{
count = parent.Items.Count;
index = parent.ItemContainerGenerator.IndexFromContainer(oldParent) + (up ? -1 : 1);
if ((0 <= index) && (index < count))
{
next = parent.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
break;
}
else if (parent == this)
{
// That was the last item in the TreeView
parent = next = null;
}
}
}
}
}
if (select != null)
{
// Earlier we found an item that fit but didn't focus it at that time
if (up)
{
if (select != _selectedContainer)
{
return select.Focus();
}
}
else
{
return TreeViewItem.FocusIntoItem(select);
}
}
}
}
return false;
}
///
/// Used when virtualization is on. Moves the viewport so that HandleScrollByPage will work.
///
private void PreScrollByPage(ScrollViewer scroller, bool up)
{
double startTop, startBottom; // offset of the top and bottom of the selected element from the top of the viewport
double adjustmentOffset = 0d; // offset we'll have to move the viewport in order to do the 'classic' page up / down algorithm
double distance = 0d; // distance from the furthest edge of either the selected item or the item we're about to select to the nearest edge of the viewport;
double viewportHeight = scroller.ViewportHeight;
_selectedContainer.GetTopAndBottom(scroller, out startTop, out startBottom);
//
// What we're doing here:
// The TreeView page down algorithm walks the tree looking for the item a page up / down from the currently selected item.
// When virtualized that algorithm breaks down since some items aren't generated.
//
// VSP maintains one page of containers above and below the viewport.
// Here we move the viewport so that the selected item or newly selected item, whichever is further,
// is only 1 page away from the top or bottom of the viewport. That allows us to use the existing
// non-virtualization algorithm.
//
if (startBottom < 0)
{
// The selected item is above the viewport; if we page up the furthest distance will be the soon-to-be selected item
distance = startBottom;
if (up)
{
distance -= viewportHeight;
}
}
else if (startTop > 0)
{
// The selected item is below the viewport; if we page down the furthest distance will be the soon-to-be selected item
distance = startTop - viewportHeight;
if (!up)
{
distance += viewportHeight;
}
}
if (Math.Abs(distance) > viewportHeight)
{
// The selected or soon-to-be selected item is more than one page away from the nearest edge of the viewport.
// Create the adjustment offset. Distance is delta between the viewport edge and the furthest item;
// we have to adjust by the viewport height to have the furthest item 1 page from the viewport
adjustmentOffset = distance < 0 ? distance + viewportHeight : distance - viewportHeight;
//
// Set the offset and update layout to ensure that all containers on the path from the selected item to the
// item we want to select are generated.
//
scroller.ScrollToVerticalOffset(scroller.VerticalOffset + adjustmentOffset);
ContextLayoutManager layoutManager = ContextLayoutManager.From(Dispatcher);
layoutManager.UpdateLayout();
}
}
///
/// Scrolls to either the start or end of the viewport. Similar to the code in ItemsControl.NavigateToItem
///
///
///
///
private void ScrollToEdge(bool toEnd)
{
double oldOffset;
double newOffset = 0.0;
ScrollViewer scrollViewer = ScrollHost;
bool isHorizontal = IsLogicalHorizontal;
if (scrollViewer != null)
{
if (toEnd)
{
newOffset = isHorizontal ? scrollViewer.ExtentWidth : scrollViewer.ExtentHeight;
}
//
// This loop ensures that if the extent changes (new items may be generated as we move the viewport)
// that we'll try again until the viewport really reaches the offset.
//
while (MakeVisible((int)newOffset, false, false))
{
oldOffset = isHorizontal ? scrollViewer.HorizontalOffset : scrollViewer.VerticalOffset;
scrollViewer.UpdateLayout();
// If offset does not change - exit the loop
if (DoubleUtil.AreClose(oldOffset, isHorizontal ? scrollViewer.HorizontalOffset : scrollViewer.VerticalOffset))
break;
if (toEnd)
{
newOffset = isHorizontal ? scrollViewer.ExtentWidth : scrollViewer.ExtentHeight;
}
}
}
}
#endregion
#region IsSelectionActive
///
/// An event reporting that the IsKeyboardFocusWithin property changed.
///
protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
{
base.OnIsKeyboardFocusWithinChanged(e);
// When focus within changes we need to update the value of IsSelectionActive.
bool isSelectionActive = false;
bool isKeyboardFocusWithin = IsKeyboardFocusWithin;
if (isKeyboardFocusWithin)
{
// Keyboard focus is within the control, selection should appear active.
isSelectionActive = true;
}
else
{
DependencyObject currentFocus = Keyboard.FocusedElement as DependencyObject;
if (currentFocus != null)
{
UIElement root = KeyboardNavigation.GetVisualRoot(this) as UIElement;
if (root != null && root.IsKeyboardFocusWithin)
{
if (FocusManager.GetFocusScope(currentFocus) != root)
{
isSelectionActive = true;
}
}
}
}
if ((bool)GetValue(Selector.IsSelectionActiveProperty) != isSelectionActive)
{
// The value changed, set the new value.
SetValue(Selector.IsSelectionActivePropertyKey, BooleanBoxes.Box(isSelectionActive));
}
if (isKeyboardFocusWithin && IsKeyboardFocused && (_selectedContainer != null) && !_selectedContainer.IsKeyboardFocusWithin)
{
_selectedContainer.Focus();
}
}
private void OnFocusEnterMainFocusScope(object sender, EventArgs e)
{
// When KeyboardFocus comes back to the main focus scope and the TreeView does not have focus within- clear IsSelectionActivePrivateProperty
if (!IsKeyboardFocusWithin)
{
ClearValue(Selector.IsSelectionActivePropertyKey);
}
}
private static DependencyObject FindParent(DependencyObject o)
{
Visual v = o as Visual;
ContentElement ce = (v == null) ? o as ContentElement : null;
if (ce != null)
{
o = ContentOperations.GetParent(ce);
if (o != null)
{
return o;
}
else
{
FrameworkContentElement fce = ce as FrameworkContentElement;
if (fce != null)
{
return fce.Parent;
}
}
}
else if (v != null)
{
return VisualTreeHelper.GetParent(v);
}
return null;
}
#endregion
#region Automation
///
/// Creates AutomationPeer ( )
///
protected override AutomationPeer OnCreateAutomationPeer()
{
return new TreeViewAutomationPeer(this);
}
#endregion
#region DTypeThemeStyleKey
// Returns the DependencyObjectType for the registered ThemeStyleKey's default
// value. Controls will override this method to return approriate types.
internal override DependencyObjectType DTypeThemeStyleKey
{
get { return _dType; }
}
private static DependencyObjectType _dType;
#endregion DTypeThemeStyleKey
#region Container Size Estimate
//
// Helper methods for TreeViewItem to compute an estimated container size. This is used by VSP when virtualizing.
//
internal Size CurrentContainerSizeEstimate
{
get
{
double estimate = ContainerSizeEstimateField.GetValue(this);
return new Size(estimate, estimate);
}
}
///
/// TreeViewItem calls into this to provide its size in the stacking direction. We use this to compute
/// the most common size.
///
///
internal void RegisterContainerSize(double containerSize)
{
bool found = false;
ContainerSize newSize = new ContainerSize(containerSize);
ContainerSize mostCommon = newSize;
List sizes = EnsureContainerSizeCount();
for (int i = 0; i < sizes.Count; i++)
{
ContainerSize size = sizes[i];
if (size.IsCloseTo(newSize) && size.NumContainers < uint.MaxValue)
{
size.NumContainers++;
found = true;
}
if (size.NumContainers > mostCommon.NumContainers)
{
mostCommon = size;
}
}
// Only track the first 5 sizes we see. Container size could have a long tail and that ought to be sufficient.
if (!found && sizes.Count < 5)
{
sizes.Add(newSize);
}
// Update the estimate
ContainerSizeEstimateField.SetValue(this, mostCommon.Size);
}
private struct ContainerSize
{
public ContainerSize(double size)
{
Size = size;
NumContainers = 1;
}
///
/// We'll say two container sizes are close if they're within a half pixel.
///
///
///
public bool IsCloseTo(ContainerSize size)
{
return Math.Abs(Size - size.Size) < 0.5;
}
public double Size;
public uint NumContainers;
}
private List EnsureContainerSizeCount()
{
List sizes = ContainerSizeCountField.GetValue(this);
if (sizes == null)
{
sizes = new List();
ContainerSizeCountField.SetValue(this, sizes);
}
return sizes;
}
#endregion
#endregion
#region Data
private enum Bits
{
IsSelectionChangeActive = 0x1,
}
// Packed boolean information
private BitVector32 _bits = new BitVector32(0);
private TreeViewItem _selectedContainer;
// Used to retrieve the value of an item, according to the SelectedValuePath
private static readonly BindingExpressionUncommonField SelectedValuePathBindingExpression = new BindingExpressionUncommonField();
private EventHandler _focusEnterMainFocusScopeEventHandler;
// Used to estimate the most common container size
private static UncommonField> ContainerSizeCountField = new UncommonField>();
private static UncommonField ContainerSizeEstimateField = new UncommonField();
#endregion
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.