TreeView.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  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;
  5. using System.Collections.Generic;
  6. using System.Collections.Specialized;
  7. using System.Linq;
  8. using Avalonia.Collections;
  9. using Avalonia.Controls.Generators;
  10. using Avalonia.Controls.Primitives;
  11. using Avalonia.Input;
  12. using Avalonia.Input.Platform;
  13. using Avalonia.Interactivity;
  14. using Avalonia.Threading;
  15. using Avalonia.VisualTree;
  16. namespace Avalonia.Controls
  17. {
  18. /// <summary>
  19. /// Displays a hierarchical tree of data.
  20. /// </summary>
  21. public class TreeView : ItemsControl, ICustomKeyboardNavigation
  22. {
  23. /// <summary>
  24. /// Defines the <see cref="AutoScrollToSelectedItem"/> property.
  25. /// </summary>
  26. public static readonly StyledProperty<bool> AutoScrollToSelectedItemProperty =
  27. SelectingItemsControl.AutoScrollToSelectedItemProperty.AddOwner<TreeView>();
  28. /// <summary>
  29. /// Defines the <see cref="SelectedItem"/> property.
  30. /// </summary>
  31. public static readonly DirectProperty<TreeView, object> SelectedItemProperty =
  32. SelectingItemsControl.SelectedItemProperty.AddOwner<TreeView>(
  33. o => o.SelectedItem,
  34. (o, v) => o.SelectedItem = v);
  35. /// <summary>
  36. /// Defines the <see cref="SelectedItems"/> property.
  37. /// </summary>
  38. public static readonly DirectProperty<TreeView, IList> SelectedItemsProperty =
  39. ListBox.SelectedItemsProperty.AddOwner<TreeView>(
  40. o => o.SelectedItems,
  41. (o, v) => o.SelectedItems = v);
  42. /// <summary>
  43. /// Defines the <see cref="SelectionMode"/> property.
  44. /// </summary>
  45. public static readonly StyledProperty<SelectionMode> SelectionModeProperty =
  46. ListBox.SelectionModeProperty.AddOwner<TreeView>();
  47. private static readonly IList Empty = Array.Empty<object>();
  48. private object _selectedItem;
  49. private IList _selectedItems;
  50. /// <summary>
  51. /// Initializes static members of the <see cref="TreeView"/> class.
  52. /// </summary>
  53. static TreeView()
  54. {
  55. // HACK: Needed or SelectedItem property will not be found in Release build.
  56. }
  57. /// <summary>
  58. /// Occurs when the control's selection changes.
  59. /// </summary>
  60. public event EventHandler<SelectionChangedEventArgs> SelectionChanged
  61. {
  62. add => AddHandler(SelectingItemsControl.SelectionChangedEvent, value);
  63. remove => RemoveHandler(SelectingItemsControl.SelectionChangedEvent, value);
  64. }
  65. /// <summary>
  66. /// Gets the <see cref="ITreeItemContainerGenerator"/> for the tree view.
  67. /// </summary>
  68. public new ITreeItemContainerGenerator ItemContainerGenerator =>
  69. (ITreeItemContainerGenerator)base.ItemContainerGenerator;
  70. /// <summary>
  71. /// Gets or sets a value indicating whether to automatically scroll to newly selected items.
  72. /// </summary>
  73. public bool AutoScrollToSelectedItem
  74. {
  75. get => GetValue(AutoScrollToSelectedItemProperty);
  76. set => SetValue(AutoScrollToSelectedItemProperty, value);
  77. }
  78. private bool _syncingSelectedItems;
  79. /// <summary>
  80. /// Gets or sets the selection mode.
  81. /// </summary>
  82. public SelectionMode SelectionMode
  83. {
  84. get => GetValue(SelectionModeProperty);
  85. set => SetValue(SelectionModeProperty, value);
  86. }
  87. /// <summary>
  88. /// Gets or sets the selected item.
  89. /// </summary>
  90. public object SelectedItem
  91. {
  92. get => _selectedItem;
  93. set
  94. {
  95. var selectedItems = SelectedItems;
  96. SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
  97. if (value != null)
  98. {
  99. if (selectedItems.Count != 1 || selectedItems[0] != value)
  100. {
  101. _syncingSelectedItems = true;
  102. SelectSingleItem(value);
  103. _syncingSelectedItems = false;
  104. }
  105. }
  106. else if (SelectedItems.Count > 0)
  107. {
  108. SelectedItems.Clear();
  109. }
  110. }
  111. }
  112. /// <summary>
  113. /// Gets the selected items.
  114. /// </summary>
  115. public IList SelectedItems
  116. {
  117. get
  118. {
  119. if (_selectedItems == null)
  120. {
  121. _selectedItems = new AvaloniaList<object>();
  122. SubscribeToSelectedItems();
  123. }
  124. return _selectedItems;
  125. }
  126. set
  127. {
  128. if (value?.IsFixedSize == true || value?.IsReadOnly == true)
  129. {
  130. throw new NotSupportedException(
  131. "Cannot use a fixed size or read-only collection as SelectedItems.");
  132. }
  133. UnsubscribeFromSelectedItems();
  134. _selectedItems = value ?? new AvaloniaList<object>();
  135. SubscribeToSelectedItems();
  136. }
  137. }
  138. /// <summary>
  139. /// Expands the specified <see cref="TreeViewItem"/> all descendent <see cref="TreeViewItem"/>s.
  140. /// </summary>
  141. /// <param name="item">The item to expand.</param>
  142. public void ExpandSubTree(TreeViewItem item)
  143. {
  144. item.IsExpanded = true;
  145. var panel = item.Presenter.Panel;
  146. if (panel != null)
  147. {
  148. foreach (var child in panel.Children)
  149. {
  150. if (child is TreeViewItem treeViewItem)
  151. {
  152. ExpandSubTree(treeViewItem);
  153. }
  154. }
  155. }
  156. }
  157. /// <summary>
  158. /// Selects all items in the <see cref="TreeView"/>.
  159. /// </summary>
  160. /// <remarks>
  161. /// Note that this method only selects nodes currently visible due to their parent nodes
  162. /// being expanded: it does not expand nodes.
  163. /// </remarks>
  164. public void SelectAll()
  165. {
  166. SynchronizeItems(SelectedItems, ItemContainerGenerator.Index.Items);
  167. }
  168. /// <summary>
  169. /// Deselects all items in the <see cref="TreeView"/>.
  170. /// </summary>
  171. public void UnselectAll()
  172. {
  173. SelectedItems.Clear();
  174. }
  175. /// <summary>
  176. /// Subscribes to the <see cref="SelectedItems"/> CollectionChanged event, if any.
  177. /// </summary>
  178. private void SubscribeToSelectedItems()
  179. {
  180. if (_selectedItems is INotifyCollectionChanged incc)
  181. {
  182. incc.CollectionChanged += SelectedItemsCollectionChanged;
  183. }
  184. SelectedItemsCollectionChanged(
  185. _selectedItems,
  186. new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
  187. }
  188. private void SelectSingleItem(object item)
  189. {
  190. SelectedItems.Clear();
  191. SelectedItems.Add(item);
  192. }
  193. /// <summary>
  194. /// Called when the <see cref="SelectedItems"/> CollectionChanged event is raised.
  195. /// </summary>
  196. /// <param name="sender">The event sender.</param>
  197. /// <param name="e">The event args.</param>
  198. private void SelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  199. {
  200. IList added = null;
  201. IList removed = null;
  202. switch (e.Action)
  203. {
  204. case NotifyCollectionChangedAction.Add:
  205. SelectedItemsAdded(e.NewItems.Cast<object>().ToArray());
  206. if (AutoScrollToSelectedItem)
  207. {
  208. var container = (TreeViewItem)ItemContainerGenerator.Index.ContainerFromItem(e.NewItems[0]);
  209. container?.BringIntoView();
  210. }
  211. added = e.NewItems;
  212. break;
  213. case NotifyCollectionChangedAction.Remove:
  214. if (!_syncingSelectedItems)
  215. {
  216. if (SelectedItems.Count == 0)
  217. {
  218. SelectedItem = null;
  219. }
  220. else
  221. {
  222. var selectedIndex = SelectedItems.IndexOf(_selectedItem);
  223. if (selectedIndex == -1)
  224. {
  225. var old = _selectedItem;
  226. _selectedItem = SelectedItems[0];
  227. RaisePropertyChanged(SelectedItemProperty, old, _selectedItem);
  228. }
  229. }
  230. }
  231. foreach (var item in e.OldItems)
  232. {
  233. MarkItemSelected(item, false);
  234. }
  235. removed = e.OldItems;
  236. break;
  237. case NotifyCollectionChangedAction.Reset:
  238. foreach (IControl container in ItemContainerGenerator.Index.Containers)
  239. {
  240. MarkContainerSelected(container, false);
  241. }
  242. if (SelectedItems.Count > 0)
  243. {
  244. SelectedItemsAdded(SelectedItems);
  245. added = SelectedItems;
  246. }
  247. else if (!_syncingSelectedItems)
  248. {
  249. SelectedItem = null;
  250. }
  251. break;
  252. case NotifyCollectionChangedAction.Replace:
  253. foreach (var item in e.OldItems)
  254. {
  255. MarkItemSelected(item, false);
  256. }
  257. foreach (var item in e.NewItems)
  258. {
  259. MarkItemSelected(item, true);
  260. }
  261. if (SelectedItem != SelectedItems[0] && !_syncingSelectedItems)
  262. {
  263. var oldItem = SelectedItem;
  264. var item = SelectedItems[0];
  265. _selectedItem = item;
  266. RaisePropertyChanged(SelectedItemProperty, oldItem, item);
  267. }
  268. added = e.NewItems;
  269. removed = e.OldItems;
  270. break;
  271. }
  272. if (added?.Count > 0 || removed?.Count > 0)
  273. {
  274. var changed = new SelectionChangedEventArgs(
  275. SelectingItemsControl.SelectionChangedEvent,
  276. added ?? Empty,
  277. removed ?? Empty);
  278. RaiseEvent(changed);
  279. }
  280. }
  281. private void MarkItemSelected(object item, bool selected)
  282. {
  283. var container = ItemContainerGenerator.Index.ContainerFromItem(item);
  284. MarkContainerSelected(container, selected);
  285. }
  286. private void SelectedItemsAdded(IList items)
  287. {
  288. if (items.Count == 0)
  289. {
  290. return;
  291. }
  292. foreach (object item in items)
  293. {
  294. MarkItemSelected(item, true);
  295. }
  296. if (SelectedItem == null && !_syncingSelectedItems)
  297. {
  298. SetAndRaise(SelectedItemProperty, ref _selectedItem, items[0]);
  299. }
  300. }
  301. /// <summary>
  302. /// Unsubscribes from the <see cref="SelectedItems"/> CollectionChanged event, if any.
  303. /// </summary>
  304. private void UnsubscribeFromSelectedItems()
  305. {
  306. if (_selectedItems is INotifyCollectionChanged incc)
  307. {
  308. incc.CollectionChanged -= SelectedItemsCollectionChanged;
  309. }
  310. }
  311. (bool handled, IInputElement next) ICustomKeyboardNavigation.GetNext(IInputElement element,
  312. NavigationDirection direction)
  313. {
  314. if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
  315. {
  316. if (!this.IsVisualAncestorOf(element))
  317. {
  318. IControl result = _selectedItem != null ?
  319. ItemContainerGenerator.Index.ContainerFromItem(_selectedItem) :
  320. ItemContainerGenerator.ContainerFromIndex(0);
  321. return (true, result);
  322. }
  323. return (true, null);
  324. }
  325. return (false, null);
  326. }
  327. /// <inheritdoc/>
  328. protected override IItemContainerGenerator CreateItemContainerGenerator()
  329. {
  330. var result = new TreeItemContainerGenerator<TreeViewItem>(
  331. this,
  332. TreeViewItem.HeaderProperty,
  333. TreeViewItem.ItemTemplateProperty,
  334. TreeViewItem.ItemsProperty,
  335. TreeViewItem.IsExpandedProperty,
  336. new TreeContainerIndex());
  337. result.Index.Materialized += ContainerMaterialized;
  338. return result;
  339. }
  340. /// <inheritdoc/>
  341. protected override void OnGotFocus(GotFocusEventArgs e)
  342. {
  343. if (e.NavigationMethod == NavigationMethod.Directional)
  344. {
  345. e.Handled = UpdateSelectionFromEventSource(
  346. e.Source,
  347. true,
  348. (e.InputModifiers & InputModifiers.Shift) != 0);
  349. }
  350. }
  351. protected override void OnKeyDown(KeyEventArgs e)
  352. {
  353. var direction = e.Key.ToNavigationDirection();
  354. if (direction?.IsDirectional() == true && !e.Handled)
  355. {
  356. if (SelectedItem != null)
  357. {
  358. var next = GetContainerInDirection(
  359. GetContainerFromEventSource(e.Source),
  360. direction.Value,
  361. true);
  362. if (next != null)
  363. {
  364. FocusManager.Instance.Focus(next, NavigationMethod.Directional);
  365. e.Handled = true;
  366. }
  367. }
  368. else
  369. {
  370. SelectedItem = ElementAt(Items, 0);
  371. }
  372. }
  373. if (!e.Handled)
  374. {
  375. var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
  376. bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
  377. if (this.SelectionMode == SelectionMode.Multiple && Match(keymap.SelectAll))
  378. {
  379. SelectAll();
  380. e.Handled = true;
  381. }
  382. }
  383. }
  384. private TreeViewItem GetContainerInDirection(
  385. TreeViewItem from,
  386. NavigationDirection direction,
  387. bool intoChildren)
  388. {
  389. IItemContainerGenerator parentGenerator = GetParentContainerGenerator(from);
  390. if (parentGenerator == null)
  391. {
  392. return null;
  393. }
  394. var index = parentGenerator.IndexFromContainer(from);
  395. var parent = from.Parent as ItemsControl;
  396. TreeViewItem result = null;
  397. switch (direction)
  398. {
  399. case NavigationDirection.Up:
  400. if (index > 0)
  401. {
  402. var previous = (TreeViewItem)parentGenerator.ContainerFromIndex(index - 1);
  403. result = previous.IsExpanded && previous.ItemCount > 0 ?
  404. (TreeViewItem)previous.ItemContainerGenerator.ContainerFromIndex(previous.ItemCount - 1) :
  405. previous;
  406. }
  407. else
  408. {
  409. result = from.Parent as TreeViewItem;
  410. }
  411. break;
  412. case NavigationDirection.Down:
  413. if (from.IsExpanded && intoChildren && from.ItemCount > 0)
  414. {
  415. result = (TreeViewItem)from.ItemContainerGenerator.ContainerFromIndex(0);
  416. }
  417. else if (index < parent?.ItemCount - 1)
  418. {
  419. result = (TreeViewItem)parentGenerator.ContainerFromIndex(index + 1);
  420. }
  421. else if (parent is TreeViewItem parentItem)
  422. {
  423. return GetContainerInDirection(parentItem, direction, false);
  424. }
  425. break;
  426. }
  427. return result;
  428. }
  429. /// <inheritdoc/>
  430. protected override void OnPointerPressed(PointerPressedEventArgs e)
  431. {
  432. base.OnPointerPressed(e);
  433. if (e.MouseButton == MouseButton.Left || e.MouseButton == MouseButton.Right)
  434. {
  435. e.Handled = UpdateSelectionFromEventSource(
  436. e.Source,
  437. true,
  438. (e.InputModifiers & InputModifiers.Shift) != 0,
  439. (e.InputModifiers & InputModifiers.Control) != 0,
  440. e.MouseButton == MouseButton.Right);
  441. }
  442. }
  443. /// <summary>
  444. /// Updates the selection for an item based on user interaction.
  445. /// </summary>
  446. /// <param name="container">The container.</param>
  447. /// <param name="select">Whether the item should be selected or unselected.</param>
  448. /// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
  449. /// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
  450. /// <param name="rightButton">Whether the event is a right-click.</param>
  451. protected void UpdateSelectionFromContainer(
  452. IControl container,
  453. bool select = true,
  454. bool rangeModifier = false,
  455. bool toggleModifier = false,
  456. bool rightButton = false)
  457. {
  458. var item = ItemContainerGenerator.Index.ItemFromContainer(container);
  459. if (item == null)
  460. {
  461. return;
  462. }
  463. IControl selectedContainer = null;
  464. if (SelectedItem != null)
  465. {
  466. selectedContainer = ItemContainerGenerator.Index.ContainerFromItem(SelectedItem);
  467. }
  468. var mode = SelectionMode;
  469. var toggle = toggleModifier || (mode & SelectionMode.Toggle) != 0;
  470. var multi = (mode & SelectionMode.Multiple) != 0;
  471. var range = multi && selectedContainer != null && rangeModifier;
  472. if (rightButton)
  473. {
  474. if (!SelectedItems.Contains(item))
  475. {
  476. SelectSingleItem(item);
  477. }
  478. }
  479. else if (!toggle && !range)
  480. {
  481. SelectSingleItem(item);
  482. }
  483. else if (multi && range)
  484. {
  485. SynchronizeItems(
  486. SelectedItems,
  487. GetItemsInRange(selectedContainer as TreeViewItem, container as TreeViewItem));
  488. }
  489. else
  490. {
  491. var i = SelectedItems.IndexOf(item);
  492. if (i != -1)
  493. {
  494. SelectedItems.Remove(item);
  495. }
  496. else
  497. {
  498. if (multi)
  499. {
  500. SelectedItems.Add(item);
  501. }
  502. else
  503. {
  504. SelectedItem = item;
  505. }
  506. }
  507. }
  508. }
  509. private static IItemContainerGenerator GetParentContainerGenerator(TreeViewItem item)
  510. {
  511. if (item == null)
  512. {
  513. return null;
  514. }
  515. switch (item.Parent)
  516. {
  517. case TreeView treeView:
  518. return treeView.ItemContainerGenerator;
  519. case TreeViewItem treeViewItem:
  520. return treeViewItem.ItemContainerGenerator;
  521. default:
  522. return null;
  523. }
  524. }
  525. /// <summary>
  526. /// Find which node is first in hierarchy.
  527. /// </summary>
  528. /// <param name="treeView">Search root.</param>
  529. /// <param name="nodeA">Nodes to find.</param>
  530. /// <param name="nodeB">Node to find.</param>
  531. /// <returns>Found first node.</returns>
  532. private static TreeViewItem FindFirstNode(TreeView treeView, TreeViewItem nodeA, TreeViewItem nodeB)
  533. {
  534. return FindInContainers(treeView.ItemContainerGenerator, nodeA, nodeB);
  535. }
  536. private static TreeViewItem FindInContainers(ITreeItemContainerGenerator containerGenerator,
  537. TreeViewItem nodeA,
  538. TreeViewItem nodeB)
  539. {
  540. IEnumerable<ItemContainerInfo> containers = containerGenerator.Containers;
  541. foreach (ItemContainerInfo container in containers)
  542. {
  543. TreeViewItem node = FindFirstNode(container.ContainerControl as TreeViewItem, nodeA, nodeB);
  544. if (node != null)
  545. {
  546. return node;
  547. }
  548. }
  549. return null;
  550. }
  551. private static TreeViewItem FindFirstNode(TreeViewItem node, TreeViewItem nodeA, TreeViewItem nodeB)
  552. {
  553. if (node == null)
  554. {
  555. return null;
  556. }
  557. TreeViewItem match = node == nodeA ? nodeA : node == nodeB ? nodeB : null;
  558. if (match != null)
  559. {
  560. return match;
  561. }
  562. return FindInContainers(node.ItemContainerGenerator, nodeA, nodeB);
  563. }
  564. /// <summary>
  565. /// Returns all items that belong to containers between <paramref name="from"/> and <paramref name="to"/>.
  566. /// The range is inclusive.
  567. /// </summary>
  568. /// <param name="from">From container.</param>
  569. /// <param name="to">To container.</param>
  570. private List<object> GetItemsInRange(TreeViewItem from, TreeViewItem to)
  571. {
  572. var items = new List<object>();
  573. if (from == null || to == null)
  574. {
  575. return items;
  576. }
  577. TreeViewItem firstItem = FindFirstNode(this, from, to);
  578. if (firstItem == null)
  579. {
  580. return items;
  581. }
  582. bool wasReversed = false;
  583. if (firstItem == to)
  584. {
  585. var temp = from;
  586. from = to;
  587. to = temp;
  588. wasReversed = true;
  589. }
  590. TreeViewItem node = from;
  591. while (node != to)
  592. {
  593. var item = ItemContainerGenerator.Index.ItemFromContainer(node);
  594. if (item != null)
  595. {
  596. items.Add(item);
  597. }
  598. node = GetContainerInDirection(node, NavigationDirection.Down, true);
  599. }
  600. var toItem = ItemContainerGenerator.Index.ItemFromContainer(to);
  601. if (toItem != null)
  602. {
  603. items.Add(toItem);
  604. }
  605. if (wasReversed)
  606. {
  607. items.Reverse();
  608. }
  609. return items;
  610. }
  611. /// <summary>
  612. /// Updates the selection based on an event that may have originated in a container that
  613. /// belongs to the control.
  614. /// </summary>
  615. /// <param name="eventSource">The control that raised the event.</param>
  616. /// <param name="select">Whether the container should be selected or unselected.</param>
  617. /// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
  618. /// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
  619. /// <param name="rightButton">Whether the event is a right-click.</param>
  620. /// <returns>
  621. /// True if the event originated from a container that belongs to the control; otherwise
  622. /// false.
  623. /// </returns>
  624. protected bool UpdateSelectionFromEventSource(
  625. IInteractive eventSource,
  626. bool select = true,
  627. bool rangeModifier = false,
  628. bool toggleModifier = false,
  629. bool rightButton = false)
  630. {
  631. var container = GetContainerFromEventSource(eventSource);
  632. if (container != null)
  633. {
  634. UpdateSelectionFromContainer(container, select, rangeModifier, toggleModifier, rightButton);
  635. return true;
  636. }
  637. return false;
  638. }
  639. /// <summary>
  640. /// Tries to get the container that was the source of an event.
  641. /// </summary>
  642. /// <param name="eventSource">The control that raised the event.</param>
  643. /// <returns>The container or null if the event did not originate in a container.</returns>
  644. protected TreeViewItem GetContainerFromEventSource(IInteractive eventSource)
  645. {
  646. var item = ((IVisual)eventSource).GetSelfAndVisualAncestors()
  647. .OfType<TreeViewItem>()
  648. .FirstOrDefault();
  649. if (item != null)
  650. {
  651. if (item.ItemContainerGenerator.Index == ItemContainerGenerator.Index)
  652. {
  653. return item;
  654. }
  655. }
  656. return null;
  657. }
  658. /// <summary>
  659. /// Called when a new item container is materialized, to set its selected state.
  660. /// </summary>
  661. /// <param name="sender">The event sender.</param>
  662. /// <param name="e">The event args.</param>
  663. private void ContainerMaterialized(object sender, ItemContainerEventArgs e)
  664. {
  665. var selectedItem = SelectedItem;
  666. if (selectedItem == null)
  667. {
  668. return;
  669. }
  670. foreach (var container in e.Containers)
  671. {
  672. if (container.Item == selectedItem)
  673. {
  674. ((TreeViewItem)container.ContainerControl).IsSelected = true;
  675. if (AutoScrollToSelectedItem)
  676. {
  677. Dispatcher.UIThread.Post(container.ContainerControl.BringIntoView);
  678. }
  679. break;
  680. }
  681. }
  682. }
  683. /// <summary>
  684. /// Sets a container's 'selected' class or <see cref="ISelectable.IsSelected"/>.
  685. /// </summary>
  686. /// <param name="container">The container.</param>
  687. /// <param name="selected">Whether the control is selected</param>
  688. private void MarkContainerSelected(IControl container, bool selected)
  689. {
  690. if (container == null)
  691. {
  692. return;
  693. }
  694. if (container is ISelectable selectable)
  695. {
  696. selectable.IsSelected = selected;
  697. }
  698. else
  699. {
  700. container.Classes.Set(":selected", selected);
  701. }
  702. }
  703. /// <summary>
  704. /// Makes a list of objects equal another (though doesn't preserve order).
  705. /// </summary>
  706. /// <param name="items">The items collection.</param>
  707. /// <param name="desired">The desired items.</param>
  708. private static void SynchronizeItems(IList items, IEnumerable<object> desired)
  709. {
  710. var list = items.Cast<object>().ToList();
  711. var toRemove = list.Except(desired).ToList();
  712. var toAdd = desired.Except(list).ToList();
  713. foreach (var i in toRemove)
  714. {
  715. items.Remove(i);
  716. }
  717. foreach (var i in toAdd)
  718. {
  719. items.Add(i);
  720. }
  721. }
  722. }
  723. }