ListBoxTests.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. using System.Collections.ObjectModel;
  2. using System.Linq;
  3. using System.Reactive.Subjects;
  4. using Avalonia.Collections;
  5. using Avalonia.Controls.Presenters;
  6. using Avalonia.Controls.Primitives;
  7. using Avalonia.Controls.Templates;
  8. using Avalonia.Data;
  9. using Avalonia.Input;
  10. using Avalonia.LogicalTree;
  11. using Avalonia.Styling;
  12. using Avalonia.UnitTests;
  13. using Avalonia.VisualTree;
  14. using Xunit;
  15. namespace Avalonia.Controls.UnitTests
  16. {
  17. public class ListBoxTests
  18. {
  19. private MouseTestHelper _mouse = new MouseTestHelper();
  20. [Fact]
  21. public void Should_Use_ItemTemplate_To_Create_Item_Content()
  22. {
  23. var target = new ListBox
  24. {
  25. Template = ListBoxTemplate(),
  26. Items = new[] { "Foo" },
  27. ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
  28. };
  29. Prepare(target);
  30. var container = (ListBoxItem)target.Presenter.Panel.Children[0];
  31. Assert.IsType<Canvas>(container.Presenter.Child);
  32. }
  33. [Fact]
  34. public void ListBox_Should_Find_ItemsPresenter_In_ScrollViewer()
  35. {
  36. var target = new ListBox
  37. {
  38. Template = ListBoxTemplate(),
  39. };
  40. Prepare(target);
  41. Assert.IsType<ItemsPresenter>(target.Presenter);
  42. }
  43. [Fact]
  44. public void ListBox_Should_Find_Scrollviewer_In_Template()
  45. {
  46. var target = new ListBox
  47. {
  48. Template = ListBoxTemplate(),
  49. };
  50. ScrollViewer viewer = null;
  51. target.TemplateApplied += (sender, e) =>
  52. {
  53. viewer = target.Scroll as ScrollViewer;
  54. };
  55. Prepare(target);
  56. Assert.NotNull(viewer);
  57. }
  58. [Fact]
  59. public void ListBoxItem_Containers_Should_Be_Generated()
  60. {
  61. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  62. {
  63. var items = new[] { "Foo", "Bar", "Baz " };
  64. var target = new ListBox
  65. {
  66. Template = ListBoxTemplate(),
  67. Items = items,
  68. };
  69. Prepare(target);
  70. var text = target.Presenter.Panel.Children
  71. .OfType<ListBoxItem>()
  72. .Select(x => x.Presenter.Child)
  73. .OfType<TextBlock>()
  74. .Select(x => x.Text)
  75. .ToList();
  76. Assert.Equal(items, text);
  77. }
  78. }
  79. [Fact]
  80. public void Container_Should_Have_Theme_Set_To_ItemContainerTheme()
  81. {
  82. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  83. {
  84. var items = new[] { "Foo", "Bar", "Baz " };
  85. var theme = new ControlTheme();
  86. var target = new ListBox
  87. {
  88. Template = ListBoxTemplate(),
  89. Items = items,
  90. ItemContainerTheme = theme,
  91. };
  92. Prepare(target);
  93. var container = (ListBoxItem)target.Presenter.Panel.Children[0];
  94. Assert.Same(container.Theme, theme);
  95. }
  96. }
  97. [Fact]
  98. public void Inline_Item_Should_Have_Theme_Set_To_ItemContainerTheme()
  99. {
  100. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  101. {
  102. var items = new[] { "Foo", "Bar", "Baz " };
  103. var theme = new ControlTheme();
  104. var target = new ListBox
  105. {
  106. Template = ListBoxTemplate(),
  107. Items = new[] { new ListBoxItem() },
  108. ItemContainerTheme = theme,
  109. };
  110. Prepare(target);
  111. var container = (ListBoxItem)target.Presenter.Panel.Children[0];
  112. Assert.Same(container.Theme, theme);
  113. }
  114. }
  115. [Fact]
  116. public void LogicalChildren_Should_Be_Set_For_DataTemplate_Generated_Items()
  117. {
  118. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  119. {
  120. var target = new ListBox
  121. {
  122. Template = ListBoxTemplate(),
  123. Items = new[] { "Foo", "Bar", "Baz " },
  124. };
  125. Prepare(target);
  126. Assert.Equal(3, target.GetLogicalChildren().Count());
  127. foreach (var child in target.GetLogicalChildren())
  128. {
  129. Assert.IsType<ListBoxItem>(child);
  130. }
  131. }
  132. }
  133. [Fact]
  134. public void DataContexts_Should_Be_Correctly_Set()
  135. {
  136. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  137. {
  138. var items = new object[]
  139. {
  140. "Foo",
  141. new Item("Bar"),
  142. new TextBlock { Text = "Baz" },
  143. new ListBoxItem { Content = "Qux" },
  144. };
  145. var target = new ListBox
  146. {
  147. Template = ListBoxTemplate(),
  148. DataContext = "Base",
  149. DataTemplates =
  150. {
  151. new FuncDataTemplate<Item>((x, _) => new Button { Content = x })
  152. },
  153. Items = items,
  154. };
  155. Prepare(target);
  156. var dataContexts = target.Presenter.Panel.Children
  157. .Cast<Control>()
  158. .Select(x => x.DataContext)
  159. .ToList();
  160. Assert.Equal(
  161. new object[] { items[0], items[1], "Base", "Base" },
  162. dataContexts);
  163. }
  164. }
  165. [Fact]
  166. public void Selection_Should_Be_Cleared_On_Recycled_Items()
  167. {
  168. using (UnitTestApplication.Start(TestServices.StyledWindow))
  169. {
  170. var target = new ListBox
  171. {
  172. Template = ListBoxTemplate(),
  173. Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
  174. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
  175. SelectedIndex = 0,
  176. };
  177. Prepare(target);
  178. // Make sure we're virtualized and first item is selected.
  179. Assert.Equal(10, target.Presenter.Panel.Children.Count);
  180. Assert.True(((ListBoxItem)target.Presenter.Panel.Children[0]).IsSelected);
  181. // Scroll down a page.
  182. target.Scroll.Offset = new Vector(0, 10);
  183. // Make sure recycled item isn't now selected.
  184. Assert.False(((ListBoxItem)target.Presenter.Panel.Children[0]).IsSelected);
  185. }
  186. }
  187. [Fact]
  188. public void ScrollViewer_Should_Have_Correct_Extent_And_Viewport()
  189. {
  190. using (UnitTestApplication.Start(TestServices.StyledWindow))
  191. {
  192. var target = new ListBox
  193. {
  194. Template = ListBoxTemplate(),
  195. Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
  196. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
  197. SelectedIndex = 0,
  198. };
  199. Prepare(target);
  200. Assert.Equal(new Size(20, 20), target.Scroll.Extent);
  201. Assert.Equal(new Size(100, 10), target.Scroll.Viewport);
  202. }
  203. }
  204. [Fact]
  205. public void Containers_Correct_After_Clear_Add_Remove()
  206. {
  207. using (UnitTestApplication.Start(TestServices.StyledWindow))
  208. {
  209. // Issue #1936
  210. var items = new AvaloniaList<string>(Enumerable.Range(0, 11).Select(x => $"Item {x}"));
  211. var target = new ListBox
  212. {
  213. Template = ListBoxTemplate(),
  214. Items = items,
  215. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
  216. SelectedIndex = 0,
  217. };
  218. Prepare(target);
  219. items.Clear();
  220. items.AddRange(Enumerable.Range(0, 11).Select(x => $"Item {x}"));
  221. items.Remove("Item 2");
  222. Assert.Equal(
  223. items,
  224. target.Presenter.Panel.Children.Cast<ListBoxItem>().Select(x => (string)x.Content));
  225. }
  226. }
  227. [Fact]
  228. public void Toggle_Selection_Should_Update_Containers()
  229. {
  230. using (UnitTestApplication.Start(TestServices.StyledWindow))
  231. {
  232. var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray();
  233. var target = new ListBox
  234. {
  235. Template = ListBoxTemplate(),
  236. Items = items,
  237. SelectionMode = SelectionMode.Toggle,
  238. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 })
  239. };
  240. Prepare(target);
  241. var lbItems = target.GetLogicalChildren().OfType<ListBoxItem>().ToArray();
  242. var item = lbItems[0];
  243. Assert.Equal(false, item.IsSelected);
  244. RaisePressedEvent(target, item, MouseButton.Left);
  245. Assert.Equal(true, item.IsSelected);
  246. RaisePressedEvent(target, item, MouseButton.Left);
  247. Assert.Equal(false, item.IsSelected);
  248. }
  249. }
  250. [Fact]
  251. public void Can_Decrease_Number_Of_Materialized_Items_By_Removing_From_Source_Collection()
  252. {
  253. using (UnitTestApplication.Start(TestServices.StyledWindow))
  254. {
  255. var items = new AvaloniaList<string>(Enumerable.Range(0, 20).Select(x => $"Item {x}"));
  256. var target = new ListBox
  257. {
  258. Template = ListBoxTemplate(),
  259. Items = items,
  260. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 })
  261. };
  262. Prepare(target);
  263. target.Scroll.Offset = new Vector(0, 1);
  264. items.RemoveRange(0, 11);
  265. }
  266. }
  267. private void RaisePressedEvent(ListBox listBox, ListBoxItem item, MouseButton mouseButton)
  268. {
  269. _mouse.Click(listBox, item, mouseButton);
  270. }
  271. [Fact]
  272. public void ListBox_After_Scroll_IndexOutOfRangeException_Shouldnt_Be_Thrown()
  273. {
  274. using (UnitTestApplication.Start(TestServices.StyledWindow))
  275. {
  276. var items = Enumerable.Range(0, 11).Select(x => $"{x}").ToArray();
  277. var target = new ListBox
  278. {
  279. Template = ListBoxTemplate(),
  280. Items = items,
  281. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 11 })
  282. };
  283. Prepare(target);
  284. var panel = target.Presenter.Panel as IVirtualizingPanel;
  285. var listBoxItems = panel.Children.OfType<ListBoxItem>();
  286. //virtualization should have created exactly 10 items
  287. Assert.Equal(10, listBoxItems.Count());
  288. Assert.Equal("0", listBoxItems.First().DataContext);
  289. Assert.Equal("9", listBoxItems.Last().DataContext);
  290. //instead pixeloffset > 0 there could be pretty complex sequence for repro
  291. //it involves add/remove/scroll to end multiple actions
  292. //which i can't find so far :(, but this is the simplest way to add it to unit test
  293. panel.PixelOffset = 1;
  294. //here scroll to end -> IndexOutOfRangeException is thrown
  295. target.Scroll.Offset = new Vector(0, 2);
  296. Assert.True(true);
  297. }
  298. }
  299. [Fact]
  300. public void LayoutManager_Should_Measure_Arrange_All()
  301. {
  302. var virtualizationMode = ItemVirtualizationMode.Simple;
  303. using (UnitTestApplication.Start(TestServices.StyledWindow))
  304. {
  305. var items = new AvaloniaList<string>(Enumerable.Range(1, 7).Select(v => v.ToString()));
  306. var wnd = new Window() { SizeToContent = SizeToContent.WidthAndHeight };
  307. wnd.IsVisible = true;
  308. var target = new ListBox();
  309. wnd.Content = target;
  310. var lm = wnd.LayoutManager;
  311. target.Height = 110;
  312. target.Width = 50;
  313. target.DataContext = items;
  314. target.VirtualizationMode = virtualizationMode;
  315. target.ItemTemplate = new FuncDataTemplate<object>((c, _) =>
  316. {
  317. var tb = new TextBlock() { Height = 10, Width = 30 };
  318. tb.Bind(TextBlock.TextProperty, new Data.Binding());
  319. return tb;
  320. }, true);
  321. lm.ExecuteInitialLayoutPass();
  322. target.Items = items;
  323. lm.ExecuteLayoutPass();
  324. items.Insert(3, "3+");
  325. lm.ExecuteLayoutPass();
  326. items.Insert(4, "4+");
  327. lm.ExecuteLayoutPass();
  328. //RESET
  329. items.Clear();
  330. foreach (var i in Enumerable.Range(1, 7))
  331. {
  332. items.Add(i.ToString());
  333. }
  334. //working bit better with this line no outof memory or remaining to arrange/measure ???
  335. //lm.ExecuteLayoutPass();
  336. items.Insert(2, "2+");
  337. lm.ExecuteLayoutPass();
  338. //after few more layout cycles layoutmanager shouldn't hold any more visual for measure/arrange
  339. lm.ExecuteLayoutPass();
  340. lm.ExecuteLayoutPass();
  341. var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
  342. var toMeasure = lm.GetType().GetField("_toMeasure", flags).GetValue(lm) as System.Collections.Generic.IEnumerable<Layout.ILayoutable>;
  343. var toArrange = lm.GetType().GetField("_toArrange", flags).GetValue(lm) as System.Collections.Generic.IEnumerable<Layout.ILayoutable>;
  344. Assert.Equal(0, toMeasure.Count());
  345. Assert.Equal(0, toArrange.Count());
  346. }
  347. }
  348. [Fact]
  349. public void ListBox_Should_Be_Valid_After_Remove_Of_Item_In_NonVisibleArea()
  350. {
  351. using (UnitTestApplication.Start(TestServices.StyledWindow))
  352. {
  353. var items = new AvaloniaList<string>(Enumerable.Range(1, 30).Select(v => v.ToString()));
  354. var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
  355. var target = new ListBox()
  356. {
  357. AutoScrollToSelectedItem = true,
  358. Height = 100,
  359. Width = 50,
  360. VirtualizationMode = ItemVirtualizationMode.Simple,
  361. ItemTemplate = new FuncDataTemplate<object>((c, _) => new Border() { Height = 10 }),
  362. Items = items,
  363. };
  364. wnd.Content = target;
  365. var lm = wnd.LayoutManager;
  366. lm.ExecuteInitialLayoutPass();
  367. //select last / scroll to last item
  368. target.SelectedItem = items.Last();
  369. lm.ExecuteLayoutPass();
  370. //remove the first item (in non realized area of the listbox)
  371. items.Remove("1");
  372. lm.ExecuteLayoutPass();
  373. Assert.Equal("30", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 1).DataContext);
  374. Assert.Equal("29", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 2).DataContext);
  375. Assert.Equal("28", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 3).DataContext);
  376. Assert.Equal("27", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 4).DataContext);
  377. Assert.Equal("26", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 5).DataContext);
  378. }
  379. }
  380. [Fact]
  381. public void Clicking_Item_Should_Raise_BringIntoView_For_Correct_Control()
  382. {
  383. using (UnitTestApplication.Start(TestServices.StyledWindow))
  384. {
  385. // Issue #3934
  386. var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray();
  387. var target = new ListBox
  388. {
  389. Template = ListBoxTemplate(),
  390. Items = items,
  391. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
  392. SelectionMode = SelectionMode.AlwaysSelected,
  393. VirtualizationMode = ItemVirtualizationMode.None,
  394. };
  395. Prepare(target);
  396. // First an item that is not index 0 must be selected.
  397. _mouse.Click(target.Presenter.Panel.Children[1]);
  398. Assert.Equal(1, target.Selection.AnchorIndex);
  399. // We're going to be clicking on item 9.
  400. var item = (ListBoxItem)target.Presenter.Panel.Children[9];
  401. var raised = 0;
  402. // Make sure a RequestBringIntoView event is raised for item 9. It won't be handled
  403. // by the ScrollContentPresenter as the item is already visible, so we don't need
  404. // handledEventsToo: true. Issue #3934 failed here because item 0 was being scrolled
  405. // into view due to SelectionMode.AlwaysSelected.
  406. target.AddHandler(Control.RequestBringIntoViewEvent, (s, e) =>
  407. {
  408. Assert.Same(item, e.TargetObject);
  409. ++raised;
  410. });
  411. // Click item 9.
  412. _mouse.Click(item);
  413. Assert.Equal(1, raised);
  414. }
  415. }
  416. [Fact]
  417. public void Adding_And_Selecting_Item_With_AutoScrollToSelectedItem_Should_NotHide_FirstItem()
  418. {
  419. using (UnitTestApplication.Start(TestServices.StyledWindow))
  420. {
  421. var items = new AvaloniaList<string>();
  422. var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
  423. var target = new ListBox()
  424. {
  425. VerticalAlignment = Layout.VerticalAlignment.Top,
  426. AutoScrollToSelectedItem = true,
  427. Width = 50,
  428. VirtualizationMode = ItemVirtualizationMode.Simple,
  429. ItemTemplate = new FuncDataTemplate<object>((c, _) => new Border() { Height = 10 }),
  430. Items = items,
  431. };
  432. wnd.Content = target;
  433. var lm = wnd.LayoutManager;
  434. lm.ExecuteInitialLayoutPass();
  435. var panel = target.Presenter.Panel;
  436. items.Add("Item 1");
  437. target.Selection.Select(0);
  438. lm.ExecuteLayoutPass();
  439. Assert.Equal(1, panel.Children.Count);
  440. items.Add("Item 2");
  441. target.Selection.Select(1);
  442. lm.ExecuteLayoutPass();
  443. Assert.Equal(2, panel.Children.Count);
  444. //make sure we have enough space to show all items
  445. Assert.True(panel.Bounds.Height >= panel.Children.Sum(c => c.Bounds.Height));
  446. //make sure we show items and they completelly visible, not only partially
  447. Assert.True(panel.Children[0].Bounds.Top >= 0 && panel.Children[0].Bounds.Bottom <= panel.Bounds.Height, "first item is not completelly visible!");
  448. Assert.True(panel.Children[1].Bounds.Top >= 0 && panel.Children[1].Bounds.Bottom <= panel.Bounds.Height, "second item is not completelly visible!");
  449. }
  450. }
  451. [Fact]
  452. public void Initial_Binding_Of_SelectedItems_Should_Not_Cause_Write_To_SelectedItems()
  453. {
  454. var target = new ListBox
  455. {
  456. [!ListBox.ItemsProperty] = new Binding("Items"),
  457. [!ListBox.SelectedItemsProperty] = new Binding("SelectedItems"),
  458. };
  459. var viewModel = new
  460. {
  461. Items = new[] { "Foo", "Bar", "Baz " },
  462. SelectedItems = new ObservableCollection<string> { "Bar" },
  463. };
  464. var raised = 0;
  465. viewModel.SelectedItems.CollectionChanged += (s, e) => ++raised;
  466. target.DataContext = viewModel;
  467. Assert.Equal(0, raised);
  468. Assert.Equal(new[] { "Bar" }, viewModel.SelectedItems);
  469. Assert.Equal(new[] { "Bar" }, target.SelectedItems);
  470. Assert.Equal(new[] { "Bar" }, target.Selection.SelectedItems);
  471. }
  472. private FuncControlTemplate ListBoxTemplate()
  473. {
  474. return new FuncControlTemplate<ListBox>((parent, scope) =>
  475. new ScrollViewer
  476. {
  477. Name = "PART_ScrollViewer",
  478. Template = ScrollViewerTemplate(),
  479. Content = new ItemsPresenter
  480. {
  481. Name = "PART_ItemsPresenter",
  482. [~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).ToBinding(),
  483. [~ItemsPresenter.ItemsPanelProperty] = parent.GetObservable(ItemsControl.ItemsPanelProperty).ToBinding(),
  484. [~ItemsPresenter.VirtualizationModeProperty] = parent.GetObservable(ListBox.VirtualizationModeProperty).ToBinding(),
  485. }.RegisterInNameScope(scope)
  486. }.RegisterInNameScope(scope));
  487. }
  488. private FuncControlTemplate ListBoxItemTemplate()
  489. {
  490. return new FuncControlTemplate<ListBoxItem>((parent, scope) =>
  491. new ContentPresenter
  492. {
  493. Name = "PART_ContentPresenter",
  494. [!ContentPresenter.ContentProperty] = parent[!ListBoxItem.ContentProperty],
  495. [!ContentPresenter.ContentTemplateProperty] = parent[!ListBoxItem.ContentTemplateProperty],
  496. }.RegisterInNameScope(scope));
  497. }
  498. private FuncControlTemplate ScrollViewerTemplate()
  499. {
  500. return new FuncControlTemplate<ScrollViewer>((parent, scope) =>
  501. new Panel
  502. {
  503. Children =
  504. {
  505. new ScrollContentPresenter
  506. {
  507. Name = "PART_ContentPresenter",
  508. [~ScrollContentPresenter.ContentProperty] = parent.GetObservable(ScrollViewer.ContentProperty).ToBinding(),
  509. [~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty],
  510. [~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty],
  511. [~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty],
  512. }.RegisterInNameScope(scope),
  513. new ScrollBar
  514. {
  515. Name = "verticalScrollBar",
  516. [~ScrollBar.MaximumProperty] = parent[~ScrollViewer.VerticalScrollBarMaximumProperty],
  517. [~~ScrollBar.ValueProperty] = parent[~~ScrollViewer.VerticalScrollBarValueProperty],
  518. }
  519. }
  520. });
  521. }
  522. private void Prepare(ListBox target)
  523. {
  524. // The ListBox needs to be part of a rooted visual tree.
  525. var root = new TestRoot();
  526. root.Child = target;
  527. // Apply the template to the ListBox itself.
  528. target.ApplyTemplate();
  529. // Then to its inner ScrollViewer.
  530. var scrollViewer = (ScrollViewer)target.GetVisualChildren().Single();
  531. scrollViewer.ApplyTemplate();
  532. // Then make the ScrollViewer create its child.
  533. ((ContentPresenter)scrollViewer.Presenter).UpdateChild();
  534. // Now the ItemsPresenter should be reigstered, so apply its template.
  535. target.Presenter.ApplyTemplate();
  536. // Because ListBox items are virtualized we need to do a layout to make them appear.
  537. target.Measure(new Size(100, 100));
  538. target.Arrange(new Rect(0, 0, 100, 100));
  539. // Now set and apply the item templates.
  540. foreach (ListBoxItem item in target.Presenter.Panel.Children)
  541. {
  542. item.Template = ListBoxItemTemplate();
  543. item.ApplyTemplate();
  544. item.Presenter.ApplyTemplate();
  545. ((ContentPresenter)item.Presenter).UpdateChild();
  546. }
  547. // The items were created before the template was applied, so now we need to go back
  548. // and re-arrange everything.
  549. foreach (IControl i in target.GetSelfAndVisualDescendants())
  550. {
  551. i.InvalidateMeasure();
  552. }
  553. target.Measure(new Size(100, 100));
  554. target.Arrange(new Rect(0, 0, 100, 100));
  555. }
  556. private class Item
  557. {
  558. public Item(string value)
  559. {
  560. Value = value;
  561. }
  562. public string Value { get; }
  563. }
  564. [Fact]
  565. public void SelectedItem_Validation()
  566. {
  567. var target = new ListBox
  568. {
  569. Template = ListBoxTemplate(),
  570. Items = new[] { "Foo" },
  571. ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
  572. SelectionMode = SelectionMode.AlwaysSelected,
  573. VirtualizationMode = ItemVirtualizationMode.None
  574. };
  575. Prepare(target);
  576. var exception = new System.InvalidCastException("failed validation");
  577. var textObservable = new BehaviorSubject<BindingNotification>(new BindingNotification(exception, BindingErrorType.DataValidationError));
  578. target.Bind(ComboBox.SelectedItemProperty, textObservable);
  579. Assert.True(DataValidationErrors.GetHasErrors(target));
  580. Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));
  581. }
  582. private void RaiseKeyEvent(ListBox listBox, Key key, KeyModifiers inputModifiers = 0)
  583. {
  584. listBox.RaiseEvent(new KeyEventArgs
  585. {
  586. RoutedEvent = InputElement.KeyDownEvent,
  587. KeyModifiers = inputModifiers,
  588. Key = key
  589. });
  590. }
  591. [Fact]
  592. public void WrapSelection_Should_Wrap()
  593. {
  594. using (UnitTestApplication.Start(TestServices.RealFocus))
  595. {
  596. var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray();
  597. var target = new ListBox
  598. {
  599. Template = ListBoxTemplate(),
  600. Items = items,
  601. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
  602. WrapSelection = true
  603. };
  604. Prepare(target);
  605. var lbItems = target.GetLogicalChildren().OfType<ListBoxItem>().ToArray();
  606. var first = lbItems.First();
  607. var last = lbItems.Last();
  608. first.Focus();
  609. RaisePressedEvent(target, first, MouseButton.Left);
  610. Assert.Equal(true, first.IsSelected);
  611. RaiseKeyEvent(target, Key.Up);
  612. Assert.Equal(true, last.IsSelected);
  613. RaiseKeyEvent(target, Key.Down);
  614. Assert.Equal(true, first.IsSelected);
  615. target.WrapSelection = false;
  616. RaiseKeyEvent(target, Key.Up);
  617. Assert.Equal(true, first.IsSelected);
  618. }
  619. }
  620. }
  621. }