ListBoxTests.cs 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Collections.Specialized;
  5. using System.Linq;
  6. using System.Reactive.Subjects;
  7. using Avalonia.Collections;
  8. using Avalonia.Controls.Presenters;
  9. using Avalonia.Controls.Primitives;
  10. using Avalonia.Controls.Templates;
  11. using Avalonia.Data;
  12. using Avalonia.Input;
  13. using Avalonia.Layout;
  14. using Avalonia.LogicalTree;
  15. using Avalonia.Styling;
  16. using Avalonia.UnitTests;
  17. using Avalonia.VisualTree;
  18. using Xunit;
  19. namespace Avalonia.Controls.UnitTests
  20. {
  21. public class ListBoxTests
  22. {
  23. private MouseTestHelper _mouse = new MouseTestHelper();
  24. [Fact]
  25. public void Should_Use_ItemTemplate_To_Create_Item_Content()
  26. {
  27. var target = new ListBox
  28. {
  29. Template = ListBoxTemplate(),
  30. ItemsSource = new[] { "Foo" },
  31. ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
  32. };
  33. Prepare(target);
  34. var container = (ListBoxItem)target.Presenter.Panel.Children[0];
  35. Assert.IsType<Canvas>(container.Presenter.Child);
  36. }
  37. [Fact]
  38. public void ListBox_Should_Find_ItemsPresenter_In_ScrollViewer()
  39. {
  40. var target = new ListBox
  41. {
  42. Template = ListBoxTemplate(),
  43. };
  44. Prepare(target);
  45. Assert.IsType<ItemsPresenter>(target.Presenter);
  46. }
  47. [Fact]
  48. public void ListBox_Should_Find_Scrollviewer_In_Template()
  49. {
  50. var target = new ListBox
  51. {
  52. Template = ListBoxTemplate(),
  53. };
  54. ScrollViewer viewer = null;
  55. target.TemplateApplied += (sender, e) =>
  56. {
  57. viewer = target.Scroll as ScrollViewer;
  58. };
  59. Prepare(target);
  60. Assert.NotNull(viewer);
  61. }
  62. [Fact]
  63. public void ListBoxItem_Containers_Should_Be_Generated()
  64. {
  65. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  66. {
  67. var items = new[] { "Foo", "Bar", "Baz " };
  68. var target = new ListBox
  69. {
  70. Template = ListBoxTemplate(),
  71. ItemsSource = items,
  72. };
  73. Prepare(target);
  74. var text = target.Presenter.Panel.Children
  75. .OfType<ListBoxItem>()
  76. .Select(x => x.Presenter.Child)
  77. .OfType<TextBlock>()
  78. .Select(x => x.Text)
  79. .ToList();
  80. Assert.Equal(items, text);
  81. }
  82. }
  83. [Fact]
  84. public void Container_Should_Have_Theme_Set_To_ItemContainerTheme()
  85. {
  86. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  87. {
  88. var items = new[] { "Foo", "Bar", "Baz " };
  89. var theme = new ControlTheme(typeof(ListBoxItem));
  90. var target = new ListBox
  91. {
  92. Template = ListBoxTemplate(),
  93. ItemsSource = items,
  94. ItemContainerTheme = theme,
  95. };
  96. Prepare(target);
  97. var container = (ListBoxItem)target.Presenter.Panel.Children[0];
  98. Assert.Same(container.Theme, theme);
  99. }
  100. }
  101. [Fact]
  102. public void Inline_Item_Should_Have_Theme_Set_To_ItemContainerTheme()
  103. {
  104. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  105. {
  106. var theme = new ControlTheme(typeof(ListBoxItem));
  107. var target = new ListBox
  108. {
  109. Template = ListBoxTemplate(),
  110. Items = { new ListBoxItem() },
  111. ItemContainerTheme = theme,
  112. };
  113. Prepare(target);
  114. var container = (ListBoxItem)target.Presenter.Panel.Children[0];
  115. Assert.Same(container.Theme, theme);
  116. }
  117. }
  118. [Fact]
  119. public void LogicalChildren_Should_Be_Set_For_DataTemplate_Generated_Items()
  120. {
  121. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  122. {
  123. var target = new ListBox
  124. {
  125. Template = ListBoxTemplate(),
  126. ItemsSource = new[] { "Foo", "Bar", "Baz " },
  127. };
  128. Prepare(target);
  129. Assert.Equal(3, target.GetLogicalChildren().Count());
  130. foreach (var child in target.GetLogicalChildren())
  131. {
  132. Assert.IsType<ListBoxItem>(child);
  133. }
  134. }
  135. }
  136. [Fact]
  137. public void DataContexts_Should_Be_Correctly_Set()
  138. {
  139. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  140. {
  141. var items = new object[]
  142. {
  143. "Foo",
  144. new Item("Bar"),
  145. new TextBlock { Text = "Baz" },
  146. new ListBoxItem { Content = "Qux" },
  147. };
  148. var target = new ListBox
  149. {
  150. Template = ListBoxTemplate(),
  151. DataContext = "Base",
  152. DataTemplates =
  153. {
  154. new FuncDataTemplate<Item>((x, _) => new Button { Content = x })
  155. },
  156. ItemsSource = items,
  157. };
  158. Prepare(target);
  159. var dataContexts = target.Presenter.Panel.Children
  160. .Cast<Control>()
  161. .Select(x => x.DataContext)
  162. .ToList();
  163. Assert.Equal(
  164. new object[] { items[0], items[1], "Base", "Base" },
  165. dataContexts);
  166. }
  167. }
  168. [Fact]
  169. public void Selection_Should_Be_Cleared_On_Recycled_Items()
  170. {
  171. using (UnitTestApplication.Start(TestServices.StyledWindow))
  172. {
  173. var target = new ListBox
  174. {
  175. Template = ListBoxTemplate(),
  176. ItemsSource = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
  177. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
  178. SelectedIndex = 0,
  179. };
  180. Prepare(target);
  181. // Make sure we're virtualized and first item is selected.
  182. Assert.Equal(10, target.Presenter.Panel.Children.Count);
  183. Assert.True(((ListBoxItem)target.Presenter.Panel.Children[0]).IsSelected);
  184. // Scroll down a page.
  185. target.Scroll.Offset = new Vector(0, 10);
  186. Layout(target);
  187. // Make sure recycled item isn't now selected.
  188. Assert.False(((ListBoxItem)target.Presenter.Panel.Children[0]).IsSelected);
  189. }
  190. }
  191. [Fact]
  192. public void ScrollViewer_Should_Have_Correct_Extent_And_Viewport()
  193. {
  194. using (UnitTestApplication.Start(TestServices.StyledWindow))
  195. {
  196. var target = new ListBox
  197. {
  198. Template = ListBoxTemplate(),
  199. ItemsSource = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
  200. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
  201. SelectedIndex = 0,
  202. };
  203. Prepare(target);
  204. Assert.Equal(new Size(100, 200), target.Scroll.Extent);
  205. Assert.Equal(new Size(100, 100), target.Scroll.Viewport);
  206. }
  207. }
  208. [Fact]
  209. public void Containers_Correct_After_Clear_Add_Remove()
  210. {
  211. using (UnitTestApplication.Start(TestServices.StyledWindow))
  212. {
  213. // Issue #1936
  214. var items = new AvaloniaList<string>(Enumerable.Range(0, 11).Select(x => $"Item {x}"));
  215. var target = new ListBox
  216. {
  217. Template = ListBoxTemplate(),
  218. ItemsSource = items,
  219. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
  220. SelectedIndex = 0,
  221. };
  222. Prepare(target);
  223. items.Clear();
  224. items.AddRange(Enumerable.Range(0, 11).Select(x => $"Item {x}"));
  225. Layout(target);
  226. items.Remove("Item 2");
  227. Layout(target);
  228. var actual = target.GetRealizedContainers().Cast<ListBoxItem>().Select(x => (string)x.Content).ToList();
  229. Assert.Equal(items.OrderBy(x => x), actual.OrderBy(x => x));
  230. }
  231. }
  232. [Fact]
  233. public void Toggle_Selection_Should_Update_Containers()
  234. {
  235. using (UnitTestApplication.Start(TestServices.StyledWindow))
  236. {
  237. var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray();
  238. var target = new ListBox
  239. {
  240. Template = ListBoxTemplate(),
  241. ItemsSource = items,
  242. SelectionMode = SelectionMode.Toggle,
  243. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 })
  244. };
  245. Prepare(target);
  246. var lbItems = target.GetLogicalChildren().OfType<ListBoxItem>().ToArray();
  247. var item = lbItems[0];
  248. Assert.Equal(false, item.IsSelected);
  249. RaisePressedEvent(target, item, MouseButton.Left);
  250. Assert.Equal(true, item.IsSelected);
  251. RaisePressedEvent(target, item, MouseButton.Left);
  252. Assert.Equal(false, item.IsSelected);
  253. }
  254. }
  255. [Fact]
  256. public void Can_Decrease_Number_Of_Materialized_Items_By_Removing_From_Source_Collection()
  257. {
  258. using (UnitTestApplication.Start(TestServices.StyledWindow))
  259. {
  260. var items = new AvaloniaList<string>(Enumerable.Range(0, 20).Select(x => $"Item {x}"));
  261. var target = new ListBox
  262. {
  263. Template = ListBoxTemplate(),
  264. ItemsSource = items,
  265. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 })
  266. };
  267. Prepare(target);
  268. target.Scroll.Offset = new Vector(0, 1);
  269. items.RemoveRange(0, 11);
  270. }
  271. }
  272. private void RaisePressedEvent(ListBox listBox, ListBoxItem item, MouseButton mouseButton)
  273. {
  274. _mouse.Click(listBox, item, mouseButton);
  275. }
  276. [Fact]
  277. public void LayoutManager_Should_Measure_Arrange_All()
  278. {
  279. using (UnitTestApplication.Start(TestServices.StyledWindow))
  280. {
  281. var items = new AvaloniaList<string>(Enumerable.Range(1, 7).Select(v => v.ToString()));
  282. var wnd = new Window() { SizeToContent = SizeToContent.WidthAndHeight };
  283. wnd.IsVisible = true;
  284. var target = new ListBox();
  285. wnd.Content = target;
  286. var lm = wnd.LayoutManager;
  287. target.Height = 110;
  288. target.Width = 50;
  289. target.DataContext = items;
  290. target.ItemTemplate = new FuncDataTemplate<object>((c, _) =>
  291. {
  292. var tb = new TextBlock() { Height = 10, Width = 30 };
  293. tb.Bind(TextBlock.TextProperty, new Data.Binding());
  294. return tb;
  295. }, true);
  296. lm.ExecuteInitialLayoutPass();
  297. target.ItemsSource = items;
  298. lm.ExecuteLayoutPass();
  299. items.Insert(3, "3+");
  300. lm.ExecuteLayoutPass();
  301. items.Insert(4, "4+");
  302. lm.ExecuteLayoutPass();
  303. //RESET
  304. items.Clear();
  305. foreach (var i in Enumerable.Range(1, 7))
  306. {
  307. items.Add(i.ToString());
  308. }
  309. //working bit better with this line no outof memory or remaining to arrange/measure ???
  310. //lm.ExecuteLayoutPass();
  311. items.Insert(2, "2+");
  312. lm.ExecuteLayoutPass();
  313. //after few more layout cycles layoutmanager shouldn't hold any more visual for measure/arrange
  314. lm.ExecuteLayoutPass();
  315. lm.ExecuteLayoutPass();
  316. var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
  317. var toMeasure = lm.GetType().GetField("_toMeasure", flags).GetValue(lm) as System.Collections.Generic.IEnumerable<Layout.Layoutable>;
  318. var toArrange = lm.GetType().GetField("_toArrange", flags).GetValue(lm) as System.Collections.Generic.IEnumerable<Layout.Layoutable>;
  319. Assert.Equal(0, toMeasure.Count());
  320. Assert.Equal(0, toArrange.Count());
  321. }
  322. }
  323. [Fact]
  324. public void ListBox_Should_Be_Valid_After_Remove_Of_Item_In_NonVisibleArea()
  325. {
  326. using (UnitTestApplication.Start(TestServices.StyledWindow))
  327. {
  328. var items = new AvaloniaList<string>(Enumerable.Range(1, 30).Select(v => v.ToString()));
  329. var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
  330. var target = new ListBox()
  331. {
  332. AutoScrollToSelectedItem = true,
  333. Height = 100,
  334. Width = 50,
  335. ItemTemplate = new FuncDataTemplate<object>((c, _) => new Border() { Height = 10 }),
  336. ItemsSource = items,
  337. };
  338. wnd.Content = target;
  339. var lm = wnd.LayoutManager;
  340. lm.ExecuteInitialLayoutPass();
  341. //select last / scroll to last item
  342. target.SelectedItem = items.Last();
  343. lm.ExecuteLayoutPass();
  344. //remove the first item (in non realized area of the listbox)
  345. items.Remove("1");
  346. lm.ExecuteLayoutPass();
  347. Assert.Equal("30", target.ContainerFromIndex(items.Count - 1).DataContext);
  348. Assert.Equal("29", target.ContainerFromIndex(items.Count - 2).DataContext);
  349. Assert.Equal("28", target.ContainerFromIndex(items.Count - 3).DataContext);
  350. Assert.Equal("27", target.ContainerFromIndex(items.Count - 4).DataContext);
  351. Assert.Equal("26", target.ContainerFromIndex(items.Count - 5).DataContext);
  352. }
  353. }
  354. [Fact]
  355. public void Clicking_Item_Should_Raise_BringIntoView_For_Correct_Control()
  356. {
  357. using (UnitTestApplication.Start(TestServices.StyledWindow))
  358. {
  359. // Issue #3934
  360. var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray();
  361. var target = new ListBox
  362. {
  363. Template = ListBoxTemplate(),
  364. ItemsSource = items,
  365. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
  366. SelectionMode = SelectionMode.AlwaysSelected,
  367. };
  368. Prepare(target);
  369. // First an item that is not index 0 must be selected.
  370. _mouse.Click(target.Presenter.Panel.Children[1]);
  371. Assert.Equal(1, target.Selection.AnchorIndex);
  372. // We're going to be clicking on item 9.
  373. var item = (ListBoxItem)target.Presenter.Panel.Children[9];
  374. var raised = 0;
  375. // Make sure a RequestBringIntoView event is raised for item 9. It won't be handled
  376. // by the ScrollContentPresenter as the item is already visible, so we don't need
  377. // handledEventsToo: true. Issue #3934 failed here because item 0 was being scrolled
  378. // into view due to SelectionMode.AlwaysSelected.
  379. target.AddHandler(Control.RequestBringIntoViewEvent, (s, e) =>
  380. {
  381. Assert.Same(item, e.TargetObject);
  382. ++raised;
  383. });
  384. // Click item 9.
  385. _mouse.Click(item);
  386. Assert.Equal(1, raised);
  387. }
  388. }
  389. [Fact]
  390. public void Adding_And_Selecting_Item_With_AutoScrollToSelectedItem_Should_NotHide_FirstItem()
  391. {
  392. using (UnitTestApplication.Start(TestServices.StyledWindow))
  393. {
  394. var items = new AvaloniaList<string>();
  395. var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
  396. var target = new ListBox()
  397. {
  398. VerticalAlignment = VerticalAlignment.Top,
  399. AutoScrollToSelectedItem = true,
  400. Width = 50,
  401. ItemTemplate = new FuncDataTemplate<object>((c, _) => new Border() { Height = 10 }),
  402. ItemsSource = items,
  403. };
  404. wnd.Content = target;
  405. var lm = wnd.LayoutManager;
  406. lm.ExecuteInitialLayoutPass();
  407. var panel = target.Presenter.Panel;
  408. items.Add("Item 1");
  409. target.Selection.Select(0);
  410. lm.ExecuteLayoutPass();
  411. Assert.Equal(1, panel.Children.Count);
  412. items.Add("Item 2");
  413. target.Selection.Select(1);
  414. lm.ExecuteLayoutPass();
  415. Assert.Equal(2, panel.Children.Count);
  416. //make sure we have enough space to show all items
  417. Assert.True(panel.Bounds.Height >= panel.Children.Sum(c => c.Bounds.Height));
  418. //make sure we show items and they completelly visible, not only partially
  419. Assert.True(panel.Children[0].Bounds.Top >= 0 && panel.Children[0].Bounds.Bottom <= panel.Bounds.Height, "first item is not completelly visible!");
  420. Assert.True(panel.Children[1].Bounds.Top >= 0 && panel.Children[1].Bounds.Bottom <= panel.Bounds.Height, "second item is not completelly visible!");
  421. }
  422. }
  423. [Fact]
  424. public void Initial_Binding_Of_SelectedItems_Should_Not_Cause_Write_To_SelectedItems()
  425. {
  426. var target = new ListBox
  427. {
  428. [!ListBox.ItemsSourceProperty] = new Binding("Items"),
  429. [!ListBox.SelectedItemsProperty] = new Binding("SelectedItems"),
  430. };
  431. var viewModel = new
  432. {
  433. Items = new[] { "Foo", "Bar", "Baz " },
  434. SelectedItems = new ObservableCollection<string> { "Bar" },
  435. };
  436. var raised = 0;
  437. viewModel.SelectedItems.CollectionChanged += (s, e) => ++raised;
  438. target.DataContext = viewModel;
  439. Assert.Equal(0, raised);
  440. Assert.Equal(new[] { "Bar" }, viewModel.SelectedItems);
  441. Assert.Equal(new[] { "Bar" }, target.SelectedItems);
  442. Assert.Equal(new[] { "Bar" }, target.Selection.SelectedItems);
  443. }
  444. [Fact]
  445. public void Content_Can_Be_Bound_In_ItemContainerTheme()
  446. {
  447. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  448. {
  449. var items = new[] { new ItemViewModel("Foo"), new ItemViewModel("Bar") };
  450. var theme = new ControlTheme(typeof(ListBoxItem))
  451. {
  452. Setters =
  453. {
  454. new Setter(ListBoxItem.ContentProperty, new Binding("Caption")),
  455. }
  456. };
  457. var target = new ListBox
  458. {
  459. Template = ListBoxTemplate(),
  460. ItemsSource = items,
  461. ItemContainerTheme = theme,
  462. };
  463. Prepare(target);
  464. var containers = target.GetRealizedContainers().Cast<ListBoxItem>().ToList();
  465. Assert.Equal(2, containers.Count);
  466. Assert.Equal("Foo", containers[0].Content);
  467. Assert.Equal("Bar", containers[1].Content);
  468. }
  469. }
  470. private static FuncControlTemplate ListBoxTemplate()
  471. {
  472. return new FuncControlTemplate<ListBox>((parent, scope) =>
  473. new ScrollViewer
  474. {
  475. Name = "PART_ScrollViewer",
  476. Template = ScrollViewerTemplate(),
  477. Content = new ItemsPresenter
  478. {
  479. Name = "PART_ItemsPresenter",
  480. [~ItemsPresenter.ItemsPanelProperty] = parent.GetObservable(ItemsControl.ItemsPanelProperty).ToBinding(),
  481. }.RegisterInNameScope(scope)
  482. }.RegisterInNameScope(scope));
  483. }
  484. private static FuncControlTemplate ListBoxItemTemplate()
  485. {
  486. return new FuncControlTemplate<ListBoxItem>((parent, scope) =>
  487. new ContentPresenter
  488. {
  489. Name = "PART_ContentPresenter",
  490. [!ContentPresenter.ContentProperty] = parent[!ListBoxItem.ContentProperty],
  491. [!ContentPresenter.ContentTemplateProperty] = parent[!ListBoxItem.ContentTemplateProperty],
  492. }.RegisterInNameScope(scope));
  493. }
  494. private static FuncControlTemplate ScrollViewerTemplate()
  495. {
  496. return new FuncControlTemplate<ScrollViewer>((parent, scope) =>
  497. new Panel
  498. {
  499. Children =
  500. {
  501. new ScrollContentPresenter
  502. {
  503. Name = "PART_ContentPresenter",
  504. }.RegisterInNameScope(scope),
  505. new ScrollBar
  506. {
  507. Name = "verticalScrollBar",
  508. Orientation = Orientation.Vertical,
  509. }
  510. }
  511. });
  512. }
  513. private static void Prepare(ListBox target)
  514. {
  515. target.Width = target.Height = 100;
  516. var root = new TestRoot(target)
  517. {
  518. Resources =
  519. {
  520. {
  521. typeof(ListBoxItem),
  522. new ControlTheme(typeof(ListBoxItem))
  523. {
  524. Setters = { new Setter(ListBoxItem.TemplateProperty, ListBoxItemTemplate()) }
  525. }
  526. }
  527. }
  528. };
  529. root.LayoutManager.ExecuteInitialLayoutPass();
  530. }
  531. private static void Layout(Control c)
  532. {
  533. ((ILayoutRoot)c.GetVisualRoot()).LayoutManager.ExecuteLayoutPass();
  534. }
  535. private class Item
  536. {
  537. public Item(string value)
  538. {
  539. Value = value;
  540. }
  541. public string Value { get; }
  542. }
  543. [Fact]
  544. public void SelectedItem_Validation()
  545. {
  546. var target = new ListBox
  547. {
  548. Template = ListBoxTemplate(),
  549. ItemsSource = new[] { "Foo" },
  550. ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
  551. SelectionMode = SelectionMode.AlwaysSelected,
  552. };
  553. Prepare(target);
  554. var exception = new System.InvalidCastException("failed validation");
  555. var textObservable = new BehaviorSubject<BindingNotification>(new BindingNotification(exception, BindingErrorType.DataValidationError));
  556. target.Bind(ComboBox.SelectedItemProperty, textObservable);
  557. Assert.True(DataValidationErrors.GetHasErrors(target));
  558. Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));
  559. }
  560. [Fact]
  561. public void Handles_Resetting_Items()
  562. {
  563. var items = new ResettingCollection(100);
  564. var target = new ListBox
  565. {
  566. Template = ListBoxTemplate(),
  567. ItemsSource = items,
  568. ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas { Height = 10 }),
  569. };
  570. Prepare(target);
  571. var realized = target.GetRealizedContainers()
  572. .Cast<ListBoxItem>()
  573. .Select(x => (string)x.DataContext)
  574. .ToList();
  575. Assert.Equal(Enumerable.Range(0, 10).Select(x => $"Item{x}"), realized);
  576. items.Reverse();
  577. Layout(target);
  578. realized = target.GetRealizedContainers()
  579. .Cast<ListBoxItem>()
  580. .Select(x => (string)x.DataContext)
  581. .ToList();
  582. Assert.Equal(Enumerable.Range(0, 10).Select(x => $"Item{99 - x}"), realized);
  583. }
  584. [Fact]
  585. public void Handles_Resetting_Items_With_Existing_Selection_And_AutoScrollToSelectedItem()
  586. {
  587. var items = new ResettingCollection(100);
  588. var target = new ListBox
  589. {
  590. Template = ListBoxTemplate(),
  591. ItemsSource = items,
  592. ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas { Height = 10 }),
  593. AutoScrollToSelectedItem = true,
  594. SelectedIndex = 1,
  595. };
  596. Prepare(target);
  597. var realized = target.GetRealizedContainers()
  598. .Cast<ListBoxItem>()
  599. .Select(x => (string)x.DataContext)
  600. .ToList();
  601. Assert.Equal(Enumerable.Range(0, 10).Select(x => $"Item{x}"), realized);
  602. items.Reverse();
  603. Layout(target);
  604. realized = target.GetRealizedContainers()
  605. .Cast<ListBoxItem>()
  606. .Select(x => (string)x.DataContext)
  607. .ToList();
  608. // "Item1" should remain selected, and now be at the bottom of the viewport.
  609. Assert.Equal(Enumerable.Range(0, 10).Select(x => $"Item{10 - x}"), realized);
  610. }
  611. [Fact]
  612. public void WrapSelection_Should_Wrap()
  613. {
  614. using (UnitTestApplication.Start(TestServices.RealFocus))
  615. {
  616. var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray();
  617. var target = new ListBox
  618. {
  619. Template = ListBoxTemplate(),
  620. ItemsSource = items,
  621. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
  622. WrapSelection = true
  623. };
  624. Prepare(target);
  625. var lbItems = target.GetLogicalChildren().OfType<ListBoxItem>().ToArray();
  626. var first = lbItems.First();
  627. var beforeLast = lbItems[^2];
  628. var last = lbItems.Last();
  629. first.Focus();
  630. RaisePressedEvent(target, first, MouseButton.Left);
  631. Assert.Equal(true, first.IsSelected);
  632. RaiseKeyEvent(target, Key.Up);
  633. Assert.Equal(true, last.IsSelected);
  634. RaiseKeyEvent(target, Key.Up);
  635. Assert.Equal(true, beforeLast.IsSelected);
  636. RaiseKeyEvent(target, Key.Down);
  637. Assert.Equal(true, last.IsSelected);
  638. RaiseKeyEvent(target, Key.Down);
  639. Assert.Equal(true, first.IsSelected);
  640. target.WrapSelection = false;
  641. RaiseKeyEvent(target, Key.Up);
  642. Assert.Equal(true, first.IsSelected);
  643. }
  644. }
  645. [Fact]
  646. public void ContainerPrepared_Is_Raised_For_Each_Item_Container_On_Layout()
  647. {
  648. using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
  649. var target = new ListBox
  650. {
  651. Template = ListBoxTemplate(),
  652. Items = { "Foo", "Bar", "Baz" },
  653. };
  654. var result = new List<Control>();
  655. var index = 0;
  656. target.ContainerPrepared += (s, e) =>
  657. {
  658. Assert.Equal(index++, e.Index);
  659. result.Add(e.Container);
  660. };
  661. Prepare(target);
  662. Assert.Equal(3, result.Count);
  663. Assert.Equal(target.GetRealizedContainers(), result);
  664. }
  665. [Fact]
  666. public void ContainerPrepared_Is_Raised_For_Each_ItemsSource_Container_On_Layout()
  667. {
  668. using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
  669. var target = new ListBox
  670. {
  671. Template = ListBoxTemplate(),
  672. ItemsSource = new[] { "Foo", "Bar", "Baz" },
  673. };
  674. var result = new List<Control>();
  675. var index = 0;
  676. target.ContainerPrepared += (s, e) =>
  677. {
  678. Assert.Equal(index++, e.Index);
  679. result.Add(e.Container);
  680. };
  681. Prepare(target);
  682. Assert.Equal(3, result.Count);
  683. Assert.Equal(target.GetRealizedContainers(), result);
  684. }
  685. [Fact]
  686. public void ContainerPrepared_Is_Raised_For_Added_Item()
  687. {
  688. using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
  689. var data = new AvaloniaList<string> { "Foo", "Bar", "Baz" };
  690. var target = new ListBox
  691. {
  692. Template = ListBoxTemplate(),
  693. ItemsSource = data,
  694. };
  695. Prepare(target);
  696. var result = new List<Control>();
  697. target.ContainerPrepared += (s, e) =>
  698. {
  699. Assert.Equal(3, e.Index);
  700. result.Add(e.Container);
  701. };
  702. data.Add("Qux");
  703. Layout(target);
  704. Assert.Equal(1, result.Count);
  705. }
  706. [Fact]
  707. public void ContainerIndexChanged_Is_Raised_When_Item_Added()
  708. {
  709. using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
  710. var data = new AvaloniaList<string> { "Foo", "Bar", "Baz" };
  711. var target = new ListBox
  712. {
  713. Template = ListBoxTemplate(),
  714. ItemsSource = data,
  715. };
  716. Prepare(target);
  717. var result = new List<Control>();
  718. var index = 1;
  719. target.ContainerIndexChanged += (s, e) =>
  720. {
  721. Assert.Equal(index++, e.OldIndex);
  722. Assert.Equal(index, e.NewIndex);
  723. result.Add(e.Container);
  724. };
  725. data.Insert(1, "Qux");
  726. Layout(target);
  727. Assert.Equal(2, result.Count);
  728. Assert.Equal(target.GetRealizedContainers().Skip(2), result);
  729. }
  730. [Fact]
  731. public void ContainerClearing_Is_Raised_When_Item_Removed()
  732. {
  733. using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
  734. var data = new AvaloniaList<string> { "Foo", "Bar", "Baz" };
  735. var target = new ListBox
  736. {
  737. Template = ListBoxTemplate(),
  738. ItemsSource = data,
  739. };
  740. Prepare(target);
  741. var expected = target.ContainerFromIndex(1);
  742. var raised = 0;
  743. target.ContainerClearing += (s, e) =>
  744. {
  745. Assert.Same(expected, e.Container);
  746. ++raised;
  747. };
  748. data.RemoveAt(1);
  749. Layout(target);
  750. Assert.Equal(1, raised);
  751. }
  752. [Fact]
  753. public void Tab_Navigation_Should_Move_To_First_Item_When_No_Anchor_Element_Selected()
  754. {
  755. var services = TestServices.StyledWindow.With(
  756. focusManager: new FocusManager(),
  757. keyboardDevice: () => new KeyboardDevice());
  758. using var app = UnitTestApplication.Start(services);
  759. var target = new ListBox
  760. {
  761. Template = ListBoxTemplate(),
  762. Items = { "Foo", "Bar", "Baz" },
  763. };
  764. var button = new Button
  765. {
  766. Content = "Button",
  767. [DockPanel.DockProperty] = Dock.Top,
  768. };
  769. var root = new TestRoot
  770. {
  771. Child = new DockPanel
  772. {
  773. Children =
  774. {
  775. button,
  776. target,
  777. }
  778. }
  779. };
  780. var navigation = new KeyboardNavigationHandler();
  781. navigation.SetOwner(root);
  782. root.LayoutManager.ExecuteInitialLayoutPass();
  783. button.Focus();
  784. RaiseKeyEvent(button, Key.Tab);
  785. var item = target.ContainerFromIndex(0);
  786. Assert.Same(item, root.FocusManager.GetFocusedElement());
  787. }
  788. [Fact]
  789. public void Tab_Navigation_Should_Move_To_Anchor_Element()
  790. {
  791. var services = TestServices.StyledWindow.With(
  792. focusManager: new FocusManager(),
  793. keyboardDevice: () => new KeyboardDevice());
  794. using var app = UnitTestApplication.Start(services);
  795. var target = new ListBox
  796. {
  797. Template = ListBoxTemplate(),
  798. Items = { "Foo", "Bar", "Baz" },
  799. };
  800. var button = new Button
  801. {
  802. Content = "Button",
  803. [DockPanel.DockProperty] = Dock.Top,
  804. };
  805. var root = new TestRoot
  806. {
  807. Width = 1000,
  808. Height = 1000,
  809. Child = new DockPanel
  810. {
  811. Children =
  812. {
  813. button,
  814. target,
  815. }
  816. }
  817. };
  818. var navigation = new KeyboardNavigationHandler();
  819. navigation.SetOwner(root);
  820. root.LayoutManager.ExecuteInitialLayoutPass();
  821. button.Focus();
  822. target.Selection.AnchorIndex = 1;
  823. RaiseKeyEvent(button, Key.Tab);
  824. var item = target.ContainerFromIndex(1);
  825. Assert.Same(item, root.FocusManager.GetFocusedElement());
  826. RaiseKeyEvent(item, Key.Tab);
  827. Assert.Same(button, root.FocusManager.GetFocusedElement());
  828. target.Selection.AnchorIndex = 2;
  829. RaiseKeyEvent(button, Key.Tab);
  830. item = target.ContainerFromIndex(2);
  831. Assert.Same(item, root.FocusManager.GetFocusedElement());
  832. }
  833. private static void RaiseKeyEvent(Control target, Key key, KeyModifiers inputModifiers = 0)
  834. {
  835. target.RaiseEvent(new KeyEventArgs
  836. {
  837. RoutedEvent = InputElement.KeyDownEvent,
  838. KeyModifiers = inputModifiers,
  839. Key = key
  840. });
  841. }
  842. private record ItemViewModel(string Caption);
  843. private class ResettingCollection : List<string>, INotifyCollectionChanged
  844. {
  845. public ResettingCollection(int itemCount)
  846. {
  847. AddRange(Enumerable.Range(0, itemCount).Select(x => $"Item{x}"));
  848. }
  849. public new void Reverse()
  850. {
  851. base.Reverse();
  852. CollectionChanged?.Invoke(
  853. this,
  854. new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
  855. }
  856. public event NotifyCollectionChangedEventHandler CollectionChanged;
  857. }
  858. }
  859. }