ItemsControl.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Collections.Specialized;
  5. using System.Diagnostics.CodeAnalysis;
  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.Data;
  14. using Avalonia.Input;
  15. using Avalonia.Interactivity;
  16. using Avalonia.Layout;
  17. using Avalonia.LogicalTree;
  18. using Avalonia.Metadata;
  19. using Avalonia.Styling;
  20. namespace Avalonia.Controls
  21. {
  22. /// <summary>
  23. /// Displays a collection of items.
  24. /// </summary>
  25. [PseudoClasses(":empty", ":singleitem")]
  26. public class ItemsControl : TemplatedControl, IChildIndexProvider, IScrollSnapPointsInfo
  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, IList?> ItemsProperty =
  37. AvaloniaProperty.RegisterDirect<ItemsControl, IList?>(
  38. nameof(Items),
  39. o => o.Items,
  40. #pragma warning disable CS0618 // Type or member is obsolete
  41. (o, v) => o.Items = v);
  42. #pragma warning restore CS0618 // Type or member is obsolete
  43. /// <summary>
  44. /// Defines the <see cref="ItemContainerTheme"/> property.
  45. /// </summary>
  46. public static readonly StyledProperty<ControlTheme?> ItemContainerThemeProperty =
  47. AvaloniaProperty.Register<ItemsControl, ControlTheme?>(nameof(ItemContainerTheme));
  48. /// <summary>
  49. /// Defines the <see cref="ItemCount"/> property.
  50. /// </summary>
  51. public static readonly DirectProperty<ItemsControl, int> ItemCountProperty =
  52. AvaloniaProperty.RegisterDirect<ItemsControl, int>(nameof(ItemCount), o => o.ItemCount);
  53. /// <summary>
  54. /// Defines the <see cref="ItemsPanel"/> property.
  55. /// </summary>
  56. public static readonly StyledProperty<ITemplate<Panel>> ItemsPanelProperty =
  57. AvaloniaProperty.Register<ItemsControl, ITemplate<Panel>>(nameof(ItemsPanel), DefaultPanel);
  58. /// <summary>
  59. /// Defines the <see cref="ItemsSource"/> property.
  60. /// </summary>
  61. public static readonly StyledProperty<IEnumerable?> ItemsSourceProperty =
  62. AvaloniaProperty.Register<ItemsControl, IEnumerable?>(nameof(ItemsSource));
  63. /// <summary>
  64. /// Defines the <see cref="ItemTemplate"/> property.
  65. /// </summary>
  66. public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
  67. AvaloniaProperty.Register<ItemsControl, IDataTemplate?>(nameof(ItemTemplate));
  68. /// <summary>
  69. /// Defines the <see cref="DisplayMemberBinding" /> property
  70. /// </summary>
  71. public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty =
  72. AvaloniaProperty.Register<ItemsControl, IBinding?>(nameof(DisplayMemberBinding));
  73. /// <summary>
  74. /// Defines the <see cref="AreHorizontalSnapPointsRegular"/> property.
  75. /// </summary>
  76. public static readonly StyledProperty<bool> AreHorizontalSnapPointsRegularProperty =
  77. AvaloniaProperty.Register<ItemsControl, bool>(nameof(AreHorizontalSnapPointsRegular));
  78. /// <summary>
  79. /// Defines the <see cref="AreVerticalSnapPointsRegular"/> property.
  80. /// </summary>
  81. public static readonly StyledProperty<bool> AreVerticalSnapPointsRegularProperty =
  82. AvaloniaProperty.Register<ItemsControl, bool>(nameof(AreVerticalSnapPointsRegular));
  83. /// <summary>
  84. /// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item.
  85. /// </summary>
  86. [AssignBinding]
  87. [InheritDataTypeFromItems(nameof(ItemsSource))]
  88. [InheritDataTypeFromItems(nameof(Items))]
  89. public IBinding? DisplayMemberBinding
  90. {
  91. get => GetValue(DisplayMemberBindingProperty);
  92. set => SetValue(DisplayMemberBindingProperty, value);
  93. }
  94. private readonly ItemCollection _items = new();
  95. private int _itemCount;
  96. private ItemContainerGenerator? _itemContainerGenerator;
  97. private EventHandler<ChildIndexChangedEventArgs>? _childIndexChanged;
  98. private IDataTemplate? _displayMemberItemTemplate;
  99. private ScrollViewer? _scrollViewer;
  100. private ItemsPresenter? _itemsPresenter;
  101. /// <summary>
  102. /// Initializes a new instance of the <see cref="ItemsControl"/> class.
  103. /// </summary>
  104. public ItemsControl()
  105. {
  106. UpdatePseudoClasses();
  107. _items.CollectionChanged += OnItemsViewCollectionChanged;
  108. }
  109. /// <summary>
  110. /// Gets the <see cref="ItemContainerGenerator"/> for the control.
  111. /// </summary>
  112. public ItemContainerGenerator ItemContainerGenerator
  113. {
  114. #pragma warning disable CS0612 // Type or member is obsolete
  115. get => _itemContainerGenerator ??= CreateItemContainerGenerator();
  116. #pragma warning restore CS0612 // Type or member is obsolete
  117. }
  118. /// <summary>
  119. /// Gets or sets the items to display.
  120. /// </summary>
  121. /// <remarks>
  122. /// Since Avalonia 11, <see cref="ItemsControl"/> has both an <see cref="Items"/> property
  123. /// and an <see cref="ItemsSource"/> property. The properties have the following differences:
  124. ///
  125. /// <list type="bullet">
  126. /// <item><see cref="Items"/> is initialized with an empty collection and is a direct property,
  127. /// meaning that it cannot be styled </item>
  128. /// <item><see cref="ItemsSource"/> is by default null, and is a styled property. This property
  129. /// is marked as the content property and will be used for items added via inline XAML.</item>
  130. /// </list>
  131. ///
  132. /// In Avalonia 11 the two properties can be used almost interchangeably but this will change
  133. /// in a later version. In order to be ready for this change, follow the following guidance:
  134. ///
  135. /// <list type="bullet">
  136. /// <item>You should use the <see cref="Items"/> property when you're assigning a collection of
  137. /// item containers directly, for example adding a collection of <see cref="ListBoxItem"/>s
  138. /// directly to a <see cref="ListBox"/>. Add the containers to the pre-existing list, do not
  139. /// reassign the <see cref="Items"/> property via the setter or with a binding.</item>
  140. /// <item>You should use the <see cref="ItemsSource"/> property when you're assigning or
  141. /// binding a collection of models which will be transformed by a data template.</item>
  142. /// </list>
  143. /// </remarks>
  144. [Content]
  145. public IList? Items
  146. {
  147. get => _items.GetItemsPropertyValue();
  148. [Obsolete("Use ItemsSource to set or bind items.")]
  149. set
  150. {
  151. var oldItems = _items.GetItemsPropertyValue();
  152. if (value != oldItems)
  153. {
  154. _items.SetItems(value);
  155. RaisePropertyChanged(ItemsProperty, oldItems, value);
  156. }
  157. }
  158. }
  159. /// <summary>
  160. /// Gets or sets the <see cref="ControlTheme"/> that is applied to the container element generated for each item.
  161. /// </summary>
  162. public ControlTheme? ItemContainerTheme
  163. {
  164. get => GetValue(ItemContainerThemeProperty);
  165. set => SetValue(ItemContainerThemeProperty, value);
  166. }
  167. /// <summary>
  168. /// Gets the number of items being displayed by the <see cref="ItemsControl"/>.
  169. /// </summary>
  170. public int ItemCount
  171. {
  172. get => _itemCount;
  173. private set
  174. {
  175. if (SetAndRaise(ItemCountProperty, ref _itemCount, value))
  176. {
  177. UpdatePseudoClasses();
  178. _childIndexChanged?.Invoke(this, ChildIndexChangedEventArgs.TotalCountChanged);
  179. }
  180. }
  181. }
  182. /// <summary>
  183. /// Gets or sets the panel used to display the items.
  184. /// </summary>
  185. public ITemplate<Panel> ItemsPanel
  186. {
  187. get => GetValue(ItemsPanelProperty);
  188. set => SetValue(ItemsPanelProperty, value);
  189. }
  190. /// <summary>
  191. /// Gets or sets a collection used to generate the content of the <see cref="ItemsControl"/>.
  192. /// </summary>
  193. /// <remarks>
  194. /// Since Avalonia 11, <see cref="ItemsControl"/> has both an <see cref="Items"/> property
  195. /// and an <see cref="ItemsSource"/> property. The properties have the following differences:
  196. ///
  197. /// <list type="bullet">
  198. /// <item><see cref="Items"/> is initialized with an empty collection and is a direct property,
  199. /// meaning that it cannot be styled </item>
  200. /// <item><see cref="ItemsSource"/> is by default null, and is a styled property. This property
  201. /// is marked as the content property and will be used for items added via inline XAML.</item>
  202. /// </list>
  203. ///
  204. /// In Avalonia 11 the two properties can be used almost interchangeably but this will change
  205. /// in a later version. In order to be ready for this change, follow the following guidance:
  206. ///
  207. /// <list type="bullet">
  208. /// <item>You should use the <see cref="Items"/> property when you're assigning a collection of
  209. /// item containers directly, for example adding a collection of <see cref="ListBoxItem"/>s
  210. /// directly to a <see cref="ListBox"/>. Add the containers to the pre-existing list, do not
  211. /// reassign the <see cref="Items"/> property via the setter or with a binding.</item>
  212. /// <item>You should use the <see cref="ItemsSource"/> property when you're assigning or
  213. /// binding a collection of models which will be transformed by a data template.</item>
  214. /// </list>
  215. /// </remarks>
  216. public IEnumerable? ItemsSource
  217. {
  218. get => GetValue(ItemsSourceProperty);
  219. set => SetValue(ItemsSourceProperty, value);
  220. }
  221. /// <summary>
  222. /// Gets or sets the data template used to display the items in the control.
  223. /// </summary>
  224. [InheritDataTypeFromItems(nameof(ItemsSource))]
  225. [InheritDataTypeFromItems(nameof(Items))]
  226. public IDataTemplate? ItemTemplate
  227. {
  228. get => GetValue(ItemTemplateProperty);
  229. set => SetValue(ItemTemplateProperty, value);
  230. }
  231. /// <summary>
  232. /// Gets the items presenter control.
  233. /// </summary>
  234. public ItemsPresenter? Presenter { get; private set; }
  235. /// <summary>
  236. /// Gets the <see cref="Panel"/> specified by <see cref="ItemsPanel"/>.
  237. /// </summary>
  238. public Panel? ItemsPanelRoot => Presenter?.Panel;
  239. /// <summary>
  240. /// Gets a read-only view of the items in the <see cref="ItemsControl"/>.
  241. /// </summary>
  242. public ItemsSourceView ItemsView => _items;
  243. private protected bool WrapFocus { get; set; }
  244. event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged
  245. {
  246. add => _childIndexChanged += value;
  247. remove => _childIndexChanged -= value;
  248. }
  249. /// <inheritdoc />
  250. public event EventHandler<RoutedEventArgs> HorizontalSnapPointsChanged
  251. {
  252. add
  253. {
  254. if (_itemsPresenter != null)
  255. {
  256. _itemsPresenter.HorizontalSnapPointsChanged += value;
  257. }
  258. }
  259. remove
  260. {
  261. if (_itemsPresenter != null)
  262. {
  263. _itemsPresenter.HorizontalSnapPointsChanged -= value;
  264. }
  265. }
  266. }
  267. /// <inheritdoc />
  268. public event EventHandler<RoutedEventArgs> VerticalSnapPointsChanged
  269. {
  270. add
  271. {
  272. if (_itemsPresenter != null)
  273. {
  274. _itemsPresenter.VerticalSnapPointsChanged += value;
  275. }
  276. }
  277. remove
  278. {
  279. if (_itemsPresenter != null)
  280. {
  281. _itemsPresenter.VerticalSnapPointsChanged -= value;
  282. }
  283. }
  284. }
  285. /// <summary>
  286. /// Gets or sets whether the horizontal snap points for the <see cref="ItemsControl"/> are equidistant from each other.
  287. /// </summary>
  288. public bool AreHorizontalSnapPointsRegular
  289. {
  290. get => GetValue(AreHorizontalSnapPointsRegularProperty);
  291. set => SetValue(AreHorizontalSnapPointsRegularProperty, value);
  292. }
  293. /// <summary>
  294. /// Gets or sets whether the vertical snap points for the <see cref="ItemsControl"/> are equidistant from each other.
  295. /// </summary>
  296. public bool AreVerticalSnapPointsRegular
  297. {
  298. get => GetValue(AreVerticalSnapPointsRegularProperty);
  299. set => SetValue(AreVerticalSnapPointsRegularProperty, value);
  300. }
  301. /// <summary>
  302. /// Returns the container for the item at the specified index.
  303. /// </summary>
  304. /// <param name="index">The index of the item to retrieve.</param>
  305. /// <returns>
  306. /// The container for the item at the specified index within the item collection, if the
  307. /// item has a container; otherwise, null.
  308. /// </returns>
  309. public Control? ContainerFromIndex(int index) => Presenter?.ContainerFromIndex(index);
  310. /// <summary>
  311. /// Returns the container corresponding to the specified item.
  312. /// </summary>
  313. /// <param name="item">The item to retrieve the container for.</param>
  314. /// <returns>
  315. /// A container that corresponds to the specified item, if the item has a container and
  316. /// exists in the collection; otherwise, null.
  317. /// </returns>
  318. public Control? ContainerFromItem(object item)
  319. {
  320. var index = _items.IndexOf(item);
  321. return index >= 0 ? ContainerFromIndex(index) : null;
  322. }
  323. /// <summary>
  324. /// Returns the index to the item that has the specified, generated container.
  325. /// </summary>
  326. /// <param name="container">The generated container to retrieve the item index for.</param>
  327. /// <returns>
  328. /// The index to the item that corresponds to the specified generated container, or -1 if
  329. /// <paramref name="container"/> is not found.
  330. /// </returns>
  331. public int IndexFromContainer(Control container) => Presenter?.IndexFromContainer(container) ?? -1;
  332. /// <summary>
  333. /// Returns the item that corresponds to the specified, generated container.
  334. /// </summary>
  335. /// <param name="container">The control that corresponds to the item to be returned.</param>
  336. /// <returns>
  337. /// The contained item, or the container if it does not contain an item.
  338. /// </returns>
  339. public object? ItemFromContainer(Control container)
  340. {
  341. var index = IndexFromContainer(container);
  342. return index >= 0 && index < _items.Count ? _items[index] : null;
  343. }
  344. /// <summary>
  345. /// Gets the currently realized containers.
  346. /// </summary>
  347. public IEnumerable<Control> GetRealizedContainers() => Presenter?.GetRealizedContainers() ?? Array.Empty<Control>();
  348. /// <summary>
  349. /// Creates or a container that can be used to display an item.
  350. /// </summary>
  351. protected internal virtual Control CreateContainerForItemOverride() => new ContentPresenter();
  352. /// <summary>
  353. /// Prepares the specified element to display the specified item.
  354. /// </summary>
  355. /// <param name="container">The element that's used to display the specified item.</param>
  356. /// <param name="item">The item to display.</param>
  357. /// <param name="index">The index of the item to display.</param>
  358. protected internal virtual void PrepareContainerForItemOverride(Control container, object? item, int index)
  359. {
  360. if (container == item)
  361. return;
  362. var itemTemplate = GetEffectiveItemTemplate();
  363. if (container is HeaderedContentControl hcc)
  364. {
  365. hcc.Content = item;
  366. if (item is IHeadered headered)
  367. hcc.Header = headered.Header;
  368. else if (item is not Visual)
  369. hcc.Header = item;
  370. if (itemTemplate is not null)
  371. hcc.HeaderTemplate = itemTemplate;
  372. }
  373. else if (container is ContentControl cc)
  374. {
  375. cc.Content = item;
  376. if (itemTemplate is not null)
  377. cc.ContentTemplate = itemTemplate;
  378. }
  379. else if (container is ContentPresenter p)
  380. {
  381. p.Content = item;
  382. if (itemTemplate is not null)
  383. p.ContentTemplate = itemTemplate;
  384. }
  385. else if (container is ItemsControl ic)
  386. {
  387. if (itemTemplate is not null)
  388. ic.ItemTemplate = itemTemplate;
  389. if (ItemContainerTheme is { } ict && !ict.IsSet(ItemContainerThemeProperty))
  390. ic.ItemContainerTheme = ict;
  391. }
  392. // This condition is separate because HeaderedItemsControl needs to also run the
  393. // ItemsControl preparation.
  394. if (container is HeaderedItemsControl hic)
  395. {
  396. hic.Header = item;
  397. hic.HeaderTemplate = itemTemplate;
  398. hic.PrepareItemContainer();
  399. }
  400. }
  401. /// <summary>
  402. /// Called when the index for a container changes due to an insertion or removal in the
  403. /// items collection.
  404. /// </summary>
  405. /// <param name="container">The container whose index changed.</param>
  406. /// <param name="oldIndex">The old index.</param>
  407. /// <param name="newIndex">The new index.</param>
  408. protected virtual void ContainerIndexChangedOverride(Control container, int oldIndex, int newIndex)
  409. {
  410. }
  411. /// <summary>
  412. /// Undoes the effects of the <see cref="PrepareContainerForItemOverride(Control, object?, int)"/> method.
  413. /// </summary>
  414. /// <param name="container">The container element.</param>
  415. protected internal virtual void ClearContainerForItemOverride(Control container)
  416. {
  417. // Feels like we should be clearing the HeaderedItemsControl.Items binding here, but looking at
  418. // the WPF source it seems that this isn't done there.
  419. }
  420. /// <summary>
  421. /// Determines whether the specified item is (or is eligible to be) its own container.
  422. /// </summary>
  423. /// <param name="item">The item to check.</param>
  424. /// <returns>true if the item is (or is eligible to be) its own container; otherwise, false.</returns>
  425. protected internal virtual bool IsItemItsOwnContainerOverride(Control item) => true;
  426. /// <inheritdoc />
  427. protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
  428. {
  429. base.OnApplyTemplate(e);
  430. _scrollViewer = e.NameScope.Find<ScrollViewer>("PART_ScrollViewer");
  431. _itemsPresenter = e.NameScope.Find<ItemsPresenter>("PART_ItemsPresenter");
  432. }
  433. /// <summary>
  434. /// Handles directional navigation within the <see cref="ItemsControl"/>.
  435. /// </summary>
  436. /// <param name="e">The key events.</param>
  437. protected override void OnKeyDown(KeyEventArgs e)
  438. {
  439. if (!e.Handled)
  440. {
  441. var focus = FocusManager.Instance;
  442. var direction = e.Key.ToNavigationDirection();
  443. var container = Presenter?.Panel as INavigableContainer;
  444. if (container == null ||
  445. focus?.Current == null ||
  446. direction == null ||
  447. direction.Value.IsTab())
  448. {
  449. return;
  450. }
  451. Visual? current = focus.Current as Visual;
  452. while (current != null)
  453. {
  454. if (current.VisualParent == container && current is IInputElement inputElement)
  455. {
  456. var next = GetNextControl(container, direction.Value, inputElement, WrapFocus);
  457. if (next != null)
  458. {
  459. focus.Focus(next, NavigationMethod.Directional, e.KeyModifiers);
  460. e.Handled = true;
  461. }
  462. break;
  463. }
  464. current = current.VisualParent;
  465. }
  466. }
  467. base.OnKeyDown(e);
  468. }
  469. /// <inheritdoc />
  470. protected override AutomationPeer OnCreateAutomationPeer()
  471. {
  472. return new ItemsControlAutomationPeer(this);
  473. }
  474. /// <inheritdoc />
  475. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
  476. {
  477. base.OnPropertyChanged(change);
  478. if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null)
  479. {
  480. RefreshContainers();
  481. }
  482. else if (change.Property == ItemsSourceProperty)
  483. {
  484. _items.SetItemsSource(change.GetNewValue<IEnumerable?>());
  485. }
  486. else if (change.Property == ItemTemplateProperty)
  487. {
  488. if (change.NewValue is not null && DisplayMemberBinding is not null)
  489. throw new InvalidOperationException("Cannot set both DisplayMemberBinding and ItemTemplate.");
  490. RefreshContainers();
  491. }
  492. else if (change.Property == DisplayMemberBindingProperty)
  493. {
  494. if (change.NewValue is not null && ItemTemplate is not null)
  495. throw new InvalidOperationException("Cannot set both DisplayMemberBinding and ItemTemplate.");
  496. _displayMemberItemTemplate = null;
  497. RefreshContainers();
  498. }
  499. }
  500. /// <summary>
  501. /// Refreshes the containers displayed by the control.
  502. /// </summary>
  503. /// <remarks>
  504. /// Causes all containers to be unrealized and re-realized.
  505. /// </remarks>
  506. protected void RefreshContainers() => Presenter?.Refresh();
  507. /// <summary>
  508. /// Called when the <see cref="INotifyCollectionChanged.CollectionChanged"/> event is
  509. /// raised on <see cref="ItemsView"/>.
  510. /// </summary>
  511. /// <param name="sender">The event sender.</param>
  512. /// <param name="e">The event args.</param>
  513. private protected virtual void OnItemsViewCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
  514. {
  515. if (!_items.IsReadOnly)
  516. {
  517. switch (e.Action)
  518. {
  519. case NotifyCollectionChangedAction.Add:
  520. AddControlItemsToLogicalChildren(e.NewItems);
  521. break;
  522. case NotifyCollectionChangedAction.Remove:
  523. RemoveControlItemsFromLogicalChildren(e.OldItems);
  524. break;
  525. }
  526. }
  527. ItemCount = ItemsView.Count;
  528. }
  529. /// <summary>
  530. /// Creates the <see cref="ItemContainerGenerator"/>
  531. /// </summary>
  532. /// <remarks>
  533. /// This method is only present for backwards compatibility with 0.10.x in order for
  534. /// TreeView to be able to create a <see cref="TreeItemContainerGenerator"/>. Can be
  535. /// removed in 12.0.
  536. /// </remarks>
  537. [Obsolete]
  538. private protected virtual ItemContainerGenerator CreateItemContainerGenerator()
  539. {
  540. return new ItemContainerGenerator(this);
  541. }
  542. internal void AddLogicalChild(Control c)
  543. {
  544. if (!LogicalChildren.Contains(c))
  545. LogicalChildren.Add(c);
  546. }
  547. internal void RemoveLogicalChild(Control c) => LogicalChildren.Remove(c);
  548. /// <summary>
  549. /// Called by <see cref="ItemsPresenter"/> to register with the <see cref="ItemsControl"/>.
  550. /// </summary>
  551. /// <param name="presenter">The items presenter.</param>
  552. /// <remarks>
  553. /// ItemsPresenters can be within nested templates or in popups and so are not necessarily
  554. /// created immediately when the ItemsControl control's template is instantiated. Instead
  555. /// they register themselves using this method.
  556. /// </remarks>
  557. internal void RegisterItemsPresenter(ItemsPresenter presenter)
  558. {
  559. Presenter = presenter;
  560. _childIndexChanged?.Invoke(this, ChildIndexChangedEventArgs.ChildIndexesReset);
  561. }
  562. internal void PrepareItemContainer(Control container, object? item, int index)
  563. {
  564. var itemContainerTheme = ItemContainerTheme;
  565. if (itemContainerTheme is not null &&
  566. !container.IsSet(ThemeProperty) &&
  567. ((IStyleable)container).StyleKey == itemContainerTheme.TargetType)
  568. {
  569. container.Theme = itemContainerTheme;
  570. }
  571. if (item is not Control)
  572. container.DataContext = item;
  573. PrepareContainerForItemOverride(container, item, index);
  574. }
  575. internal void ItemContainerPrepared(Control container, object? item, int index)
  576. {
  577. _childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container, index));
  578. _scrollViewer?.RegisterAnchorCandidate(container);
  579. }
  580. internal void ItemContainerIndexChanged(Control container, int oldIndex, int newIndex)
  581. {
  582. ContainerIndexChangedOverride(container, oldIndex, newIndex);
  583. _childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container, newIndex));
  584. }
  585. internal void ClearItemContainer(Control container)
  586. {
  587. _scrollViewer?.UnregisterAnchorCandidate(container);
  588. ClearContainerForItemOverride(container);
  589. }
  590. private void AddControlItemsToLogicalChildren(IEnumerable? items)
  591. {
  592. if (items is null)
  593. return;
  594. List<ILogical>? toAdd = null;
  595. foreach (var i in items)
  596. {
  597. if (i is Control control && !LogicalChildren.Contains(control))
  598. {
  599. toAdd ??= new();
  600. toAdd.Add(control);
  601. }
  602. }
  603. if (toAdd is not null)
  604. LogicalChildren.AddRange(toAdd);
  605. }
  606. private void RemoveControlItemsFromLogicalChildren(IEnumerable? items)
  607. {
  608. if (items is null)
  609. return;
  610. List<ILogical>? toRemove = null;
  611. foreach (var i in items)
  612. {
  613. if (i is Control control)
  614. {
  615. toRemove ??= new();
  616. toRemove.Add(control);
  617. }
  618. }
  619. if (toRemove is not null)
  620. LogicalChildren.RemoveAll(toRemove);
  621. }
  622. private IDataTemplate? GetEffectiveItemTemplate()
  623. {
  624. if (ItemTemplate is { } itemTemplate)
  625. return itemTemplate;
  626. if (_displayMemberItemTemplate is null && DisplayMemberBinding is { } binding)
  627. {
  628. _displayMemberItemTemplate = new FuncDataTemplate<object?>((_, _) =>
  629. new TextBlock
  630. {
  631. [!TextBlock.TextProperty] = binding,
  632. });
  633. }
  634. return _displayMemberItemTemplate;
  635. }
  636. private void UpdatePseudoClasses()
  637. {
  638. PseudoClasses.Set(":empty", ItemCount == 0);
  639. PseudoClasses.Set(":singleitem", ItemCount == 1);
  640. }
  641. protected static IInputElement? GetNextControl(
  642. INavigableContainer container,
  643. NavigationDirection direction,
  644. IInputElement? from,
  645. bool wrap)
  646. {
  647. var current = from;
  648. for (;;)
  649. {
  650. var result = container.GetControl(direction, current, wrap);
  651. if (result is null)
  652. {
  653. return null;
  654. }
  655. if (result.Focusable &&
  656. result.IsEffectivelyEnabled &&
  657. result.IsEffectivelyVisible)
  658. {
  659. return result;
  660. }
  661. current = result;
  662. if (current == from)
  663. {
  664. return null;
  665. }
  666. switch (direction)
  667. {
  668. //We did not find an enabled first item. Move downwards until we find one.
  669. case NavigationDirection.First:
  670. direction = NavigationDirection.Down;
  671. from = result;
  672. break;
  673. //We did not find an enabled last item. Move upwards until we find one.
  674. case NavigationDirection.Last:
  675. direction = NavigationDirection.Up;
  676. from = result;
  677. break;
  678. }
  679. }
  680. }
  681. int IChildIndexProvider.GetChildIndex(ILogical child)
  682. {
  683. return child is Control container ? IndexFromContainer(container) : -1;
  684. }
  685. bool IChildIndexProvider.TryGetTotalCount(out int count)
  686. {
  687. count = ItemsView.Count;
  688. return true;
  689. }
  690. /// <inheritdoc />
  691. public IReadOnlyList<double> GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
  692. {
  693. return _itemsPresenter?.GetIrregularSnapPoints(orientation, snapPointsAlignment) ?? new List<double>();
  694. }
  695. /// <inheritdoc />
  696. public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset)
  697. {
  698. offset = 0;
  699. return _itemsPresenter?.GetRegularSnapPoints(orientation, snapPointsAlignment, out offset) ?? 0;
  700. }
  701. }
  702. }