|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Specialized;
using System.ComponentModel;
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;
using MS.Internal.Telemetry.PresentationFramework;
namespace System.Windows.Controls
{
/// <summary>
/// A control that presents items in a tree structure.
/// </summary>
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(TreeViewItem))]
public class TreeView : ItemsControl
{
#region Constructors
static TreeView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeView), new FrameworkPropertyMetadata(typeof(TreeView)));
VirtualizingPanel.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));
VirtualizingStackPanel.ScrollUnitProperty.OverrideMetadata(typeof(TreeView), new FrameworkPropertyMetadata(ScrollUnit.Pixel));
ControlsTraceLogger.AddControl(TelemetryControls.TreeView);
}
/// <summary>
/// Creates an instance of this control.
/// </summary>
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));
/// <summary>
/// The DependencyProperty for the <see cref="SelectedItem"/> property.
/// Default Value: null
/// </summary>
public static readonly DependencyProperty SelectedItemProperty = SelectedItemPropertyKey.DependencyProperty;
/// <summary>
/// Specifies the selected item.
/// </summary>
[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));
/// <summary>
/// The DependencyProperty for the <see cref="SelectedValue"/> property.
/// Default Value: null
/// </summary>
public static readonly DependencyProperty SelectedValueProperty = SelectedValuePropertyKey.DependencyProperty;
/// <summary>
/// Specifies the a value on the selected item as defined by <see cref="SelectedValuePath" />.
/// </summary>
[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);
}
}
/// <summary>
/// The DependencyProperty for the <see cref="SelectedValuePath"/> property.
/// Default Value: String.Empty
/// </summary>
public static readonly DependencyProperty SelectedValuePathProperty =
DependencyProperty.Register(
"SelectedValuePath",
typeof(string),
typeof(TreeView),
new FrameworkPropertyMetadata(
String.Empty,
new PropertyChangedCallback(OnSelectedValuePathChanged)));
/// <summary>
/// Specifies the path to query on <see cref="SelectedItem" /> to calculate <see cref="SelectedValue" />.
/// </summary>
[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
/// <summary>
/// Event fired when <see cref="SelectedItem"/> changes.
/// </summary>
public static readonly RoutedEvent SelectedItemChangedEvent = EventManager.RegisterRoutedEvent("SelectedItemChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<object>), typeof(TreeView));
/// <summary>
/// Event fired when <see cref="SelectedItem"/> changes.
/// </summary>
[Category("Behavior")]
public event RoutedPropertyChangedEventHandler<object> SelectedItemChanged
{
add
{
AddHandler(SelectedItemChangedEvent, value);
}
remove
{
RemoveHandler(SelectedItemChangedEvent, value);
}
}
/// <summary>
/// Called when <see cref="SelectedItem"/> changes.
/// Default implementation fires the <see cref="SelectedItemChanged"/> event.
/// </summary>
/// <param name="e">Event arguments.</param>
protected virtual void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
{
// Needs an Automation Event
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);
UpdateSelectedValue(null);
oldValue = data;
changed = true;
}
}
if (container.IsSelected != selected)
{
container.IsSelected = selected;
}
}
finally
{
IsSelectionChangeActive = false;
}
if (changed)
{
if ( _selectedContainer != null
&& AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementSelected) )
{
TreeViewItemAutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(_selectedContainer) as TreeViewItemAutomationPeer;
if (peer != null)
peer.RaiseAutomationSelectionEvent(AutomationEvents.SelectionItemPatternOnElementSelected);
}
if ( oldContainer != null
&& AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection) )
{
TreeViewItemAutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(oldContainer) as TreeViewItemAutomationPeer;
if (peer != null)
peer.RaiseAutomationSelectionEvent(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection);
}
RoutedPropertyChangedEventArgs<object> e = new RoutedPropertyChangedEventArgs<object>(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 = SystemXmlHelper.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 = null;
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
/// <summary>
/// Returns true if the item is or should be its own container.
/// </summary>
/// <param name="item">The item to test.</param>
/// <returns>true if its type matches the container type.</returns>
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeViewItem;
}
/// <summary>
/// Create or identify the element used to display the given item.
/// </summary>
/// <returns>The container.</returns>
protected override DependencyObject GetContainerForItemOverride()
{
return new TreeViewItem();
}
/// <summary>
/// This method is invoked when the Items property changes.
/// </summary>
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.Format(SR.UnexpectedCollectionChangeAction, e.Action));
}
}
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
/// <summary>
/// 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.
/// </summary>
protected internal override bool HandlesScrolling
{
get { return true; }
}
/// <summary>
/// Called when a keyboard key is pressed down.
/// </summary>
/// <param name="e">Event Arguments</param>
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))
{
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;
case Key.Multiply:
if (ExpandSubtree(_selectedContainer))
{
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()
{
FrameworkElement container;
return NavigateToStartInternal(new ItemNavigateArgs(Keyboard.PrimaryDevice, Keyboard.Modifiers), true /*shouldFocus*/, out container);
}
private bool FocusLastItem()
{
FrameworkElement container;
return NavigateToEndInternal(new ItemNavigateArgs(Keyboard.PrimaryDevice, Keyboard.Modifiers), true /*shouldFocus*/, out container);
}
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;
}
private bool HandleScrollByPage(KeyEventArgs e)
{
IInputElement originalFocusedElement = Keyboard.FocusedElement;
ItemsControl parentItemsControl = ItemsControl.ItemsControlFromItemContainer(_selectedContainer);
ItemInfo startingInfo = (parentItemsControl != null)
? parentItemsControl.ItemInfoFromContainer(_selectedContainer)
: null;
FrameworkElement startingContainer = _selectedContainer.HeaderElement;
if (startingContainer == null)
{
startingContainer = _selectedContainer;
}
return NavigateByPage(startingInfo,
startingContainer,
(e.Key == Key.PageUp ? FocusNavigationDirection.Up : FocusNavigationDirection.Down),
new ItemNavigateArgs(e.Device, Keyboard.Modifiers));
}
/// <summary>
/// Recursively expands all the nodes under the given item.
/// </summary>
/// <returns>true if the subtree was expanded, false otherwise.</returns>
/// <remarks>This can be overriden to modify/disable the numpad-* behavior.</remarks>
protected virtual bool ExpandSubtree(TreeViewItem container)
{
if (container != null)
{
container.ExpandSubtree();
return true;
}
return false;
}
#endregion
#region IsSelectionActive
/// <summary>
/// An event reporting that the IsKeyboardFocusWithin property changed.
/// </summary>
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();
}
}
/// <summary>
/// Polymorphic method which gets called when control gets focus.
/// Passes on the focus to an inner TreeViewItem if necessary.
/// </summary>
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
// Pass on the focus to selecteContainer if TreeView recieves focus
// but its IsKeyboardFocusWithin doesnt change.
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
/// <summary>
/// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
/// </summary>
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
#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;
#endregion
}
}
|