PageRenderTime 27ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Avalonia.Controls/TreeView.cs

https://gitlab.com/kush/Avalonia
C# | 411 lines | 294 code | 49 blank | 68 comment | 65 complexity | 223f01be6eb6b0f1dc5f033c8fa69fcb MD5 | raw file
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using Avalonia.Controls.Generators;
  7. using Avalonia.Controls.Primitives;
  8. using Avalonia.Input;
  9. using Avalonia.Interactivity;
  10. using Avalonia.Styling;
  11. using Avalonia.Threading;
  12. using Avalonia.VisualTree;
  13. namespace Avalonia.Controls
  14. {
  15. /// <summary>
  16. /// Displays a hierarchical tree of data.
  17. /// </summary>
  18. public class TreeView : ItemsControl, ICustomKeyboardNavigation
  19. {
  20. /// <summary>
  21. /// Defines the <see cref="AutoScrollToSelectedItem"/> property.
  22. /// </summary>
  23. public static readonly StyledProperty<bool> AutoScrollToSelectedItemProperty =
  24. SelectingItemsControl.AutoScrollToSelectedItemProperty.AddOwner<TreeView>();
  25. /// <summary>
  26. /// Defines the <see cref="SelectedItem"/> property.
  27. /// </summary>
  28. public static readonly DirectProperty<TreeView, object> SelectedItemProperty =
  29. SelectingItemsControl.SelectedItemProperty.AddOwner<TreeView>(
  30. o => o.SelectedItem,
  31. (o, v) => o.SelectedItem = v);
  32. /// <summary>
  33. /// Defines the <see cref="SelectedItemChanged"/> event.
  34. /// </summary>
  35. public static readonly RoutedEvent<SelectionChangedEventArgs> SelectedItemChangedEvent =
  36. RoutedEvent.Register<TreeView, SelectionChangedEventArgs>(
  37. "SelectedItemChanged",
  38. RoutingStrategies.Bubble);
  39. private object _selectedItem;
  40. /// <summary>
  41. /// Initializes static members of the <see cref="TreeView"/> class.
  42. /// </summary>
  43. static TreeView()
  44. {
  45. // HACK: Needed or SelectedItem property will not be found in Release build.
  46. }
  47. /// <summary>
  48. /// Occurs when the control's selection changes.
  49. /// </summary>
  50. public event EventHandler<SelectionChangedEventArgs> SelectedItemChanged
  51. {
  52. add { AddHandler(SelectedItemChangedEvent, value); }
  53. remove { RemoveHandler(SelectedItemChangedEvent, value); }
  54. }
  55. /// <summary>
  56. /// Gets the <see cref="ITreeItemContainerGenerator"/> for the tree view.
  57. /// </summary>
  58. public new ITreeItemContainerGenerator ItemContainerGenerator =>
  59. (ITreeItemContainerGenerator)base.ItemContainerGenerator;
  60. /// <summary>
  61. /// Gets or sets a value indicating whether to automatically scroll to newly selected items.
  62. /// </summary>
  63. public bool AutoScrollToSelectedItem
  64. {
  65. get { return GetValue(AutoScrollToSelectedItemProperty); }
  66. set { SetValue(AutoScrollToSelectedItemProperty, value); }
  67. }
  68. /// <summary>
  69. /// Gets or sets the selected item.
  70. /// </summary>
  71. public object SelectedItem
  72. {
  73. get
  74. {
  75. return _selectedItem;
  76. }
  77. set
  78. {
  79. if (_selectedItem != null)
  80. {
  81. var container = ItemContainerGenerator.Index.ContainerFromItem(_selectedItem);
  82. MarkContainerSelected(container, false);
  83. }
  84. var oldItem = _selectedItem;
  85. SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
  86. if (_selectedItem != null)
  87. {
  88. var container = ItemContainerGenerator.Index.ContainerFromItem(_selectedItem);
  89. MarkContainerSelected(container, true);
  90. if (AutoScrollToSelectedItem && container != null)
  91. {
  92. container.BringIntoView();
  93. }
  94. }
  95. if (oldItem != _selectedItem)
  96. {
  97. // Fire the SelectionChanged event
  98. List<object> removed = new List<object>();
  99. if (oldItem != null)
  100. {
  101. removed.Add(oldItem);
  102. }
  103. List<object> added = new List<object>();
  104. if (_selectedItem != null)
  105. {
  106. added.Add(_selectedItem);
  107. }
  108. var changed = new SelectionChangedEventArgs(
  109. SelectedItemChangedEvent,
  110. added,
  111. removed);
  112. RaiseEvent(changed);
  113. }
  114. }
  115. }
  116. (bool handled, IInputElement next) ICustomKeyboardNavigation.GetNext(IInputElement element, NavigationDirection direction)
  117. {
  118. if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
  119. {
  120. if (!this.IsVisualAncestorOf(element))
  121. {
  122. IControl result = _selectedItem != null ?
  123. ItemContainerGenerator.Index.ContainerFromItem(_selectedItem) :
  124. ItemContainerGenerator.ContainerFromIndex(0);
  125. return (true, result);
  126. }
  127. else
  128. {
  129. return (true, null);
  130. }
  131. }
  132. return (false, null);
  133. }
  134. /// <inheritdoc/>
  135. protected override IItemContainerGenerator CreateItemContainerGenerator()
  136. {
  137. var result = new TreeItemContainerGenerator<TreeViewItem>(
  138. this,
  139. TreeViewItem.HeaderProperty,
  140. TreeViewItem.ItemTemplateProperty,
  141. TreeViewItem.ItemsProperty,
  142. TreeViewItem.IsExpandedProperty,
  143. new TreeContainerIndex());
  144. result.Index.Materialized += ContainerMaterialized;
  145. return result;
  146. }
  147. /// <inheritdoc/>
  148. protected override void OnGotFocus(GotFocusEventArgs e)
  149. {
  150. if (e.NavigationMethod == NavigationMethod.Directional)
  151. {
  152. e.Handled = UpdateSelectionFromEventSource(
  153. e.Source,
  154. true,
  155. (e.InputModifiers & InputModifiers.Shift) != 0);
  156. }
  157. }
  158. protected override void OnKeyDown(KeyEventArgs e)
  159. {
  160. var direction = e.Key.ToNavigationDirection();
  161. if (direction?.IsDirectional() == true && !e.Handled)
  162. {
  163. if (SelectedItem != null)
  164. {
  165. var next = GetContainerInDirection(
  166. GetContainerFromEventSource(e.Source) as TreeViewItem,
  167. direction.Value,
  168. true);
  169. if (next != null)
  170. {
  171. FocusManager.Instance.Focus(next, NavigationMethod.Directional);
  172. e.Handled = true;
  173. }
  174. }
  175. else
  176. {
  177. SelectedItem = ElementAt(Items, 0);
  178. }
  179. }
  180. }
  181. private TreeViewItem GetContainerInDirection(
  182. TreeViewItem from,
  183. NavigationDirection direction,
  184. bool intoChildren)
  185. {
  186. IItemContainerGenerator parentGenerator;
  187. if (from?.Parent is TreeView treeView)
  188. {
  189. parentGenerator = treeView.ItemContainerGenerator;
  190. }
  191. else if (from?.Parent is TreeViewItem item)
  192. {
  193. parentGenerator = item.ItemContainerGenerator;
  194. }
  195. else
  196. {
  197. return null;
  198. }
  199. var index = parentGenerator.IndexFromContainer(from);
  200. var parent = from.Parent as ItemsControl;
  201. TreeViewItem result = null;
  202. switch (direction)
  203. {
  204. case NavigationDirection.Up:
  205. if (index > 0)
  206. {
  207. var previous = (TreeViewItem)parentGenerator.ContainerFromIndex(index - 1);
  208. result = previous.IsExpanded ?
  209. (TreeViewItem)previous.ItemContainerGenerator.ContainerFromIndex(previous.ItemCount - 1) :
  210. previous;
  211. }
  212. else
  213. {
  214. result = from.Parent as TreeViewItem;
  215. }
  216. break;
  217. case NavigationDirection.Down:
  218. if (from.IsExpanded && intoChildren)
  219. {
  220. result = (TreeViewItem)from.ItemContainerGenerator.ContainerFromIndex(0);
  221. }
  222. else if (index < parent?.ItemCount - 1)
  223. {
  224. result = (TreeViewItem)parentGenerator.ContainerFromIndex(index + 1);
  225. }
  226. else if (parent is TreeViewItem parentItem)
  227. {
  228. return GetContainerInDirection(parentItem, direction, false);
  229. }
  230. break;
  231. }
  232. return result;
  233. }
  234. /// <inheritdoc/>
  235. protected override void OnPointerPressed(PointerPressedEventArgs e)
  236. {
  237. base.OnPointerPressed(e);
  238. if (e.MouseButton == MouseButton.Left || e.MouseButton == MouseButton.Right)
  239. {
  240. e.Handled = UpdateSelectionFromEventSource(
  241. e.Source,
  242. true,
  243. (e.InputModifiers & InputModifiers.Shift) != 0,
  244. (e.InputModifiers & InputModifiers.Control) != 0);
  245. }
  246. }
  247. /// <summary>
  248. /// Updates the selection for an item based on user interaction.
  249. /// </summary>
  250. /// <param name="container">The container.</param>
  251. /// <param name="select">Whether the item should be selected or unselected.</param>
  252. /// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
  253. /// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
  254. protected void UpdateSelectionFromContainer(
  255. IControl container,
  256. bool select = true,
  257. bool rangeModifier = false,
  258. bool toggleModifier = false)
  259. {
  260. var item = ItemContainerGenerator.Index.ItemFromContainer(container);
  261. if (item != null)
  262. {
  263. if (SelectedItem != null)
  264. {
  265. var old = ItemContainerGenerator.Index.ContainerFromItem(SelectedItem);
  266. MarkContainerSelected(old, false);
  267. }
  268. SelectedItem = item;
  269. MarkContainerSelected(container, true);
  270. }
  271. }
  272. /// <summary>
  273. /// Updates the selection based on an event that may have originated in a container that
  274. /// belongs to the control.
  275. /// </summary>
  276. /// <param name="eventSource">The control that raised the event.</param>
  277. /// <param name="select">Whether the container should be selected or unselected.</param>
  278. /// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
  279. /// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
  280. /// <returns>
  281. /// True if the event originated from a container that belongs to the control; otherwise
  282. /// false.
  283. /// </returns>
  284. protected bool UpdateSelectionFromEventSource(
  285. IInteractive eventSource,
  286. bool select = true,
  287. bool rangeModifier = false,
  288. bool toggleModifier = false)
  289. {
  290. var container = GetContainerFromEventSource(eventSource);
  291. if (container != null)
  292. {
  293. UpdateSelectionFromContainer(container, select, rangeModifier, toggleModifier);
  294. return true;
  295. }
  296. return false;
  297. }
  298. /// <summary>
  299. /// Tries to get the container that was the source of an event.
  300. /// </summary>
  301. /// <param name="eventSource">The control that raised the event.</param>
  302. /// <returns>The container or null if the event did not originate in a container.</returns>
  303. protected IControl GetContainerFromEventSource(IInteractive eventSource)
  304. {
  305. var item = ((IVisual)eventSource).GetSelfAndVisualAncestors()
  306. .OfType<TreeViewItem>()
  307. .FirstOrDefault();
  308. if (item != null)
  309. {
  310. if (item.ItemContainerGenerator.Index == this.ItemContainerGenerator.Index)
  311. {
  312. return item;
  313. }
  314. }
  315. return null;
  316. }
  317. /// <summary>
  318. /// Called when a new item container is materialized, to set its selected state.
  319. /// </summary>
  320. /// <param name="sender">The event sender.</param>
  321. /// <param name="e">The event args.</param>
  322. private void ContainerMaterialized(object sender, ItemContainerEventArgs e)
  323. {
  324. var selectedItem = SelectedItem;
  325. if (selectedItem != null)
  326. {
  327. foreach (var container in e.Containers)
  328. {
  329. if (container.Item == selectedItem)
  330. {
  331. ((TreeViewItem)container.ContainerControl).IsSelected = true;
  332. if (AutoScrollToSelectedItem)
  333. {
  334. Dispatcher.UIThread.Post(container.ContainerControl.BringIntoView);
  335. }
  336. break;
  337. }
  338. }
  339. }
  340. }
  341. /// <summary>
  342. /// Sets a container's 'selected' class or <see cref="ISelectable.IsSelected"/>.
  343. /// </summary>
  344. /// <param name="container">The container.</param>
  345. /// <param name="selected">Whether the control is selected</param>
  346. private void MarkContainerSelected(IControl container, bool selected)
  347. {
  348. if (container != null)
  349. {
  350. var selectable = container as ISelectable;
  351. if (selectable != null)
  352. {
  353. selectable.IsSelected = selected;
  354. }
  355. else
  356. {
  357. ((IPseudoClasses)container.Classes).Set(":selected", selected);
  358. }
  359. }
  360. }
  361. }
  362. }