ItemsControl.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Collections.Specialized;
  5. using System.Linq;
  6. using Avalonia.Automation.Peers;
  7. using Avalonia.Collections;
  8. using Avalonia.Controls.Generators;
  9. using Avalonia.Controls.Metadata;
  10. using Avalonia.Controls.Presenters;
  11. using Avalonia.Controls.Primitives;
  12. using Avalonia.Controls.Templates;
  13. using Avalonia.Controls.Utils;
  14. using Avalonia.Data;
  15. using Avalonia.Input;
  16. using Avalonia.LogicalTree;
  17. using Avalonia.Metadata;
  18. using Avalonia.Styling;
  19. using Avalonia.VisualTree;
  20. namespace Avalonia.Controls
  21. {
  22. /// <summary>
  23. /// Displays a collection of items.
  24. /// </summary>
  25. [PseudoClasses(":empty", ":singleitem")]
  26. public class ItemsControl : TemplatedControl, IItemsPresenterHost, ICollectionChangedListener, IChildIndexProvider
  27. {
  28. /// <summary>
  29. /// The default value for the <see cref="ItemsPanel"/> property.
  30. /// </summary>
  31. private static readonly FuncTemplate<Panel> DefaultPanel =
  32. new FuncTemplate<Panel>(() => new StackPanel());
  33. /// <summary>
  34. /// Defines the <see cref="Items"/> property.
  35. /// </summary>
  36. public static readonly DirectProperty<ItemsControl, IEnumerable?> ItemsProperty =
  37. AvaloniaProperty.RegisterDirect<ItemsControl, IEnumerable?>(nameof(Items), o => o.Items, (o, v) => o.Items = v);
  38. /// <summary>
  39. /// Defines the <see cref="ItemContainerTheme"/> property.
  40. /// </summary>
  41. public static readonly StyledProperty<ControlTheme?> ItemContainerThemeProperty =
  42. AvaloniaProperty.Register<ItemsControl, ControlTheme?>(nameof(ItemContainerTheme));
  43. /// <summary>
  44. /// Defines the <see cref="ItemCount"/> property.
  45. /// </summary>
  46. public static readonly DirectProperty<ItemsControl, int> ItemCountProperty =
  47. AvaloniaProperty.RegisterDirect<ItemsControl, int>(nameof(ItemCount), o => o.ItemCount);
  48. /// <summary>
  49. /// Defines the <see cref="ItemsPanel"/> property.
  50. /// </summary>
  51. public static readonly StyledProperty<ITemplate<Panel>> ItemsPanelProperty =
  52. AvaloniaProperty.Register<ItemsControl, ITemplate<Panel>>(nameof(ItemsPanel), DefaultPanel);
  53. /// <summary>
  54. /// Defines the <see cref="ItemTemplate"/> property.
  55. /// </summary>
  56. public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
  57. AvaloniaProperty.Register<ItemsControl, IDataTemplate?>(nameof(ItemTemplate));
  58. /// <summary>
  59. /// Defines the <see cref="DisplayMemberBinding" /> property
  60. /// </summary>
  61. public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty =
  62. AvaloniaProperty.Register<ItemsControl, IBinding?>(nameof(DisplayMemberBinding));
  63. /// <summary>
  64. /// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
  65. /// </summary>
  66. [AssignBinding]
  67. public IBinding? DisplayMemberBinding
  68. {
  69. get { return GetValue(DisplayMemberBindingProperty); }
  70. set { SetValue(DisplayMemberBindingProperty, value); }
  71. }
  72. private IEnumerable? _items = new AvaloniaList<object>();
  73. private int _itemCount;
  74. private ItemContainerGenerator? _itemContainerGenerator;
  75. private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
  76. /// <summary>
  77. /// Initializes static members of the <see cref="ItemsControl"/> class.
  78. /// </summary>
  79. static ItemsControl()
  80. {
  81. ItemsProperty.Changed.AddClassHandler<ItemsControl>((x, e) => x.ItemsChanged(e));
  82. ItemTemplateProperty.Changed.AddClassHandler<ItemsControl>((x, e) => x.ItemTemplateChanged(e));
  83. }
  84. /// <summary>
  85. /// Initializes a new instance of the <see cref="ItemsControl"/> class.
  86. /// </summary>
  87. public ItemsControl()
  88. {
  89. UpdatePseudoClasses(0);
  90. SubscribeToItems(_items);
  91. }
  92. /// <summary>
  93. /// Gets the <see cref="ItemContainerGenerator"/> for the control.
  94. /// </summary>
  95. public ItemContainerGenerator ItemContainerGenerator => _itemContainerGenerator ??= new(this);
  96. /// <summary>
  97. /// Gets or sets the items to display.
  98. /// </summary>
  99. [Content]
  100. public IEnumerable? Items
  101. {
  102. get { return _items; }
  103. set { SetAndRaise(ItemsProperty, ref _items, value); }
  104. }
  105. /// <summary>
  106. /// Gets or sets the <see cref="ControlTheme"/> that is applied to the container element generated for each item.
  107. /// </summary>
  108. public ControlTheme? ItemContainerTheme
  109. {
  110. get { return GetValue(ItemContainerThemeProperty); }
  111. set { SetValue(ItemContainerThemeProperty, value); }
  112. }
  113. /// <summary>
  114. /// Gets the number of items in <see cref="Items"/>.
  115. /// </summary>
  116. public int ItemCount
  117. {
  118. get => _itemCount;
  119. private set => SetAndRaise(ItemCountProperty, ref _itemCount, value);
  120. }
  121. /// <summary>
  122. /// Gets or sets the panel used to display the items.
  123. /// </summary>
  124. public ITemplate<Panel> ItemsPanel
  125. {
  126. get { return GetValue(ItemsPanelProperty); }
  127. set { SetValue(ItemsPanelProperty, value); }
  128. }
  129. /// <summary>
  130. /// Gets or sets the data template used to display the items in the control.
  131. /// </summary>
  132. public IDataTemplate? ItemTemplate
  133. {
  134. get { return GetValue(ItemTemplateProperty); }
  135. set { SetValue(ItemTemplateProperty, value); }
  136. }
  137. /// <summary>
  138. /// Gets the items presenter control.
  139. /// </summary>
  140. public ItemsPresenter? Presenter { get; private set; }
  141. private protected bool WrapFocus { get; set; }
  142. event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged
  143. {
  144. add => _childIndexChanged += value;
  145. remove => _childIndexChanged -= value;
  146. }
  147. /// <summary>
  148. /// Returns the container for the item at the specified index.
  149. /// </summary>
  150. /// <param name="index">The index of the item to retrieve.</param>
  151. /// <returns>
  152. /// The container for the item at the specified index within the item collection, if the
  153. /// item has a container; otherwise, null.
  154. /// </returns>
  155. public Control? ContainerFromIndex(int index) => Presenter?.ContainerFromIndex(index);
  156. /// <summary>
  157. /// Returns the container corresponding to the specified item.
  158. /// </summary>
  159. /// <param name="item">The item to retrieve the container for.</param>
  160. /// <returns>
  161. /// A container that corresponds to the specified item, if the item has a container and
  162. /// exists in the collection; otherwise, null.
  163. /// </returns>
  164. public Control? ContainerFromItem(object item)
  165. {
  166. throw new NotImplementedException();
  167. }
  168. /// <summary>
  169. /// Returns the index to the item that has the specified, generated container.
  170. /// </summary>
  171. /// <param name="container">The generated container to retrieve the item index for.</param>
  172. /// <returns>
  173. /// The index to the item that corresponds to the specified generated container, or -1 if
  174. /// <paramref name="container"/> is not found.
  175. /// </returns>
  176. public int IndexFromContainer(Control container) => Presenter?.IndexFromContainer(container) ?? -1;
  177. /// <summary>
  178. /// Returns the item that corresponds to the specified, generated container.
  179. /// </summary>
  180. /// <param name="container">The control that corresponds to the item to be returned.</param>
  181. /// <returns>
  182. /// The contained item, or the container if it does not contain an item.
  183. /// </returns>
  184. public object? ItemFromContainer(Control container)
  185. {
  186. // TODO: Should this throw or return null of container isn't a container?
  187. throw new NotImplementedException();
  188. }
  189. /// <summary>
  190. /// Gets the currently realized containers.
  191. /// </summary>
  192. public IEnumerable<Control> GetRealizedContainers() => Presenter?.GetRealizedContainers() ?? Array.Empty<Control>();
  193. /// <inheritdoc/>
  194. void IItemsPresenterHost.RegisterItemsPresenter(ItemsPresenter presenter)
  195. {
  196. if (Presenter is IChildIndexProvider oldInnerProvider)
  197. {
  198. oldInnerProvider.ChildIndexChanged -= PresenterChildIndexChanged;
  199. }
  200. Presenter = presenter;
  201. ////ItemContainerGenerator?.Clear();
  202. if (Presenter is IChildIndexProvider innerProvider)
  203. {
  204. innerProvider.ChildIndexChanged += PresenterChildIndexChanged;
  205. _childIndexChanged?.Invoke(this, ChildIndexChangedEventArgs.Empty);
  206. }
  207. }
  208. void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
  209. {
  210. }
  211. void ICollectionChangedListener.Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
  212. {
  213. }
  214. void ICollectionChangedListener.PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
  215. {
  216. ItemsCollectionChanged(sender, e);
  217. }
  218. /// <summary>
  219. /// Gets the item at the specified index in a collection.
  220. /// </summary>
  221. /// <param name="items">The collection.</param>
  222. /// <param name="index">The index.</param>
  223. /// <returns>The item at the given index or null if the index is out of bounds.</returns>
  224. protected static object? ElementAt(IEnumerable? items, int index)
  225. {
  226. if (index != -1 && index < items.Count())
  227. {
  228. return items!.ElementAt(index) ?? null;
  229. }
  230. else
  231. {
  232. return null;
  233. }
  234. }
  235. /// <summary>
  236. /// Creates or a container that can be used to display an item.
  237. /// </summary>
  238. protected internal virtual Control CreateContainerOverride() => new ContentPresenter();
  239. /// <summary>
  240. /// Prepares the specified element to display the specified item.
  241. /// </summary>
  242. /// <param name="container">The element that's used to display the specified item.</param>
  243. /// <param name="item">The item to display.</param>
  244. /// <param name="index">The index of the item to display.</param>
  245. protected internal virtual void PrepareContainerForItemOverride(Control container, object? item, int index)
  246. {
  247. if (container == item)
  248. return;
  249. if (container is HeaderedContentControl hcc)
  250. {
  251. hcc.Content = item;
  252. if (item is IHeadered headered)
  253. hcc.Header = headered.Header;
  254. else if (item is not Visual)
  255. hcc.Header = item;
  256. if (ItemTemplate is { } it)
  257. hcc.HeaderTemplate = it;
  258. }
  259. else if (container is ContentControl cc)
  260. {
  261. cc.Content = item;
  262. if (ItemTemplate is { } it)
  263. cc.ContentTemplate = it;
  264. }
  265. else if (container is ContentPresenter p)
  266. {
  267. p.Content = item;
  268. if (ItemTemplate is { } it)
  269. p.ContentTemplate = it;
  270. }
  271. }
  272. /// <summary>
  273. /// Called when the index for a container changes due to an insertion or removal in the
  274. /// items collection.
  275. /// </summary>
  276. /// <param name="container">The container whose index changed.</param>
  277. /// <param name="oldIndex">The old index.</param>
  278. /// <param name="newIndex">The new index.</param>
  279. protected virtual void ContainerIndexChangedOverride(Control container, int oldIndex, int newIndex)
  280. {
  281. }
  282. /// <summary>
  283. /// Undoes the effects of the <see cref="PrepareContainerForItemOverride(Control, object?, int)"/> method.
  284. /// </summary>
  285. /// <param name="container">The container element.</param>
  286. protected internal virtual void ClearContainerForItemOverride(Control container)
  287. {
  288. }
  289. /// <summary>
  290. /// Gets the index of an item in a collection.
  291. /// </summary>
  292. /// <param name="items">The collection.</param>
  293. /// <param name="item">The item.</param>
  294. /// <returns>The index of the item or -1 if the item was not found.</returns>
  295. protected static int IndexOf(IEnumerable? items, object item)
  296. {
  297. if (items != null && item != null)
  298. {
  299. var list = items as IList;
  300. if (list != null)
  301. {
  302. return list.IndexOf(item);
  303. }
  304. else
  305. {
  306. int index = 0;
  307. foreach (var i in items)
  308. {
  309. if (Equals(i, item))
  310. {
  311. return index;
  312. }
  313. ++index;
  314. }
  315. }
  316. }
  317. return -1;
  318. }
  319. /// <summary>
  320. /// Determines whether the specified item is (or is eligible to be) its own container.
  321. /// </summary>
  322. /// <param name="item">The item to check.</param>
  323. /// <returns>true if the item is (or is eligible to be) its own container; otherwise, false.</returns>
  324. protected internal virtual bool IsItemItsOwnContainerOverride(Control item) => true;
  325. /// <summary>
  326. /// Handles directional navigation within the <see cref="ItemsControl"/>.
  327. /// </summary>
  328. /// <param name="e">The key events.</param>
  329. protected override void OnKeyDown(KeyEventArgs e)
  330. {
  331. if (!e.Handled)
  332. {
  333. var focus = FocusManager.Instance;
  334. var direction = e.Key.ToNavigationDirection();
  335. var container = Presenter?.Panel as INavigableContainer;
  336. if (container == null ||
  337. focus?.Current == null ||
  338. direction == null ||
  339. direction.Value.IsTab())
  340. {
  341. return;
  342. }
  343. Visual? current = focus.Current as Visual;
  344. while (current != null)
  345. {
  346. if (current.VisualParent == container && current is IInputElement inputElement)
  347. {
  348. var next = GetNextControl(container, direction.Value, inputElement, WrapFocus);
  349. if (next != null)
  350. {
  351. focus.Focus(next, NavigationMethod.Directional, e.KeyModifiers);
  352. e.Handled = true;
  353. }
  354. break;
  355. }
  356. current = current.VisualParent;
  357. }
  358. }
  359. base.OnKeyDown(e);
  360. }
  361. protected override AutomationPeer OnCreateAutomationPeer()
  362. {
  363. return new ItemsControlAutomationPeer(this);
  364. }
  365. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
  366. {
  367. base.OnPropertyChanged(change);
  368. if (change.Property == ItemCountProperty)
  369. {
  370. UpdatePseudoClasses(change.GetNewValue<int>());
  371. }
  372. else if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null)
  373. {
  374. throw new NotImplementedException();
  375. ////_itemContainerGenerator.ItemContainerTheme = change.GetNewValue<ControlTheme?>();
  376. }
  377. }
  378. /// <summary>
  379. /// Called when the <see cref="Items"/> property changes.
  380. /// </summary>
  381. /// <param name="e">The event args.</param>
  382. protected virtual void ItemsChanged(AvaloniaPropertyChangedEventArgs e)
  383. {
  384. var oldValue = e.OldValue as IEnumerable;
  385. var newValue = e.NewValue as IEnumerable;
  386. if (oldValue is INotifyCollectionChanged incc)
  387. {
  388. CollectionChangedEventManager.Instance.RemoveListener(incc, this);
  389. }
  390. UpdateItemCount();
  391. RemoveControlItemsFromLogicalChildren(oldValue);
  392. AddControlItemsToLogicalChildren(newValue);
  393. SubscribeToItems(newValue);
  394. }
  395. /// <summary>
  396. /// Called when the <see cref="INotifyCollectionChanged.CollectionChanged"/> event is
  397. /// raised on <see cref="Items"/>.
  398. /// </summary>
  399. /// <param name="sender">The event sender.</param>
  400. /// <param name="e">The event args.</param>
  401. protected virtual void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  402. {
  403. UpdateItemCount();
  404. switch (e.Action)
  405. {
  406. case NotifyCollectionChangedAction.Add:
  407. AddControlItemsToLogicalChildren(e.NewItems);
  408. break;
  409. case NotifyCollectionChangedAction.Remove:
  410. RemoveControlItemsFromLogicalChildren(e.OldItems);
  411. break;
  412. }
  413. }
  414. internal void AddLogicalChild(Control c) => LogicalChildren.Add(c);
  415. internal void RemoveLogicalChild(Control c) => LogicalChildren.Remove(c);
  416. internal void PrepareItemContainer(Control container, object? item, int index)
  417. {
  418. // Putting this precondition in place in case we want to raise an event when a
  419. // container is realized. If we want to do that, then the event subscriber will expect
  420. // the container to be attached to the tree. Not using IsAttachedToVisualTree here
  421. // because a bunch of tests don't have a rooted visual tree.
  422. if (container.GetVisualParent() is null)
  423. throw new InvalidOperationException(
  424. "Container must be attached to parent before PrepareItemContainer is called.");
  425. var itemContainerTheme = ItemContainerTheme;
  426. if (itemContainerTheme is not null &&
  427. !container.IsSet(ThemeProperty) &&
  428. ((IStyleable)container).StyleKey == itemContainerTheme.TargetType)
  429. {
  430. container.Theme = itemContainerTheme;
  431. }
  432. if (item is not Control)
  433. container.DataContext = item;
  434. PrepareContainerForItemOverride(container, item, index);
  435. }
  436. internal void ItemContainerIndexChanged(Control container, int oldIndex, int newIndex)
  437. {
  438. ContainerIndexChangedOverride(container, oldIndex, newIndex);
  439. }
  440. /// <summary>
  441. /// Given a collection of items, adds those that are controls to the logical children.
  442. /// </summary>
  443. /// <param name="items">The items.</param>
  444. private void AddControlItemsToLogicalChildren(IEnumerable? items)
  445. {
  446. var toAdd = new List<ILogical>();
  447. if (items != null)
  448. {
  449. foreach (var i in items)
  450. {
  451. var control = i as Control;
  452. if (control != null && !LogicalChildren.Contains(control))
  453. {
  454. toAdd.Add(control);
  455. }
  456. }
  457. }
  458. LogicalChildren.AddRange(toAdd);
  459. }
  460. /// <summary>
  461. /// Given a collection of items, removes those that are controls to from logical children.
  462. /// </summary>
  463. /// <param name="items">The items.</param>
  464. private void RemoveControlItemsFromLogicalChildren(IEnumerable? items)
  465. {
  466. var toRemove = new List<ILogical>();
  467. if (items != null)
  468. {
  469. foreach (var i in items)
  470. {
  471. var control = i as Control;
  472. if (control != null)
  473. {
  474. toRemove.Add(control);
  475. }
  476. }
  477. }
  478. LogicalChildren.RemoveAll(toRemove);
  479. }
  480. /// <summary>
  481. /// Subscribes to an <see cref="Items"/> collection.
  482. /// </summary>
  483. /// <param name="items">The items collection.</param>
  484. private void SubscribeToItems(IEnumerable? items)
  485. {
  486. if (items is INotifyCollectionChanged incc)
  487. {
  488. CollectionChangedEventManager.Instance.AddListener(incc, this);
  489. }
  490. }
  491. /// <summary>
  492. /// Called when the <see cref="ItemTemplate"/> changes.
  493. /// </summary>
  494. /// <param name="e">The event args.</param>
  495. private void ItemTemplateChanged(AvaloniaPropertyChangedEventArgs e)
  496. {
  497. if (_itemContainerGenerator != null)
  498. {
  499. ////_itemContainerGenerator.ItemTemplate = (IDataTemplate?)e.NewValue;
  500. // TODO: Rebuild the item containers.
  501. }
  502. }
  503. private void UpdateItemCount()
  504. {
  505. if (Items == null)
  506. {
  507. ItemCount = 0;
  508. }
  509. else if (Items is IList list)
  510. {
  511. ItemCount = list.Count;
  512. }
  513. else
  514. {
  515. ItemCount = Items.Count();
  516. }
  517. }
  518. private void UpdatePseudoClasses(int itemCount)
  519. {
  520. PseudoClasses.Set(":empty", itemCount == 0);
  521. PseudoClasses.Set(":singleitem", itemCount == 1);
  522. }
  523. protected static IInputElement? GetNextControl(
  524. INavigableContainer container,
  525. NavigationDirection direction,
  526. IInputElement? from,
  527. bool wrap)
  528. {
  529. var current = from;
  530. for (;;)
  531. {
  532. var result = container.GetControl(direction, current, wrap);
  533. if (result is null)
  534. {
  535. return null;
  536. }
  537. if (result.Focusable &&
  538. result.IsEffectivelyEnabled &&
  539. result.IsEffectivelyVisible)
  540. {
  541. return result;
  542. }
  543. current = result;
  544. if (current == from)
  545. {
  546. return null;
  547. }
  548. switch (direction)
  549. {
  550. //We did not find an enabled first item. Move downwards until we find one.
  551. case NavigationDirection.First:
  552. direction = NavigationDirection.Down;
  553. from = result;
  554. break;
  555. //We did not find an enabled last item. Move upwards until we find one.
  556. case NavigationDirection.Last:
  557. direction = NavigationDirection.Up;
  558. from = result;
  559. break;
  560. }
  561. }
  562. }
  563. private void PresenterChildIndexChanged(object? sender, ChildIndexChangedEventArgs e)
  564. {
  565. _childIndexChanged?.Invoke(this, e);
  566. }
  567. int IChildIndexProvider.GetChildIndex(ILogical child)
  568. {
  569. return Presenter is IChildIndexProvider innerProvider
  570. ? innerProvider.GetChildIndex(child) : -1;
  571. }
  572. bool IChildIndexProvider.TryGetTotalCount(out int count)
  573. {
  574. if (Presenter is IChildIndexProvider presenter
  575. && presenter.TryGetTotalCount(out count))
  576. {
  577. return true;
  578. }
  579. count = ItemCount;
  580. return true;
  581. }
  582. }
  583. }