SelectingItemsControl.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Collections.Specialized;
  5. using System.ComponentModel;
  6. using System.Diagnostics;
  7. using System.Linq;
  8. using Avalonia.Controls.Generators;
  9. using Avalonia.Controls.Utils;
  10. using Avalonia.Data;
  11. using Avalonia.Input;
  12. using Avalonia.Input.Platform;
  13. using Avalonia.Interactivity;
  14. using Avalonia.VisualTree;
  15. namespace Avalonia.Controls.Primitives
  16. {
  17. /// <summary>
  18. /// An <see cref="ItemsControl"/> that maintains a selection.
  19. /// </summary>
  20. /// <remarks>
  21. /// <para>
  22. /// <see cref="SelectingItemsControl"/> provides a base class for <see cref="ItemsControl"/>s
  23. /// that maintain a selection (single or multiple). By default only its
  24. /// <see cref="SelectedIndex"/> and <see cref="SelectedItem"/> properties are visible; the
  25. /// current multiple <see cref="Selection"/> and <see cref="SelectedItems"/> together with the
  26. /// <see cref="SelectionMode"/> and properties are protected, however a derived class can
  27. /// expose these if it wishes to support multiple selection.
  28. /// </para>
  29. /// <para>
  30. /// <see cref="SelectingItemsControl"/> maintains a selection respecting the current
  31. /// <see cref="SelectionMode"/> but it does not react to user input; this must be handled in a
  32. /// derived class. It does, however, respond to <see cref="IsSelectedChangedEvent"/> events
  33. /// from items and updates the selection accordingly.
  34. /// </para>
  35. /// </remarks>
  36. public class SelectingItemsControl : ItemsControl
  37. {
  38. /// <summary>
  39. /// Defines the <see cref="AutoScrollToSelectedItem"/> property.
  40. /// </summary>
  41. public static readonly StyledProperty<bool> AutoScrollToSelectedItemProperty =
  42. AvaloniaProperty.Register<SelectingItemsControl, bool>(
  43. nameof(AutoScrollToSelectedItem),
  44. defaultValue: true);
  45. /// <summary>
  46. /// Defines the <see cref="SelectedIndex"/> property.
  47. /// </summary>
  48. public static readonly DirectProperty<SelectingItemsControl, int> SelectedIndexProperty =
  49. AvaloniaProperty.RegisterDirect<SelectingItemsControl, int>(
  50. nameof(SelectedIndex),
  51. o => o.SelectedIndex,
  52. (o, v) => o.SelectedIndex = v,
  53. unsetValue: -1,
  54. defaultBindingMode: BindingMode.TwoWay);
  55. /// <summary>
  56. /// Defines the <see cref="SelectedItem"/> property.
  57. /// </summary>
  58. public static readonly DirectProperty<SelectingItemsControl, object> SelectedItemProperty =
  59. AvaloniaProperty.RegisterDirect<SelectingItemsControl, object>(
  60. nameof(SelectedItem),
  61. o => o.SelectedItem,
  62. (o, v) => o.SelectedItem = v,
  63. defaultBindingMode: BindingMode.TwoWay);
  64. /// <summary>
  65. /// Defines the <see cref="SelectedItems"/> property.
  66. /// </summary>
  67. protected static readonly DirectProperty<SelectingItemsControl, IList> SelectedItemsProperty =
  68. AvaloniaProperty.RegisterDirect<SelectingItemsControl, IList>(
  69. nameof(SelectedItems),
  70. o => o.SelectedItems,
  71. (o, v) => o.SelectedItems = v);
  72. /// <summary>
  73. /// Defines the <see cref="Selection"/> property.
  74. /// </summary>
  75. public static readonly DirectProperty<SelectingItemsControl, ISelectionModel> SelectionProperty =
  76. AvaloniaProperty.RegisterDirect<SelectingItemsControl, ISelectionModel>(
  77. nameof(Selection),
  78. o => o.Selection,
  79. (o, v) => o.Selection = v);
  80. /// <summary>
  81. /// Defines the <see cref="SelectionMode"/> property.
  82. /// </summary>
  83. protected static readonly StyledProperty<SelectionMode> SelectionModeProperty =
  84. AvaloniaProperty.Register<SelectingItemsControl, SelectionMode>(
  85. nameof(SelectionMode));
  86. /// <summary>
  87. /// Event that should be raised by items that implement <see cref="ISelectable"/> to
  88. /// notify the parent <see cref="SelectingItemsControl"/> that their selection state
  89. /// has changed.
  90. /// </summary>
  91. public static readonly RoutedEvent<RoutedEventArgs> IsSelectedChangedEvent =
  92. RoutedEvent.Register<SelectingItemsControl, RoutedEventArgs>(
  93. "IsSelectedChanged",
  94. RoutingStrategies.Bubble);
  95. /// <summary>
  96. /// Defines the <see cref="SelectionChanged"/> event.
  97. /// </summary>
  98. public static readonly RoutedEvent<SelectionChangedEventArgs> SelectionChangedEvent =
  99. RoutedEvent.Register<SelectingItemsControl, SelectionChangedEventArgs>(
  100. "SelectionChanged",
  101. RoutingStrategies.Bubble);
  102. private static readonly IList Empty = Array.Empty<object>();
  103. private readonly SelectedItemsSync _selectedItems;
  104. private ISelectionModel _selection;
  105. private int _selectedIndex = -1;
  106. private object _selectedItem;
  107. private bool _ignoreContainerSelectionChanged;
  108. private int _updateCount;
  109. private int _updateSelectedIndex;
  110. private object _updateSelectedItem;
  111. public SelectingItemsControl()
  112. {
  113. // Setting Selection to null causes a default SelectionModel to be created.
  114. Selection = null;
  115. _selectedItems = new SelectedItemsSync(Selection);
  116. }
  117. /// <summary>
  118. /// Initializes static members of the <see cref="SelectingItemsControl"/> class.
  119. /// </summary>
  120. static SelectingItemsControl()
  121. {
  122. IsSelectedChangedEvent.AddClassHandler<SelectingItemsControl>((x, e) => x.ContainerSelectionChanged(e));
  123. }
  124. /// <summary>
  125. /// Occurs when the control's selection changes.
  126. /// </summary>
  127. public event EventHandler<SelectionChangedEventArgs> SelectionChanged
  128. {
  129. add { AddHandler(SelectionChangedEvent, value); }
  130. remove { RemoveHandler(SelectionChangedEvent, value); }
  131. }
  132. /// <summary>
  133. /// Gets or sets a value indicating whether to automatically scroll to newly selected items.
  134. /// </summary>
  135. public bool AutoScrollToSelectedItem
  136. {
  137. get { return GetValue(AutoScrollToSelectedItemProperty); }
  138. set { SetValue(AutoScrollToSelectedItemProperty, value); }
  139. }
  140. /// <summary>
  141. /// Gets or sets the index of the selected item.
  142. /// </summary>
  143. public int SelectedIndex
  144. {
  145. get => Selection.SelectedIndex != default ? Selection.SelectedIndex.GetAt(0) : -1;
  146. set
  147. {
  148. if (_updateCount == 0)
  149. {
  150. if (value != SelectedIndex)
  151. {
  152. Selection.SelectedIndex = new IndexPath(value);
  153. }
  154. }
  155. else
  156. {
  157. _updateSelectedIndex = value;
  158. _updateSelectedItem = null;
  159. }
  160. }
  161. }
  162. /// <summary>
  163. /// Gets or sets the selected item.
  164. /// </summary>
  165. public object SelectedItem
  166. {
  167. get => Selection.SelectedItem;
  168. set
  169. {
  170. if (_updateCount == 0)
  171. {
  172. SelectedIndex = IndexOf(Items, value);
  173. }
  174. else
  175. {
  176. _updateSelectedItem = value;
  177. _updateSelectedIndex = int.MinValue;
  178. }
  179. }
  180. }
  181. /// <summary>
  182. /// Gets or sets the selected items.
  183. /// </summary>
  184. protected IList SelectedItems
  185. {
  186. get => _selectedItems.GetOrCreateItems();
  187. set => _selectedItems.SetItems(value);
  188. }
  189. /// <summary>
  190. /// Gets or sets a model holding the current selection.
  191. /// </summary>
  192. protected ISelectionModel Selection
  193. {
  194. get => _selection;
  195. set
  196. {
  197. value ??= new SelectionModel
  198. {
  199. SingleSelect = !SelectionMode.HasFlagCustom(SelectionMode.Multiple),
  200. AutoSelect = SelectionMode.HasFlagCustom(SelectionMode.AlwaysSelected),
  201. RetainSelectionOnReset = true,
  202. };
  203. if (_selection != value)
  204. {
  205. if (value == null)
  206. {
  207. throw new ArgumentNullException(nameof(value), "Cannot set Selection to null.");
  208. }
  209. else if (value.Source != null && value.Source != Items)
  210. {
  211. throw new ArgumentException("Selection has invalid Source.");
  212. }
  213. List<object> oldSelection = null;
  214. if (_selection != null)
  215. {
  216. oldSelection = Selection.SelectedItems.ToList();
  217. _selection.PropertyChanged -= OnSelectionModelPropertyChanged;
  218. _selection.SelectionChanged -= OnSelectionModelSelectionChanged;
  219. MarkContainersUnselected();
  220. }
  221. _selection = value;
  222. if (oldSelection?.Count > 0)
  223. {
  224. RaiseEvent(new SelectionChangedEventArgs(
  225. SelectionChangedEvent,
  226. oldSelection,
  227. Array.Empty<object>()));
  228. }
  229. if (_selection != null)
  230. {
  231. _selection.Source = Items;
  232. _selection.PropertyChanged += OnSelectionModelPropertyChanged;
  233. _selection.SelectionChanged += OnSelectionModelSelectionChanged;
  234. if (_selection.SingleSelect)
  235. {
  236. SelectionMode &= ~SelectionMode.Multiple;
  237. }
  238. else
  239. {
  240. SelectionMode |= SelectionMode.Multiple;
  241. }
  242. if (_selection.AutoSelect)
  243. {
  244. SelectionMode |= SelectionMode.AlwaysSelected;
  245. }
  246. else
  247. {
  248. SelectionMode &= ~SelectionMode.AlwaysSelected;
  249. }
  250. UpdateContainerSelection();
  251. var selectedIndex = SelectedIndex;
  252. var selectedItem = SelectedItem;
  253. if (_selectedIndex != selectedIndex)
  254. {
  255. RaisePropertyChanged(SelectedIndexProperty, _selectedIndex, selectedIndex);
  256. _selectedIndex = selectedIndex;
  257. }
  258. if (_selectedItem != selectedItem)
  259. {
  260. RaisePropertyChanged(SelectedItemProperty, _selectedItem, selectedItem);
  261. _selectedItem = selectedItem;
  262. }
  263. if (selectedIndex != -1)
  264. {
  265. RaiseEvent(new SelectionChangedEventArgs(
  266. SelectionChangedEvent,
  267. Array.Empty<object>(),
  268. Selection.SelectedItems.ToList()));
  269. }
  270. }
  271. }
  272. }
  273. }
  274. /// <summary>
  275. /// Gets or sets the selection mode.
  276. /// </summary>
  277. /// <remarks>
  278. /// Note that the selection mode only applies to selections made via user interaction.
  279. /// Multiple selections can be made programatically regardless of the value of this property.
  280. /// </remarks>
  281. protected SelectionMode SelectionMode
  282. {
  283. get { return GetValue(SelectionModeProperty); }
  284. set { SetValue(SelectionModeProperty, value); }
  285. }
  286. /// <summary>
  287. /// Gets a value indicating whether <see cref="SelectionMode.AlwaysSelected"/> is set.
  288. /// </summary>
  289. protected bool AlwaysSelected => (SelectionMode & SelectionMode.AlwaysSelected) != 0;
  290. /// <inheritdoc/>
  291. public override void BeginInit()
  292. {
  293. base.BeginInit();
  294. InternalBeginInit();
  295. }
  296. /// <inheritdoc/>
  297. public override void EndInit()
  298. {
  299. InternalEndInit();
  300. base.EndInit();
  301. }
  302. /// <summary>
  303. /// Scrolls the specified item into view.
  304. /// </summary>
  305. /// <param name="index">The index of the item.</param>
  306. public void ScrollIntoView(int index) => Presenter?.ScrollIntoView(index);
  307. /// <summary>
  308. /// Scrolls the specified item into view.
  309. /// </summary>
  310. /// <param name="item">The item.</param>
  311. public void ScrollIntoView(object item) => ScrollIntoView(IndexOf(Items, item));
  312. /// <summary>
  313. /// Tries to get the container that was the source of an event.
  314. /// </summary>
  315. /// <param name="eventSource">The control that raised the event.</param>
  316. /// <returns>The container or null if the event did not originate in a container.</returns>
  317. protected IControl GetContainerFromEventSource(IInteractive eventSource)
  318. {
  319. var parent = (IVisual)eventSource;
  320. while (parent != null)
  321. {
  322. if (parent is IControl control && control.LogicalParent == this
  323. && ItemContainerGenerator?.IndexFromContainer(control) != -1)
  324. {
  325. return control;
  326. }
  327. parent = parent.VisualParent;
  328. }
  329. return null;
  330. }
  331. /// <inheritdoc/>
  332. protected override void ItemsChanged(AvaloniaPropertyChangedEventArgs e)
  333. {
  334. if (_updateCount == 0)
  335. {
  336. Selection.Source = e.NewValue;
  337. }
  338. base.ItemsChanged(e);
  339. }
  340. /// <inheritdoc/>
  341. protected override void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  342. {
  343. base.ItemsCollectionChanged(sender, e);
  344. }
  345. /// <inheritdoc/>
  346. protected override void OnContainersMaterialized(ItemContainerEventArgs e)
  347. {
  348. base.OnContainersMaterialized(e);
  349. foreach (var container in e.Containers)
  350. {
  351. if ((container.ContainerControl as ISelectable)?.IsSelected == true)
  352. {
  353. Selection.Select(container.Index);
  354. MarkContainerSelected(container.ContainerControl, true);
  355. }
  356. else if (Selection.IsSelected(container.Index) == true)
  357. {
  358. MarkContainerSelected(container.ContainerControl, true);
  359. }
  360. }
  361. }
  362. /// <inheritdoc/>
  363. protected override void OnContainersDematerialized(ItemContainerEventArgs e)
  364. {
  365. base.OnContainersDematerialized(e);
  366. var panel = (InputElement)Presenter.Panel;
  367. if (panel != null)
  368. {
  369. foreach (var container in e.Containers)
  370. {
  371. if (KeyboardNavigation.GetTabOnceActiveElement(panel) == container.ContainerControl)
  372. {
  373. KeyboardNavigation.SetTabOnceActiveElement(panel, null);
  374. break;
  375. }
  376. }
  377. }
  378. }
  379. protected override void OnContainersRecycled(ItemContainerEventArgs e)
  380. {
  381. foreach (var i in e.Containers)
  382. {
  383. if (i.ContainerControl != null && i.Item != null)
  384. {
  385. bool selected = Selection.IsSelected(i.Index) == true;
  386. MarkContainerSelected(i.ContainerControl, selected);
  387. }
  388. }
  389. }
  390. /// <inheritdoc/>
  391. protected override void OnDataContextBeginUpdate()
  392. {
  393. base.OnDataContextBeginUpdate();
  394. InternalBeginInit();
  395. }
  396. /// <inheritdoc/>
  397. protected override void OnDataContextEndUpdate()
  398. {
  399. base.OnDataContextEndUpdate();
  400. InternalEndInit();
  401. }
  402. protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
  403. {
  404. base.OnPropertyChanged(change);
  405. if (change.Property == SelectionModeProperty)
  406. {
  407. var mode = change.NewValue.GetValueOrDefault<SelectionMode>();
  408. Selection.SingleSelect = !mode.HasFlagCustom(SelectionMode.Multiple);
  409. Selection.AutoSelect = mode.HasFlagCustom(SelectionMode.AlwaysSelected);
  410. }
  411. }
  412. protected override void OnKeyDown(KeyEventArgs e)
  413. {
  414. base.OnKeyDown(e);
  415. if (!e.Handled)
  416. {
  417. var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
  418. bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
  419. if (ItemCount > 0 &&
  420. Match(keymap.SelectAll) &&
  421. (((SelectionMode & SelectionMode.Multiple) != 0) ||
  422. (SelectionMode & SelectionMode.Toggle) != 0))
  423. {
  424. Selection.SelectAll();
  425. e.Handled = true;
  426. }
  427. }
  428. }
  429. /// <summary>
  430. /// Moves the selection in the specified direction relative to the current selection.
  431. /// </summary>
  432. /// <param name="direction">The direction to move.</param>
  433. /// <param name="wrap">Whether to wrap when the selection reaches the first or last item.</param>
  434. /// <returns>True if the selection was moved; otherwise false.</returns>
  435. protected bool MoveSelection(NavigationDirection direction, bool wrap)
  436. {
  437. var from = SelectedIndex != -1 ? ItemContainerGenerator.ContainerFromIndex(SelectedIndex) : null;
  438. return MoveSelection(from, direction, wrap);
  439. }
  440. /// <summary>
  441. /// Moves the selection in the specified direction relative to the specified container.
  442. /// </summary>
  443. /// <param name="from">The container which serves as a starting point for the movement.</param>
  444. /// <param name="direction">The direction to move.</param>
  445. /// <param name="wrap">Whether to wrap when the selection reaches the first or last item.</param>
  446. /// <returns>True if the selection was moved; otherwise false.</returns>
  447. protected bool MoveSelection(IControl from, NavigationDirection direction, bool wrap)
  448. {
  449. if (Presenter?.Panel is INavigableContainer container &&
  450. GetNextControl(container, direction, from, wrap) is IControl next)
  451. {
  452. var index = ItemContainerGenerator.IndexFromContainer(next);
  453. if (index != -1)
  454. {
  455. SelectedIndex = index;
  456. return true;
  457. }
  458. }
  459. return false;
  460. }
  461. /// <summary>
  462. /// Updates the selection for an item based on user interaction.
  463. /// </summary>
  464. /// <param name="index">The index of the item.</param>
  465. /// <param name="select">Whether the item should be selected or unselected.</param>
  466. /// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
  467. /// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
  468. /// <param name="rightButton">Whether the event is a right-click.</param>
  469. protected void UpdateSelection(
  470. int index,
  471. bool select = true,
  472. bool rangeModifier = false,
  473. bool toggleModifier = false,
  474. bool rightButton = false)
  475. {
  476. if (index != -1)
  477. {
  478. if (select)
  479. {
  480. var mode = SelectionMode;
  481. var multi = (mode & SelectionMode.Multiple) != 0;
  482. var toggle = (toggleModifier || (mode & SelectionMode.Toggle) != 0);
  483. var range = multi && rangeModifier;
  484. if (rightButton)
  485. {
  486. if (Selection.IsSelected(index) == false)
  487. {
  488. SelectedIndex = index;
  489. }
  490. }
  491. else if (range)
  492. {
  493. using var operation = Selection.Update();
  494. var anchor = Selection.AnchorIndex;
  495. if (anchor.GetSize() == 0)
  496. {
  497. anchor = new IndexPath(0);
  498. }
  499. Selection.ClearSelection();
  500. Selection.AnchorIndex = anchor;
  501. Selection.SelectRangeFromAnchor(index);
  502. }
  503. else if (multi && toggle)
  504. {
  505. if (Selection.IsSelected(index) == true)
  506. {
  507. Selection.Deselect(index);
  508. }
  509. else
  510. {
  511. Selection.Select(index);
  512. }
  513. }
  514. else if (toggle)
  515. {
  516. SelectedIndex = (SelectedIndex == index) ? -1 : index;
  517. }
  518. else
  519. {
  520. using var operation = Selection.Update();
  521. Selection.ClearSelection();
  522. Selection.Select(index);
  523. }
  524. if (Presenter?.Panel != null)
  525. {
  526. var container = ItemContainerGenerator.ContainerFromIndex(index);
  527. KeyboardNavigation.SetTabOnceActiveElement(
  528. (InputElement)Presenter.Panel,
  529. container);
  530. }
  531. }
  532. else
  533. {
  534. LostSelection();
  535. }
  536. }
  537. }
  538. /// <summary>
  539. /// Updates the selection for a container based on user interaction.
  540. /// </summary>
  541. /// <param name="container">The container.</param>
  542. /// <param name="select">Whether the container should be selected or unselected.</param>
  543. /// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
  544. /// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
  545. /// <param name="rightButton">Whether the event is a right-click.</param>
  546. protected void UpdateSelection(
  547. IControl container,
  548. bool select = true,
  549. bool rangeModifier = false,
  550. bool toggleModifier = false,
  551. bool rightButton = false)
  552. {
  553. var index = ItemContainerGenerator?.IndexFromContainer(container) ?? -1;
  554. if (index != -1)
  555. {
  556. UpdateSelection(index, select, rangeModifier, toggleModifier, rightButton);
  557. }
  558. }
  559. /// <summary>
  560. /// Updates the selection based on an event that may have originated in a container that
  561. /// belongs to the control.
  562. /// </summary>
  563. /// <param name="eventSource">The control that raised the event.</param>
  564. /// <param name="select">Whether the container should be selected or unselected.</param>
  565. /// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
  566. /// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
  567. /// <param name="rightButton">Whether the event is a right-click.</param>
  568. /// <returns>
  569. /// True if the event originated from a container that belongs to the control; otherwise
  570. /// false.
  571. /// </returns>
  572. protected bool UpdateSelectionFromEventSource(
  573. IInteractive eventSource,
  574. bool select = true,
  575. bool rangeModifier = false,
  576. bool toggleModifier = false,
  577. bool rightButton = false)
  578. {
  579. var container = GetContainerFromEventSource(eventSource);
  580. if (container != null)
  581. {
  582. UpdateSelection(container, select, rangeModifier, toggleModifier, rightButton);
  583. return true;
  584. }
  585. return false;
  586. }
  587. /// <summary>
  588. /// Called when <see cref="SelectionModel.PropertyChanged"/> is raised.
  589. /// </summary>
  590. /// <param name="sender">The sender.</param>
  591. /// <param name="e">The event args.</param>
  592. private void OnSelectionModelPropertyChanged(object sender, PropertyChangedEventArgs e)
  593. {
  594. if (e.PropertyName == nameof(SelectionModel.AnchorIndex) && AutoScrollToSelectedItem)
  595. {
  596. if (Selection.AnchorIndex.GetSize() > 0)
  597. {
  598. ScrollIntoView(Selection.AnchorIndex.GetAt(0));
  599. }
  600. }
  601. }
  602. /// <summary>
  603. /// Called when <see cref="SelectionModel.SelectionChanged"/> is raised.
  604. /// </summary>
  605. /// <param name="sender">The sender.</param>
  606. /// <param name="e">The event args.</param>
  607. private void OnSelectionModelSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
  608. {
  609. void Mark(int index, bool selected)
  610. {
  611. var container = ItemContainerGenerator.ContainerFromIndex(index);
  612. if (container != null)
  613. {
  614. MarkContainerSelected(container, selected);
  615. }
  616. }
  617. if (e.SelectedIndices.Count > 0 || e.DeselectedIndices.Count > 0)
  618. {
  619. foreach (var i in e.SelectedIndices)
  620. {
  621. Mark(i.GetAt(0), true);
  622. }
  623. foreach (var i in e.DeselectedIndices)
  624. {
  625. Mark(i.GetAt(0), false);
  626. }
  627. }
  628. else if (e.DeselectedItems.Count > 0)
  629. {
  630. // (De)selected indices being empty means that a selected item was removed from
  631. // the Items (it can't tell us the index of the item because the index is no longer
  632. // valid). In this case, we just update the selection state of all containers.
  633. UpdateContainerSelection();
  634. }
  635. var newSelectedIndex = SelectedIndex;
  636. var newSelectedItem = SelectedItem;
  637. if (newSelectedIndex != _selectedIndex)
  638. {
  639. RaisePropertyChanged(SelectedIndexProperty, _selectedIndex, newSelectedIndex);
  640. _selectedIndex = newSelectedIndex;
  641. }
  642. if (newSelectedItem != _selectedItem)
  643. {
  644. RaisePropertyChanged(SelectedItemProperty, _selectedItem, newSelectedItem);
  645. _selectedItem = newSelectedItem;
  646. }
  647. var ev = new SelectionChangedEventArgs(
  648. SelectionChangedEvent,
  649. e.DeselectedItems.ToList(),
  650. e.SelectedItems.ToList());
  651. RaiseEvent(ev);
  652. }
  653. /// <summary>
  654. /// Called when a container raises the <see cref="IsSelectedChangedEvent"/>.
  655. /// </summary>
  656. /// <param name="e">The event.</param>
  657. private void ContainerSelectionChanged(RoutedEventArgs e)
  658. {
  659. if (!_ignoreContainerSelectionChanged)
  660. {
  661. var control = e.Source as IControl;
  662. var selectable = e.Source as ISelectable;
  663. if (control != null &&
  664. selectable != null &&
  665. control.LogicalParent == this &&
  666. ItemContainerGenerator?.IndexFromContainer(control) != -1)
  667. {
  668. UpdateSelection(control, selectable.IsSelected);
  669. }
  670. }
  671. if (e.Source != this)
  672. {
  673. e.Handled = true;
  674. }
  675. }
  676. /// <summary>
  677. /// Called when the currently selected item is lost and the selection must be changed
  678. /// depending on the <see cref="SelectionMode"/> property.
  679. /// </summary>
  680. private void LostSelection()
  681. {
  682. var items = Items?.Cast<object>();
  683. var index = -1;
  684. if (items != null && AlwaysSelected)
  685. {
  686. index = Math.Min(SelectedIndex, items.Count() - 1);
  687. }
  688. SelectedIndex = index;
  689. }
  690. /// <summary>
  691. /// Sets a container's 'selected' class or <see cref="ISelectable.IsSelected"/>.
  692. /// </summary>
  693. /// <param name="container">The container.</param>
  694. /// <param name="selected">Whether the control is selected</param>
  695. /// <returns>The previous selection state.</returns>
  696. private bool MarkContainerSelected(IControl container, bool selected)
  697. {
  698. try
  699. {
  700. var selectable = container as ISelectable;
  701. bool result;
  702. _ignoreContainerSelectionChanged = true;
  703. if (selectable != null)
  704. {
  705. result = selectable.IsSelected;
  706. selectable.IsSelected = selected;
  707. }
  708. else
  709. {
  710. result = container.Classes.Contains(":selected");
  711. ((IPseudoClasses)container.Classes).Set(":selected", selected);
  712. }
  713. return result;
  714. }
  715. finally
  716. {
  717. _ignoreContainerSelectionChanged = false;
  718. }
  719. }
  720. private void MarkContainersUnselected()
  721. {
  722. foreach (var container in ItemContainerGenerator.Containers)
  723. {
  724. MarkContainerSelected(container.ContainerControl, false);
  725. }
  726. }
  727. private void UpdateContainerSelection()
  728. {
  729. foreach (var container in ItemContainerGenerator.Containers)
  730. {
  731. MarkContainerSelected(
  732. container.ContainerControl,
  733. Selection.IsSelected(container.Index) != false);
  734. }
  735. }
  736. /// <summary>
  737. /// Sets an item container's 'selected' class or <see cref="ISelectable.IsSelected"/>.
  738. /// </summary>
  739. /// <param name="index">The index of the item.</param>
  740. /// <param name="selected">Whether the item should be selected or deselected.</param>
  741. private void MarkItemSelected(int index, bool selected)
  742. {
  743. var container = ItemContainerGenerator?.ContainerFromIndex(index);
  744. if (container != null)
  745. {
  746. MarkContainerSelected(container, selected);
  747. }
  748. }
  749. private void UpdateFinished()
  750. {
  751. Selection.Source = Items;
  752. if (_updateSelectedItem != null)
  753. {
  754. SelectedItem = _updateSelectedItem;
  755. }
  756. else
  757. {
  758. if (ItemCount == 0 && SelectedIndex != -1)
  759. {
  760. SelectedIndex = -1;
  761. }
  762. else
  763. {
  764. if (_updateSelectedIndex != int.MinValue)
  765. {
  766. SelectedIndex = _updateSelectedIndex;
  767. }
  768. if (AlwaysSelected && SelectedIndex == -1)
  769. {
  770. SelectedIndex = 0;
  771. }
  772. }
  773. }
  774. }
  775. private void InternalBeginInit()
  776. {
  777. if (_updateCount == 0)
  778. {
  779. _updateSelectedIndex = int.MinValue;
  780. }
  781. ++_updateCount;
  782. }
  783. private void InternalEndInit()
  784. {
  785. Debug.Assert(_updateCount > 0);
  786. if (--_updateCount == 0)
  787. {
  788. UpdateFinished();
  789. }
  790. }
  791. }
  792. }