TreeView.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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.Linq;
  5. using Avalonia.Controls.Generators;
  6. using Avalonia.Controls.Primitives;
  7. using Avalonia.Input;
  8. using Avalonia.Interactivity;
  9. using Avalonia.Styling;
  10. using Avalonia.Threading;
  11. using Avalonia.VisualTree;
  12. namespace Avalonia.Controls
  13. {
  14. /// <summary>
  15. /// Displays a hierachical tree of data.
  16. /// </summary>
  17. public class TreeView : ItemsControl, ICustomKeyboardNavigation
  18. {
  19. /// <summary>
  20. /// Defines the <see cref="AutoScrollToSelectedItem"/> property.
  21. /// </summary>
  22. public static readonly StyledProperty<bool> AutoScrollToSelectedItemProperty =
  23. SelectingItemsControl.AutoScrollToSelectedItemProperty.AddOwner<TreeView>();
  24. /// <summary>
  25. /// Defines the <see cref="SelectedItem"/> property.
  26. /// </summary>
  27. public static readonly DirectProperty<TreeView, object> SelectedItemProperty =
  28. SelectingItemsControl.SelectedItemProperty.AddOwner<TreeView>(
  29. o => o.SelectedItem,
  30. (o, v) => o.SelectedItem = v);
  31. private object _selectedItem;
  32. /// <summary>
  33. /// Initializes static members of the <see cref="TreeView"/> class.
  34. /// </summary>
  35. static TreeView()
  36. {
  37. // HACK: Needed or SelectedItem property will not be found in Release build.
  38. }
  39. /// <summary>
  40. /// Gets the <see cref="ITreeItemContainerGenerator"/> for the tree view.
  41. /// </summary>
  42. public new ITreeItemContainerGenerator ItemContainerGenerator =>
  43. (ITreeItemContainerGenerator)base.ItemContainerGenerator;
  44. /// <summary>
  45. /// Gets or sets a value indicating whether to automatically scroll to newly selected items.
  46. /// </summary>
  47. public bool AutoScrollToSelectedItem
  48. {
  49. get { return GetValue(AutoScrollToSelectedItemProperty); }
  50. set { SetValue(AutoScrollToSelectedItemProperty, value); }
  51. }
  52. /// <summary>
  53. /// Gets or sets the selected item.
  54. /// </summary>
  55. public object SelectedItem
  56. {
  57. get
  58. {
  59. return _selectedItem;
  60. }
  61. set
  62. {
  63. if (_selectedItem != null)
  64. {
  65. var container = ItemContainerGenerator.Index.ContainerFromItem(_selectedItem);
  66. MarkContainerSelected(container, false);
  67. }
  68. SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
  69. if (_selectedItem != null)
  70. {
  71. var container = ItemContainerGenerator.Index.ContainerFromItem(_selectedItem);
  72. MarkContainerSelected(container, true);
  73. if (AutoScrollToSelectedItem && container != null)
  74. {
  75. container.BringIntoView();
  76. }
  77. }
  78. }
  79. }
  80. (bool handled, IInputElement next) ICustomKeyboardNavigation.GetNext(IInputElement element, NavigationDirection direction)
  81. {
  82. if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
  83. {
  84. if (!this.IsVisualAncestorOf(element))
  85. {
  86. IControl result = _selectedItem != null ?
  87. ItemContainerGenerator.Index.ContainerFromItem(_selectedItem) :
  88. ItemContainerGenerator.ContainerFromIndex(0);
  89. return (true, result);
  90. }
  91. else
  92. {
  93. return (true, null);
  94. }
  95. }
  96. return (false, null);
  97. }
  98. /// <inheritdoc/>
  99. protected override IItemContainerGenerator CreateItemContainerGenerator()
  100. {
  101. var result = new TreeItemContainerGenerator<TreeViewItem>(
  102. this,
  103. TreeViewItem.HeaderProperty,
  104. TreeViewItem.ItemTemplateProperty,
  105. TreeViewItem.ItemsProperty,
  106. TreeViewItem.IsExpandedProperty,
  107. new TreeContainerIndex());
  108. result.Index.Materialized += ContainerMaterialized;
  109. return result;
  110. }
  111. /// <inheritdoc/>
  112. protected override void OnGotFocus(GotFocusEventArgs e)
  113. {
  114. if (e.NavigationMethod == NavigationMethod.Directional)
  115. {
  116. e.Handled = UpdateSelectionFromEventSource(
  117. e.Source,
  118. true,
  119. (e.InputModifiers & InputModifiers.Shift) != 0);
  120. }
  121. }
  122. /// <inheritdoc/>
  123. protected override void OnPointerPressed(PointerPressedEventArgs e)
  124. {
  125. base.OnPointerPressed(e);
  126. if (e.MouseButton == MouseButton.Left || e.MouseButton == MouseButton.Right)
  127. {
  128. e.Handled = UpdateSelectionFromEventSource(
  129. e.Source,
  130. true,
  131. (e.InputModifiers & InputModifiers.Shift) != 0,
  132. (e.InputModifiers & InputModifiers.Control) != 0);
  133. }
  134. }
  135. /// <summary>
  136. /// Updates the selection for an item based on user interaction.
  137. /// </summary>
  138. /// <param name="container">The container.</param>
  139. /// <param name="select">Whether the item should be selected or unselected.</param>
  140. /// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
  141. /// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
  142. protected void UpdateSelectionFromContainer(
  143. IControl container,
  144. bool select = true,
  145. bool rangeModifier = false,
  146. bool toggleModifier = false)
  147. {
  148. var item = ItemContainerGenerator.Index.ItemFromContainer(container);
  149. if (item != null)
  150. {
  151. if (SelectedItem != null)
  152. {
  153. var old = ItemContainerGenerator.Index.ContainerFromItem(SelectedItem);
  154. MarkContainerSelected(old, false);
  155. }
  156. SelectedItem = item;
  157. if (SelectedItem != null)
  158. {
  159. MarkContainerSelected(container, true);
  160. }
  161. }
  162. }
  163. /// <summary>
  164. /// Updates the selection based on an event that may have originated in a container that
  165. /// belongs to the control.
  166. /// </summary>
  167. /// <param name="eventSource">The control that raised the event.</param>
  168. /// <param name="select">Whether the container should be selected or unselected.</param>
  169. /// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
  170. /// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
  171. /// <returns>
  172. /// True if the event originated from a container that belongs to the control; otherwise
  173. /// false.
  174. /// </returns>
  175. protected bool UpdateSelectionFromEventSource(
  176. IInteractive eventSource,
  177. bool select = true,
  178. bool rangeModifier = false,
  179. bool toggleModifier = false)
  180. {
  181. var container = GetContainerFromEventSource(eventSource);
  182. if (container != null)
  183. {
  184. UpdateSelectionFromContainer(container, select, rangeModifier, toggleModifier);
  185. return true;
  186. }
  187. return false;
  188. }
  189. /// <summary>
  190. /// Tries to get the container that was the source of an event.
  191. /// </summary>
  192. /// <param name="eventSource">The control that raised the event.</param>
  193. /// <returns>The container or null if the event did not originate in a container.</returns>
  194. protected IControl GetContainerFromEventSource(IInteractive eventSource)
  195. {
  196. var item = ((IVisual)eventSource).GetSelfAndVisualAncestors()
  197. .OfType<TreeViewItem>()
  198. .FirstOrDefault();
  199. if (item != null)
  200. {
  201. if (item.ItemContainerGenerator.Index == this.ItemContainerGenerator.Index)
  202. {
  203. return item;
  204. }
  205. }
  206. return null;
  207. }
  208. /// <summary>
  209. /// Called when a new item container is materialized, to set its selected state.
  210. /// </summary>
  211. /// <param name="sender">The event sender.</param>
  212. /// <param name="e">The event args.</param>
  213. private void ContainerMaterialized(object sender, ItemContainerEventArgs e)
  214. {
  215. var selectedItem = SelectedItem;
  216. if (selectedItem != null)
  217. {
  218. foreach (var container in e.Containers)
  219. {
  220. if (container.Item == selectedItem)
  221. {
  222. ((TreeViewItem)container.ContainerControl).IsSelected = true;
  223. if (AutoScrollToSelectedItem)
  224. {
  225. Dispatcher.UIThread.InvokeAsync(container.ContainerControl.BringIntoView);
  226. }
  227. break;
  228. }
  229. }
  230. }
  231. }
  232. /// <summary>
  233. /// Sets a container's 'selected' class or <see cref="ISelectable.IsSelected"/>.
  234. /// </summary>
  235. /// <param name="container">The container.</param>
  236. /// <param name="selected">Whether the control is selected</param>
  237. private void MarkContainerSelected(IControl container, bool selected)
  238. {
  239. if (container != null)
  240. {
  241. var selectable = container as ISelectable;
  242. if (selectable != null)
  243. {
  244. selectable.IsSelected = selected;
  245. }
  246. else
  247. {
  248. ((IPseudoClasses)container.Classes).Set(":selected", selected);
  249. }
  250. }
  251. }
  252. }
  253. }