ItemsControlTests.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System.Collections.Specialized;
  4. using System.Linq;
  5. using Avalonia.Collections;
  6. using Avalonia.Controls.Presenters;
  7. using Avalonia.Controls.Templates;
  8. using Avalonia.LogicalTree;
  9. using Avalonia.VisualTree;
  10. using Xunit;
  11. using System.Collections.ObjectModel;
  12. using Avalonia.UnitTests;
  13. using Avalonia.Input;
  14. using System.Collections.Generic;
  15. namespace Avalonia.Controls.UnitTests
  16. {
  17. public class ItemsControlTests
  18. {
  19. [Fact]
  20. public void Should_Use_ItemTemplate_To_Create_Control()
  21. {
  22. var target = new ItemsControl
  23. {
  24. Template = GetTemplate(),
  25. ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
  26. };
  27. target.Items = new[] { "Foo" };
  28. target.ApplyTemplate();
  29. target.Presenter.ApplyTemplate();
  30. var container = (ContentPresenter)target.Presenter.Panel.Children[0];
  31. container.UpdateChild();
  32. Assert.IsType<Canvas>(container.Child);
  33. }
  34. [Fact]
  35. public void Panel_Should_Have_TemplatedParent_Set_To_ItemsControl()
  36. {
  37. var target = new ItemsControl();
  38. target.Template = GetTemplate();
  39. target.Items = new[] { "Foo" };
  40. target.ApplyTemplate();
  41. target.Presenter.ApplyTemplate();
  42. Assert.Equal(target, target.Presenter.Panel.TemplatedParent);
  43. }
  44. [Fact]
  45. public void Container_Should_Have_TemplatedParent_Set_To_Null()
  46. {
  47. var target = new ItemsControl();
  48. target.Template = GetTemplate();
  49. target.Items = new[] { "Foo" };
  50. target.ApplyTemplate();
  51. target.Presenter.ApplyTemplate();
  52. var container = (ContentPresenter)target.Presenter.Panel.Children[0];
  53. Assert.Null(container.TemplatedParent);
  54. }
  55. [Fact]
  56. public void Container_Should_Have_LogicalParent_Set_To_ItemsControl()
  57. {
  58. using (UnitTestApplication.Start(TestServices.StyledWindow))
  59. {
  60. var root = new Window();
  61. var target = new ItemsControl();
  62. root.Content = target;
  63. var templatedParent = new Button();
  64. target.SetValue(StyledElement.TemplatedParentProperty, templatedParent);
  65. target.Template = GetTemplate();
  66. target.Items = new[] { "Foo" };
  67. root.ApplyTemplate();
  68. target.ApplyTemplate();
  69. target.Presenter.ApplyTemplate();
  70. var container = (ContentPresenter)target.Presenter.Panel.Children[0];
  71. Assert.Equal(target, container.Parent);
  72. }
  73. }
  74. [Fact]
  75. public void Control_Item_Should_Be_Logical_Child_Before_ApplyTemplate()
  76. {
  77. var target = new ItemsControl();
  78. var child = new Control();
  79. target.Template = GetTemplate();
  80. target.Items = new[] { child };
  81. Assert.Equal(child.Parent, target);
  82. Assert.Equal(child.GetLogicalParent(), target);
  83. Assert.Equal(new[] { child }, target.GetLogicalChildren());
  84. }
  85. [Fact]
  86. public void Added_Container_Should_Have_LogicalParent_Set_To_ItemsControl()
  87. {
  88. var item = new Border();
  89. var items = new ObservableCollection<Border>();
  90. var target = new ItemsControl
  91. {
  92. Template = GetTemplate(),
  93. Items = items,
  94. };
  95. var root = new TestRoot(true, target);
  96. root.Measure(new Size(100, 100));
  97. root.Arrange(new Rect(0, 0, 100, 100));
  98. items.Add(item);
  99. Assert.Equal(target, item.Parent);
  100. }
  101. [Fact]
  102. public void Control_Item_Should_Be_Removed_From_Logical_Children_Before_ApplyTemplate()
  103. {
  104. var target = new ItemsControl();
  105. var child = new Control();
  106. var items = new AvaloniaList<Control>(child);
  107. target.Template = GetTemplate();
  108. target.Items = items;
  109. items.RemoveAt(0);
  110. Assert.Null(child.Parent);
  111. Assert.Null(child.GetLogicalParent());
  112. Assert.Empty(target.GetLogicalChildren());
  113. }
  114. [Fact]
  115. public void Clearing_Items_Should_Clear_Child_Controls_Parent_Before_ApplyTemplate()
  116. {
  117. var target = new ItemsControl();
  118. var child = new Control();
  119. target.Template = GetTemplate();
  120. target.Items = new[] { child };
  121. target.Items = null;
  122. Assert.Null(child.Parent);
  123. Assert.Null(((ILogical)child).LogicalParent);
  124. }
  125. [Fact]
  126. public void Clearing_Items_Should_Clear_Child_Controls_Parent()
  127. {
  128. var target = new ItemsControl();
  129. var child = new Control();
  130. target.Template = GetTemplate();
  131. target.Items = new[] { child };
  132. target.ApplyTemplate();
  133. target.Items = null;
  134. Assert.Null(child.Parent);
  135. Assert.Null(((ILogical)child).LogicalParent);
  136. }
  137. [Fact]
  138. public void Adding_Control_Item_Should_Make_Control_Appear_In_LogicalChildren()
  139. {
  140. var target = new ItemsControl();
  141. var child = new Control();
  142. target.Template = GetTemplate();
  143. target.Items = new[] { child };
  144. // Should appear both before and after applying template.
  145. Assert.Equal(new ILogical[] { child }, target.GetLogicalChildren());
  146. target.ApplyTemplate();
  147. Assert.Equal(new ILogical[] { child }, target.GetLogicalChildren());
  148. }
  149. [Fact]
  150. public void Adding_String_Item_Should_Make_ContentPresenter_Appear_In_LogicalChildren()
  151. {
  152. var target = new ItemsControl();
  153. var child = new Control();
  154. target.Template = GetTemplate();
  155. target.Items = new[] { "Foo" };
  156. target.ApplyTemplate();
  157. target.Presenter.ApplyTemplate();
  158. var logical = (ILogical)target;
  159. Assert.Equal(1, logical.LogicalChildren.Count);
  160. Assert.IsType<ContentPresenter>(logical.LogicalChildren[0]);
  161. }
  162. [Fact]
  163. public void Setting_Items_To_Null_Should_Remove_LogicalChildren()
  164. {
  165. var target = new ItemsControl();
  166. var child = new Control();
  167. target.Template = GetTemplate();
  168. target.Items = new[] { "Foo" };
  169. target.ApplyTemplate();
  170. target.Presenter.ApplyTemplate();
  171. Assert.NotEmpty(target.GetLogicalChildren());
  172. target.Items = null;
  173. Assert.Equal(new ILogical[0], target.GetLogicalChildren());
  174. }
  175. [Fact]
  176. public void Setting_Items_Should_Fire_LogicalChildren_CollectionChanged()
  177. {
  178. var target = new ItemsControl();
  179. var child = new Control();
  180. var called = false;
  181. target.Template = GetTemplate();
  182. target.ApplyTemplate();
  183. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
  184. called = e.Action == NotifyCollectionChangedAction.Add;
  185. target.Items = new[] { child };
  186. Assert.True(called);
  187. }
  188. [Fact]
  189. public void Setting_Items_To_Null_Should_Fire_LogicalChildren_CollectionChanged()
  190. {
  191. var target = new ItemsControl();
  192. var child = new Control();
  193. var called = false;
  194. target.Template = GetTemplate();
  195. target.Items = new[] { child };
  196. target.ApplyTemplate();
  197. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
  198. called = e.Action == NotifyCollectionChangedAction.Remove;
  199. target.Items = null;
  200. Assert.True(called);
  201. }
  202. [Fact]
  203. public void Changing_Items_Should_Fire_LogicalChildren_CollectionChanged()
  204. {
  205. var target = new ItemsControl();
  206. var child = new Control();
  207. var called = false;
  208. target.Template = GetTemplate();
  209. target.Items = new[] { child };
  210. target.ApplyTemplate();
  211. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true;
  212. target.Items = new[] { "Foo" };
  213. Assert.True(called);
  214. }
  215. [Fact]
  216. public void Adding_Items_Should_Fire_LogicalChildren_CollectionChanged()
  217. {
  218. var target = new ItemsControl();
  219. var items = new AvaloniaList<string> { "Foo" };
  220. var called = false;
  221. target.Template = GetTemplate();
  222. target.Items = items;
  223. target.ApplyTemplate();
  224. target.Presenter.ApplyTemplate();
  225. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
  226. called = e.Action == NotifyCollectionChangedAction.Add;
  227. items.Add("Bar");
  228. Assert.True(called);
  229. }
  230. [Fact]
  231. public void Removing_Items_Should_Fire_LogicalChildren_CollectionChanged()
  232. {
  233. var target = new ItemsControl();
  234. var items = new AvaloniaList<string> { "Foo", "Bar" };
  235. var called = false;
  236. target.Template = GetTemplate();
  237. target.Items = items;
  238. target.ApplyTemplate();
  239. target.Presenter.ApplyTemplate();
  240. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
  241. called = e.Action == NotifyCollectionChangedAction.Remove;
  242. items.Remove("Bar");
  243. Assert.True(called);
  244. }
  245. [Fact]
  246. public void LogicalChildren_Should_Not_Change_Instance_When_Template_Changed()
  247. {
  248. var target = new ItemsControl()
  249. {
  250. Template = GetTemplate(),
  251. };
  252. var before = ((ILogical)target).LogicalChildren;
  253. target.Template = null;
  254. target.Template = GetTemplate();
  255. var after = ((ILogical)target).LogicalChildren;
  256. Assert.NotNull(before);
  257. Assert.NotNull(after);
  258. Assert.Same(before, after);
  259. }
  260. [Fact]
  261. public void Should_Clear_Containers_When_ItemsPresenter_Changes()
  262. {
  263. var target = new ItemsControl
  264. {
  265. Items = new[] { "foo", "bar" },
  266. Template = GetTemplate(),
  267. };
  268. target.ApplyTemplate();
  269. target.Presenter.ApplyTemplate();
  270. Assert.Equal(2, target.ItemContainerGenerator.Containers.Count());
  271. target.Template = GetTemplate();
  272. target.ApplyTemplate();
  273. Assert.Empty(target.ItemContainerGenerator.Containers);
  274. }
  275. [Fact]
  276. public void Empty_Class_Should_Initially_Be_Applied()
  277. {
  278. var target = new ItemsControl()
  279. {
  280. Template = GetTemplate(),
  281. };
  282. Assert.Contains(":empty", target.Classes);
  283. }
  284. [Fact]
  285. public void Empty_Class_Should_Be_Cleared_When_Items_Added()
  286. {
  287. var target = new ItemsControl()
  288. {
  289. Template = GetTemplate(),
  290. Items = new[] { 1, 2, 3 },
  291. };
  292. Assert.DoesNotContain(":empty", target.Classes);
  293. }
  294. [Fact]
  295. public void Empty_Class_Should_Be_Set_When_Empty_Collection_Set()
  296. {
  297. var target = new ItemsControl()
  298. {
  299. Template = GetTemplate(),
  300. Items = new[] { 1, 2, 3 },
  301. };
  302. target.Items = new int[0];
  303. Assert.Contains(":empty", target.Classes);
  304. }
  305. [Fact]
  306. public void Setting_Presenter_Explicitly_Should_Set_Item_Parent()
  307. {
  308. var target = new TestItemsControl();
  309. var child = new Control();
  310. var presenter = new ItemsPresenter
  311. {
  312. [StyledElement.TemplatedParentProperty] = target,
  313. [~ItemsPresenter.ItemsProperty] = target[~ItemsControl.ItemsProperty],
  314. };
  315. presenter.ApplyTemplate();
  316. target.Presenter = presenter;
  317. target.Items = new[] { child };
  318. target.ApplyTemplate();
  319. Assert.Equal(target, child.Parent);
  320. Assert.Equal(target, ((ILogical)child).LogicalParent);
  321. }
  322. [Fact]
  323. public void DataContexts_Should_Be_Correctly_Set()
  324. {
  325. var items = new object[]
  326. {
  327. "Foo",
  328. new Item("Bar"),
  329. new TextBlock { Text = "Baz" },
  330. new ListBoxItem { Content = "Qux" },
  331. };
  332. var target = new ItemsControl
  333. {
  334. Template = GetTemplate(),
  335. DataContext = "Base",
  336. DataTemplates =
  337. {
  338. new FuncDataTemplate<Item>((x, __) => new Button { Content = x })
  339. },
  340. Items = items,
  341. };
  342. target.ApplyTemplate();
  343. target.Presenter.ApplyTemplate();
  344. var dataContexts = target.Presenter.Panel.Children
  345. .Do(x => (x as ContentPresenter)?.UpdateChild())
  346. .Cast<Control>()
  347. .Select(x => x.DataContext)
  348. .ToList();
  349. Assert.Equal(
  350. new object[] { items[0], items[1], "Base", "Base" },
  351. dataContexts);
  352. }
  353. [Fact]
  354. public void Control_Item_Should_Not_Be_NameScope()
  355. {
  356. var items = new object[]
  357. {
  358. new TextBlock(),
  359. };
  360. var target = new ItemsControl
  361. {
  362. Template = GetTemplate(),
  363. Items = items,
  364. };
  365. target.ApplyTemplate();
  366. target.Presenter.ApplyTemplate();
  367. var item = target.Presenter.Panel.LogicalChildren[0];
  368. Assert.Null(NameScope.GetNameScope((TextBlock)item));
  369. }
  370. [Fact]
  371. public void Focuses_Next_Item_On_Key_Down()
  372. {
  373. using (UnitTestApplication.Start(TestServices.RealFocus))
  374. {
  375. var items = new object[]
  376. {
  377. new Button(),
  378. new Button(),
  379. };
  380. var target = new ItemsControl
  381. {
  382. Template = GetTemplate(),
  383. Items = items,
  384. };
  385. var root = new TestRoot { Child = target };
  386. target.ApplyTemplate();
  387. target.Presenter.ApplyTemplate();
  388. target.Presenter.Panel.Children[0].Focus();
  389. target.RaiseEvent(new KeyEventArgs
  390. {
  391. RoutedEvent = InputElement.KeyDownEvent,
  392. Key = Key.Down,
  393. });
  394. Assert.Equal(
  395. target.Presenter.Panel.Children[1],
  396. FocusManager.Instance.Current);
  397. }
  398. }
  399. [Fact]
  400. public void Does_Not_Focus_Non_Focusable_Item_On_Key_Down()
  401. {
  402. using (UnitTestApplication.Start(TestServices.RealFocus))
  403. {
  404. var items = new object[]
  405. {
  406. new Button(),
  407. new Button { Focusable = false },
  408. new Button(),
  409. };
  410. var target = new ItemsControl
  411. {
  412. Template = GetTemplate(),
  413. Items = items,
  414. };
  415. var root = new TestRoot { Child = target };
  416. target.ApplyTemplate();
  417. target.Presenter.ApplyTemplate();
  418. target.Presenter.Panel.Children[0].Focus();
  419. target.RaiseEvent(new KeyEventArgs
  420. {
  421. RoutedEvent = InputElement.KeyDownEvent,
  422. Key = Key.Down,
  423. });
  424. Assert.Equal(
  425. target.Presenter.Panel.Children[2],
  426. FocusManager.Instance.Current);
  427. }
  428. }
  429. [Fact]
  430. public void Presenter_Items_Should_Be_In_Sync()
  431. {
  432. var target = new ItemsControl
  433. {
  434. Template = GetTemplate(),
  435. Items = new object[]
  436. {
  437. new Button(),
  438. new Button(),
  439. },
  440. };
  441. var root = new TestRoot { Child = target };
  442. var otherPanel = new StackPanel();
  443. target.ApplyTemplate();
  444. target.Presenter.ApplyTemplate();
  445. target.ItemContainerGenerator.Materialized += (s, e) =>
  446. {
  447. Assert.IsType<Canvas>(e.Containers[0].Item);
  448. };
  449. target.Items = new[]
  450. {
  451. new Canvas()
  452. };
  453. }
  454. [Fact]
  455. public void Detaching_Then_Reattaching_To_Logical_Tree_Twice_Does_Not_Throw()
  456. {
  457. // # Issue 3487
  458. var target = new ItemsControl
  459. {
  460. Template = GetTemplate(),
  461. Items = new[] { "foo", "bar" },
  462. ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
  463. };
  464. var root = new TestRoot(target);
  465. root.Measure(Size.Infinity);
  466. root.Arrange(new Rect(root.DesiredSize));
  467. root.Child = null;
  468. root.Child = target;
  469. target.Measure(Size.Infinity);
  470. root.Child = null;
  471. root.Child = target;
  472. }
  473. private class Item
  474. {
  475. public Item(string value)
  476. {
  477. Value = value;
  478. }
  479. public string Value { get; }
  480. }
  481. private FuncControlTemplate GetTemplate()
  482. {
  483. return new FuncControlTemplate<ItemsControl>((parent, scope) =>
  484. {
  485. return new Border
  486. {
  487. Background = new Media.SolidColorBrush(0xffffffff),
  488. Child = new ItemsPresenter
  489. {
  490. Name = "PART_ItemsPresenter",
  491. [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
  492. }.RegisterInNameScope(scope)
  493. };
  494. });
  495. }
  496. private class TestItemsControl : ItemsControl
  497. {
  498. public new IItemsPresenter Presenter
  499. {
  500. get { return base.Presenter; }
  501. set { base.Presenter = value; }
  502. }
  503. }
  504. }
  505. }