ItemsControl.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. // Copyright (c) The Perspex 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.Collections;
  4. using System.Collections.Generic;
  5. using System.Collections.Specialized;
  6. using System.Diagnostics.CodeAnalysis;
  7. using System.Linq;
  8. using Perspex.Collections;
  9. using Perspex.Controls.Generators;
  10. using Perspex.Controls.Presenters;
  11. using Perspex.Controls.Primitives;
  12. using Perspex.Controls.Templates;
  13. using Perspex.Controls.Utils;
  14. using Perspex.Metadata;
  15. namespace Perspex.Controls
  16. {
  17. /// <summary>
  18. /// Displays a collection of items.
  19. /// </summary>
  20. public class ItemsControl : TemplatedControl, IItemsPresenterHost
  21. {
  22. /// <summary>
  23. /// The default value for the <see cref="ItemsPanel"/> property.
  24. /// </summary>
  25. [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "Needs to be before or a NullReferenceException is thrown.")]
  26. private static readonly FuncTemplate<IPanel> DefaultPanel =
  27. new FuncTemplate<IPanel>(() => new StackPanel());
  28. /// <summary>
  29. /// Defines the <see cref="Items"/> property.
  30. /// </summary>
  31. public static readonly DirectProperty<ItemsControl, IEnumerable> ItemsProperty =
  32. PerspexProperty.RegisterDirect<ItemsControl, IEnumerable>(nameof(Items), o => o.Items, (o, v) => o.Items = v);
  33. /// <summary>
  34. /// Defines the <see cref="ItemsPanel"/> property.
  35. /// </summary>
  36. public static readonly StyledProperty<ITemplate<IPanel>> ItemsPanelProperty =
  37. PerspexProperty.Register<ItemsControl, ITemplate<IPanel>>(nameof(ItemsPanel), DefaultPanel);
  38. /// <summary>
  39. /// Defines the <see cref="MemberSelector"/> property.
  40. /// </summary>
  41. public static readonly StyledProperty<IMemberSelector> MemberSelectorProperty =
  42. PerspexProperty.Register<ItemsControl, IMemberSelector>(nameof(MemberSelector));
  43. private IEnumerable _items = new PerspexList<object>();
  44. private IItemContainerGenerator _itemContainerGenerator;
  45. /// <summary>
  46. /// Initializes static members of the <see cref="ItemsControl"/> class.
  47. /// </summary>
  48. static ItemsControl()
  49. {
  50. ItemsProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemsChanged);
  51. }
  52. /// <summary>
  53. /// Initializes a new instance of the <see cref="ItemsControl"/> class.
  54. /// </summary>
  55. public ItemsControl()
  56. {
  57. PseudoClasses.Add(":empty");
  58. SubscribeToItems(_items);
  59. }
  60. /// <summary>
  61. /// Gets the <see cref="IItemContainerGenerator"/> for the control.
  62. /// </summary>
  63. public IItemContainerGenerator ItemContainerGenerator
  64. {
  65. get
  66. {
  67. if (_itemContainerGenerator == null)
  68. {
  69. _itemContainerGenerator = CreateItemContainerGenerator();
  70. if (_itemContainerGenerator != null)
  71. {
  72. _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e);
  73. _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e);
  74. }
  75. }
  76. return _itemContainerGenerator;
  77. }
  78. }
  79. /// <summary>
  80. /// Gets or sets the items to display.
  81. /// </summary>
  82. [Content]
  83. public IEnumerable Items
  84. {
  85. get { return _items; }
  86. set { SetAndRaise(ItemsProperty, ref _items, value); }
  87. }
  88. /// <summary>
  89. /// Gets or sets the panel used to display the items.
  90. /// </summary>
  91. public ITemplate<IPanel> ItemsPanel
  92. {
  93. get { return GetValue(ItemsPanelProperty); }
  94. set { SetValue(ItemsPanelProperty, value); }
  95. }
  96. /// <summary>
  97. /// Selects a member from <see cref="Items"/> to use as the list item.
  98. /// </summary>
  99. public IMemberSelector MemberSelector
  100. {
  101. get { return GetValue(MemberSelectorProperty); }
  102. set { SetValue(MemberSelectorProperty, value); }
  103. }
  104. /// <summary>
  105. /// Gets the items presenter control.
  106. /// </summary>
  107. public IItemsPresenter Presenter
  108. {
  109. get;
  110. protected set;
  111. }
  112. /// <inheritdoc/>
  113. void IItemsPresenterHost.RegisterItemsPresenter(IItemsPresenter presenter)
  114. {
  115. Presenter = presenter;
  116. }
  117. /// <summary>
  118. /// Gets the item at the specified index in a collection.
  119. /// </summary>
  120. /// <param name="items">The collection.</param>
  121. /// <param name="index">The index.</param>
  122. /// <returns>The index of the item or -1 if the item was not found.</returns>
  123. protected static object ElementAt(IEnumerable items, int index)
  124. {
  125. var typedItems = items?.Cast<object>();
  126. if (index != -1 && typedItems != null && index < typedItems.Count())
  127. {
  128. return typedItems.ElementAt(index) ?? null;
  129. }
  130. else
  131. {
  132. return null;
  133. }
  134. }
  135. /// <summary>
  136. /// Gets the index of an item in a collection.
  137. /// </summary>
  138. /// <param name="items">The collection.</param>
  139. /// <param name="item">The item.</param>
  140. /// <returns>The index of the item or -1 if the item was not found.</returns>
  141. protected static int IndexOf(IEnumerable items, object item)
  142. {
  143. if (items != null && item != null)
  144. {
  145. var list = items as IList;
  146. if (list != null)
  147. {
  148. return list.IndexOf(item);
  149. }
  150. else
  151. {
  152. int index = 0;
  153. foreach (var i in items)
  154. {
  155. if (Equals(i, item))
  156. {
  157. return index;
  158. }
  159. ++index;
  160. }
  161. }
  162. }
  163. return -1;
  164. }
  165. /// <summary>
  166. /// Creates the <see cref="ItemContainerGenerator"/> for the control.
  167. /// </summary>
  168. /// <returns>
  169. /// An <see cref="IItemContainerGenerator"/> or null.
  170. /// </returns>
  171. /// <remarks>
  172. /// Certain controls such as <see cref="TabControl"/> don't actually create item
  173. /// containers; however they want it to be ItemsControls so that they have an Items
  174. /// property etc. In this case, a derived class can override this method to return null
  175. /// in order to disable the creation of item containers.
  176. /// </remarks>
  177. protected virtual IItemContainerGenerator CreateItemContainerGenerator()
  178. {
  179. return new ItemContainerGenerator(this);
  180. }
  181. /// <summary>
  182. /// Called when new containers are materialized for the <see cref="ItemsControl"/> by its
  183. /// <see cref="ItemContainerGenerator"/>.
  184. /// </summary>
  185. /// <param name="e">The details of the containers.</param>
  186. protected virtual void OnContainersMaterialized(ItemContainerEventArgs e)
  187. {
  188. var toAdd = new List<ILogical>();
  189. foreach (var container in e.Containers)
  190. {
  191. // If the item is its own container, then it will be added to the logical tree when
  192. // it was added to the Items collection.
  193. if (container.ContainerControl != null && container.ContainerControl != container.Item)
  194. {
  195. toAdd.Add(container.ContainerControl);
  196. }
  197. }
  198. LogicalChildren.AddRange(toAdd);
  199. }
  200. /// <summary>
  201. /// Called when containers are dematerialized for the <see cref="ItemsControl"/> by its
  202. /// <see cref="ItemContainerGenerator"/>.
  203. /// </summary>
  204. /// <param name="e">The details of the containers.</param>
  205. protected virtual void OnContainersDematerialized(ItemContainerEventArgs e)
  206. {
  207. var toRemove = new List<ILogical>();
  208. foreach (var container in e.Containers)
  209. {
  210. // If the item is its own container, then it will be removed from the logical tree
  211. // when it is removed from the Items collection.
  212. if (container.ContainerControl != container.Item)
  213. {
  214. toRemove.Add(container.ContainerControl);
  215. }
  216. }
  217. LogicalChildren.RemoveAll(toRemove);
  218. }
  219. /// <inheritdoc/>
  220. protected override void OnTemplateChanged(PerspexPropertyChangedEventArgs e)
  221. {
  222. base.OnTemplateChanged(e);
  223. if (e.NewValue == null)
  224. {
  225. ItemContainerGenerator?.Clear();
  226. }
  227. }
  228. /// <summary>
  229. /// Caled when the <see cref="Items"/> property changes.
  230. /// </summary>
  231. /// <param name="e">The event args.</param>
  232. protected virtual void ItemsChanged(PerspexPropertyChangedEventArgs e)
  233. {
  234. var incc = e.OldValue as INotifyCollectionChanged;
  235. if (incc != null)
  236. {
  237. incc.CollectionChanged -= ItemsCollectionChanged;
  238. }
  239. var oldValue = e.OldValue as IEnumerable;
  240. var newValue = e.NewValue as IEnumerable;
  241. RemoveControlItemsFromLogicalChildren(oldValue);
  242. AddControlItemsToLogicalChildren(newValue);
  243. SubscribeToItems(newValue);
  244. }
  245. /// <summary>
  246. /// Called when the <see cref="INotifyCollectionChanged.CollectionChanged"/> event is
  247. /// raised on <see cref="Items"/>.
  248. /// </summary>
  249. /// <param name="sender">The event sender.</param>
  250. /// <param name="e">The event args.</param>
  251. protected virtual void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  252. {
  253. switch (e.Action)
  254. {
  255. case NotifyCollectionChangedAction.Add:
  256. AddControlItemsToLogicalChildren(e.NewItems);
  257. break;
  258. case NotifyCollectionChangedAction.Remove:
  259. RemoveControlItemsFromLogicalChildren(e.OldItems);
  260. break;
  261. }
  262. var collection = sender as ICollection;
  263. PseudoClasses.Set(":empty", collection.Count == 0);
  264. }
  265. /// <summary>
  266. /// Given a collection of items, adds those that are controls to the logical children.
  267. /// </summary>
  268. /// <param name="items">The items.</param>
  269. private void AddControlItemsToLogicalChildren(IEnumerable items)
  270. {
  271. var toAdd = new List<ILogical>();
  272. if (items != null)
  273. {
  274. foreach (var i in items)
  275. {
  276. var control = i as IControl;
  277. if (control != null && !LogicalChildren.Contains(control))
  278. {
  279. toAdd.Add(control);
  280. }
  281. }
  282. }
  283. LogicalChildren.AddRange(toAdd);
  284. }
  285. /// <summary>
  286. /// Given a collection of items, removes those that are controls to from logical children.
  287. /// </summary>
  288. /// <param name="items">The items.</param>
  289. private void RemoveControlItemsFromLogicalChildren(IEnumerable items)
  290. {
  291. var toRemove = new List<ILogical>();
  292. if (items != null)
  293. {
  294. foreach (var i in items)
  295. {
  296. var control = i as IControl;
  297. if (control != null)
  298. {
  299. toRemove.Add(control);
  300. }
  301. }
  302. }
  303. LogicalChildren.RemoveAll(toRemove);
  304. }
  305. /// <summary>
  306. /// Subscribes to an <see cref="Items"/> collection.
  307. /// </summary>
  308. /// <param name="items"></param>
  309. private void SubscribeToItems(IEnumerable items)
  310. {
  311. PseudoClasses.Set(":empty", items == null || items.Count() == 0);
  312. var incc = items as INotifyCollectionChanged;
  313. if (incc != null)
  314. {
  315. incc.CollectionChanged += ItemsCollectionChanged;
  316. }
  317. }
  318. }
  319. }