ListBoxTests.cs 40 KB

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