TreeViewTests.cs 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using Avalonia.Collections;
  8. using Avalonia.Controls.Presenters;
  9. using Avalonia.Controls.Templates;
  10. using Avalonia.Data;
  11. using Avalonia.Data.Core;
  12. using Avalonia.Diagnostics;
  13. using Avalonia.Input;
  14. using Avalonia.Input.Platform;
  15. using Avalonia.Interactivity;
  16. using Avalonia.LogicalTree;
  17. using Avalonia.Styling;
  18. using Avalonia.UnitTests;
  19. using Xunit;
  20. namespace Avalonia.Controls.UnitTests
  21. {
  22. public class TreeViewTests
  23. {
  24. MouseTestHelper _mouse = new MouseTestHelper();
  25. [Fact]
  26. public void Items_Should_Be_Created()
  27. {
  28. var target = new TreeView
  29. {
  30. Template = CreateTreeViewTemplate(),
  31. Items = CreateTestTreeData(),
  32. };
  33. var root = new TestRoot(target);
  34. CreateNodeDataTemplate(target);
  35. ApplyTemplates(target);
  36. Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0));
  37. Assert.Equal(new[] { "Child1", "Child2", "Child3" }, ExtractItemHeader(target, 1));
  38. Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2));
  39. }
  40. [Fact]
  41. public void Items_Should_Be_Created_Using_ItemTemplate_If_Present()
  42. {
  43. TreeView target;
  44. var root = new TestRoot
  45. {
  46. Child = target = new TreeView
  47. {
  48. Template = CreateTreeViewTemplate(),
  49. Items = CreateTestTreeData(),
  50. ItemTemplate = new FuncTreeDataTemplate<Node>(
  51. (_, __) => new Canvas(),
  52. x => x.Children),
  53. }
  54. };
  55. ApplyTemplates(target);
  56. var items = target.ItemContainerGenerator.Index.Containers
  57. .OfType<TreeViewItem>()
  58. .ToList();
  59. Assert.Equal(5, items.Count);
  60. Assert.All(items, x => Assert.IsType<Canvas>(x.HeaderPresenter.Child));
  61. }
  62. [Fact]
  63. public void Root_ItemContainerGenerator_Containers_Should_Be_Root_Containers()
  64. {
  65. var target = new TreeView
  66. {
  67. Template = CreateTreeViewTemplate(),
  68. Items = CreateTestTreeData(),
  69. };
  70. var root = new TestRoot(target);
  71. CreateNodeDataTemplate(target);
  72. ApplyTemplates(target);
  73. var container = (TreeViewItem)target.ItemContainerGenerator.Containers.Single().ContainerControl;
  74. var header = (TextBlock)container.Header;
  75. Assert.Equal("Root", header.Text);
  76. }
  77. [Fact]
  78. public void Root_TreeContainerFromItem_Should_Return_Descendant_Item()
  79. {
  80. var tree = CreateTestTreeData();
  81. var target = new TreeView
  82. {
  83. Template = CreateTreeViewTemplate(),
  84. Items = tree,
  85. };
  86. // For TreeViewItem to find its parent TreeView, OnAttachedToLogicalTree needs
  87. // to be called, which requires an IStyleRoot.
  88. var root = new TestRoot();
  89. root.Child = target;
  90. CreateNodeDataTemplate(target);
  91. ApplyTemplates(target);
  92. var container = target.ItemContainerGenerator.Index.ContainerFromItem(
  93. tree[0].Children[1].Children[0]);
  94. Assert.NotNull(container);
  95. var header = ((TreeViewItem)container).Header;
  96. var headerContent = ((TextBlock)header).Text;
  97. Assert.Equal("Grandchild2a", headerContent);
  98. }
  99. [Fact]
  100. public void Clicking_Item_Should_Select_It()
  101. {
  102. var tree = CreateTestTreeData();
  103. var target = new TreeView
  104. {
  105. Template = CreateTreeViewTemplate(),
  106. Items = tree,
  107. };
  108. var visualRoot = new TestRoot();
  109. visualRoot.Child = target;
  110. CreateNodeDataTemplate(target);
  111. ApplyTemplates(target);
  112. var item = tree[0].Children[1].Children[0];
  113. var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
  114. Assert.NotNull(container);
  115. _mouse.Click(container);
  116. Assert.Equal(item, target.SelectedItem);
  117. Assert.True(container.IsSelected);
  118. }
  119. [Fact]
  120. public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_It()
  121. {
  122. var tree = CreateTestTreeData();
  123. var target = new TreeView
  124. {
  125. Template = CreateTreeViewTemplate(),
  126. Items = tree
  127. };
  128. var visualRoot = new TestRoot();
  129. visualRoot.Child = target;
  130. CreateNodeDataTemplate(target);
  131. ApplyTemplates(target);
  132. var item = tree[0].Children[1].Children[0];
  133. var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
  134. Assert.NotNull(container);
  135. target.SelectedItem = item;
  136. Assert.True(container.IsSelected);
  137. _mouse.Click(container, modifiers: InputModifiers.Control);
  138. Assert.Null(target.SelectedItem);
  139. Assert.False(container.IsSelected);
  140. }
  141. [Fact]
  142. public void Clicking_WithControlModifier_Not_Selected_Item_Should_Select_It()
  143. {
  144. var tree = CreateTestTreeData();
  145. var target = new TreeView
  146. {
  147. Template = CreateTreeViewTemplate(),
  148. Items = tree
  149. };
  150. var visualRoot = new TestRoot();
  151. visualRoot.Child = target;
  152. CreateNodeDataTemplate(target);
  153. ApplyTemplates(target);
  154. var item1 = tree[0].Children[1].Children[0];
  155. var container1 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
  156. var item2 = tree[0].Children[1];
  157. var container2 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2);
  158. Assert.NotNull(container1);
  159. Assert.NotNull(container2);
  160. target.SelectedItem = item1;
  161. Assert.True(container1.IsSelected);
  162. _mouse.Click(container2, modifiers: InputModifiers.Control);
  163. Assert.Equal(item2, target.SelectedItem);
  164. Assert.False(container1.IsSelected);
  165. Assert.True(container2.IsSelected);
  166. }
  167. [Fact]
  168. public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_And_Remove_From_SelectedItems()
  169. {
  170. var tree = CreateTestTreeData();
  171. var target = new TreeView
  172. {
  173. Template = CreateTreeViewTemplate(),
  174. Items = tree,
  175. SelectionMode = SelectionMode.Multiple
  176. };
  177. var visualRoot = new TestRoot();
  178. visualRoot.Child = target;
  179. CreateNodeDataTemplate(target);
  180. ApplyTemplates(target);
  181. var rootNode = tree[0];
  182. var item1 = rootNode.Children[0];
  183. var item2 = rootNode.Children.Last();
  184. var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
  185. var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2);
  186. ClickContainer(item1Container, InputModifiers.Control);
  187. Assert.True(item1Container.IsSelected);
  188. ClickContainer(item2Container, InputModifiers.Control);
  189. Assert.True(item2Container.IsSelected);
  190. Assert.Equal(new[] {item1, item2}, target.SelectedItems.OfType<Node>());
  191. ClickContainer(item1Container, InputModifiers.Control);
  192. Assert.False(item1Container.IsSelected);
  193. Assert.DoesNotContain(item1, target.SelectedItems.OfType<Node>());
  194. }
  195. [Fact]
  196. public void Clicking_WithShiftModifier_DownDirection_Should_Select_Range_Of_Items()
  197. {
  198. var tree = CreateTestTreeData();
  199. var target = new TreeView
  200. {
  201. Template = CreateTreeViewTemplate(),
  202. Items = tree,
  203. SelectionMode = SelectionMode.Multiple
  204. };
  205. var visualRoot = new TestRoot();
  206. visualRoot.Child = target;
  207. CreateNodeDataTemplate(target);
  208. ApplyTemplates(target);
  209. var rootNode = tree[0];
  210. var from = rootNode.Children[0];
  211. var to = rootNode.Children.Last();
  212. var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
  213. var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
  214. ClickContainer(fromContainer, InputModifiers.None);
  215. Assert.True(fromContainer.IsSelected);
  216. ClickContainer(toContainer, InputModifiers.Shift);
  217. AssertChildrenSelected(target, rootNode);
  218. }
  219. [Fact]
  220. public void Clicking_WithShiftModifier_UpDirection_Should_Select_Range_Of_Items()
  221. {
  222. var tree = CreateTestTreeData();
  223. var target = new TreeView
  224. {
  225. Template = CreateTreeViewTemplate(),
  226. Items = tree,
  227. SelectionMode = SelectionMode.Multiple
  228. };
  229. var visualRoot = new TestRoot();
  230. visualRoot.Child = target;
  231. CreateNodeDataTemplate(target);
  232. ApplyTemplates(target);
  233. var rootNode = tree[0];
  234. var from = rootNode.Children.Last();
  235. var to = rootNode.Children[0];
  236. var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
  237. var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
  238. ClickContainer(fromContainer, InputModifiers.None);
  239. Assert.True(fromContainer.IsSelected);
  240. ClickContainer(toContainer, InputModifiers.Shift);
  241. AssertChildrenSelected(target, rootNode);
  242. }
  243. [Fact]
  244. public void Clicking_First_Item_Of_SelectedItems_Should_Select_Only_It()
  245. {
  246. var tree = CreateTestTreeData();
  247. var target = new TreeView
  248. {
  249. Template = CreateTreeViewTemplate(),
  250. Items = tree,
  251. SelectionMode = SelectionMode.Multiple
  252. };
  253. var visualRoot = new TestRoot();
  254. visualRoot.Child = target;
  255. CreateNodeDataTemplate(target);
  256. ApplyTemplates(target);
  257. var rootNode = tree[0];
  258. var from = rootNode.Children.Last();
  259. var to = rootNode.Children[0];
  260. var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
  261. var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
  262. ClickContainer(fromContainer, InputModifiers.None);
  263. ClickContainer(toContainer, InputModifiers.Shift);
  264. AssertChildrenSelected(target, rootNode);
  265. ClickContainer(fromContainer, InputModifiers.None);
  266. Assert.True(fromContainer.IsSelected);
  267. foreach (var child in rootNode.Children)
  268. {
  269. if (child == from)
  270. {
  271. continue;
  272. }
  273. var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(child);
  274. Assert.False(container.IsSelected);
  275. }
  276. }
  277. [Fact]
  278. public void Setting_SelectedItem_Should_Set_Container_Selected()
  279. {
  280. var tree = CreateTestTreeData();
  281. var target = new TreeView
  282. {
  283. Template = CreateTreeViewTemplate(),
  284. Items = tree,
  285. };
  286. var visualRoot = new TestRoot();
  287. visualRoot.Child = target;
  288. CreateNodeDataTemplate(target);
  289. ApplyTemplates(target);
  290. var item = tree[0].Children[1].Children[0];
  291. var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
  292. Assert.NotNull(container);
  293. target.SelectedItem = item;
  294. Assert.True(container.IsSelected);
  295. }
  296. [Fact]
  297. public void Setting_SelectedItem_Should_Raise_SelectedItemChanged_Event()
  298. {
  299. var tree = CreateTestTreeData();
  300. var target = new TreeView
  301. {
  302. Template = CreateTreeViewTemplate(),
  303. Items = tree,
  304. };
  305. var visualRoot = new TestRoot();
  306. visualRoot.Child = target;
  307. CreateNodeDataTemplate(target);
  308. ApplyTemplates(target);
  309. var item = tree[0].Children[1].Children[0];
  310. var called = false;
  311. target.SelectionChanged += (s, e) =>
  312. {
  313. Assert.Empty(e.RemovedItems);
  314. Assert.Equal(1, e.AddedItems.Count);
  315. Assert.Same(item, e.AddedItems[0]);
  316. called = true;
  317. };
  318. target.SelectedItem = item;
  319. Assert.True(called);
  320. }
  321. [Fact]
  322. public void LogicalChildren_Should_Be_Set()
  323. {
  324. var target = new TreeView
  325. {
  326. Template = CreateTreeViewTemplate(),
  327. Items = new[] { "Foo", "Bar", "Baz " },
  328. };
  329. ApplyTemplates(target);
  330. var result = target.GetLogicalChildren()
  331. .OfType<TreeViewItem>()
  332. .Select(x => x.Header)
  333. .OfType<TextBlock>()
  334. .Select(x => x.Text)
  335. .ToList();
  336. Assert.Equal(new[] { "Foo", "Bar", "Baz " }, result);
  337. }
  338. [Fact]
  339. public void Removing_Item_Should_Remove_Itself_And_Children_From_Index()
  340. {
  341. var tree = CreateTestTreeData();
  342. var target = new TreeView
  343. {
  344. Template = CreateTreeViewTemplate(),
  345. Items = tree,
  346. };
  347. var root = new TestRoot();
  348. root.Child = target;
  349. CreateNodeDataTemplate(target);
  350. ApplyTemplates(target);
  351. Assert.Equal(5, target.ItemContainerGenerator.Index.Containers.Count());
  352. tree[0].Children.RemoveAt(1);
  353. Assert.Equal(3, target.ItemContainerGenerator.Index.Containers.Count());
  354. }
  355. [Fact]
  356. public void DataContexts_Should_Be_Correctly_Set()
  357. {
  358. var items = new object[]
  359. {
  360. "Foo",
  361. new Node { Value = "Bar" },
  362. new TextBlock { Text = "Baz" },
  363. new TreeViewItem { Header = "Qux" },
  364. };
  365. var target = new TreeView
  366. {
  367. Template = CreateTreeViewTemplate(),
  368. DataContext = "Base",
  369. DataTemplates =
  370. {
  371. new FuncDataTemplate<Node>((x, _) => new Button { Content = x })
  372. },
  373. Items = items,
  374. };
  375. ApplyTemplates(target);
  376. var dataContexts = target.Presenter.Panel.Children
  377. .Cast<Control>()
  378. .Select(x => x.DataContext)
  379. .ToList();
  380. Assert.Equal(
  381. new object[] { items[0], items[1], "Base", "Base" },
  382. dataContexts);
  383. }
  384. [Fact]
  385. public void Control_Item_Should_Not_Be_NameScope()
  386. {
  387. var items = new object[]
  388. {
  389. new TreeViewItem(),
  390. };
  391. var target = new TreeView
  392. {
  393. Template = CreateTreeViewTemplate(),
  394. Items = items,
  395. };
  396. target.ApplyTemplate();
  397. target.Presenter.ApplyTemplate();
  398. var item = target.Presenter.Panel.LogicalChildren[0];
  399. Assert.Null(NameScope.GetNameScope((TreeViewItem)item));
  400. }
  401. [Fact]
  402. public void Should_React_To_Children_Changing()
  403. {
  404. var data = CreateTestTreeData();
  405. var target = new TreeView
  406. {
  407. Template = CreateTreeViewTemplate(),
  408. Items = data,
  409. };
  410. var root = new TestRoot(target);
  411. CreateNodeDataTemplate(target);
  412. ApplyTemplates(target);
  413. Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0));
  414. Assert.Equal(new[] { "Child1", "Child2", "Child3" }, ExtractItemHeader(target, 1));
  415. Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2));
  416. // Make sure that the binding to Node.Children does not get collected.
  417. GC.Collect();
  418. data[0].Children = new AvaloniaList<Node>
  419. {
  420. new Node
  421. {
  422. Value = "NewChild1",
  423. }
  424. };
  425. Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0));
  426. Assert.Equal(new[] { "NewChild1" }, ExtractItemHeader(target, 1));
  427. }
  428. [Fact]
  429. public void Keyboard_Navigation_Should_Move_To_Last_Selected_Node()
  430. {
  431. using (UnitTestApplication.Start(TestServices.RealFocus))
  432. {
  433. var focus = FocusManager.Instance;
  434. var navigation = AvaloniaLocator.Current.GetService<IKeyboardNavigationHandler>();
  435. var data = CreateTestTreeData();
  436. var target = new TreeView
  437. {
  438. Template = CreateTreeViewTemplate(),
  439. Items = data,
  440. };
  441. var button = new Button();
  442. var root = new TestRoot
  443. {
  444. Child = new StackPanel
  445. {
  446. Children = { target, button },
  447. }
  448. };
  449. CreateNodeDataTemplate(target);
  450. ApplyTemplates(target);
  451. var item = data[0].Children[0];
  452. var node = target.ItemContainerGenerator.Index.ContainerFromItem(item);
  453. Assert.NotNull(node);
  454. target.SelectedItem = item;
  455. node.Focus();
  456. Assert.Same(node, focus.Current);
  457. navigation.Move(focus.Current, NavigationDirection.Next);
  458. Assert.Same(button, focus.Current);
  459. navigation.Move(focus.Current, NavigationDirection.Next);
  460. Assert.Same(node, focus.Current);
  461. }
  462. }
  463. [Fact]
  464. public void Pressing_SelectAll_Gesture_Should_Select_All_Nodes()
  465. {
  466. using (UnitTestApplication.Start())
  467. {
  468. var tree = CreateTestTreeData();
  469. var target = new TreeView
  470. {
  471. Template = CreateTreeViewTemplate(),
  472. Items = tree,
  473. SelectionMode = SelectionMode.Multiple
  474. };
  475. var visualRoot = new TestRoot();
  476. visualRoot.Child = target;
  477. CreateNodeDataTemplate(target);
  478. ApplyTemplates(target);
  479. var rootNode = tree[0];
  480. var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
  481. var selectAllGesture = keymap.SelectAll.First();
  482. var keyEvent = new KeyEventArgs
  483. {
  484. RoutedEvent = InputElement.KeyDownEvent,
  485. Key = selectAllGesture.Key,
  486. KeyModifiers = selectAllGesture.KeyModifiers
  487. };
  488. target.RaiseEvent(keyEvent);
  489. AssertChildrenSelected(target, rootNode);
  490. }
  491. }
  492. [Fact]
  493. public void Pressing_SelectAll_Gesture_With_Downward_Range_Selected_Should_Select_All_Nodes()
  494. {
  495. using (UnitTestApplication.Start())
  496. {
  497. var tree = CreateTestTreeData();
  498. var target = new TreeView
  499. {
  500. Template = CreateTreeViewTemplate(),
  501. Items = tree,
  502. SelectionMode = SelectionMode.Multiple
  503. };
  504. var visualRoot = new TestRoot();
  505. visualRoot.Child = target;
  506. CreateNodeDataTemplate(target);
  507. ApplyTemplates(target);
  508. var rootNode = tree[0];
  509. var from = rootNode.Children[0];
  510. var to = rootNode.Children.Last();
  511. var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
  512. var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
  513. ClickContainer(fromContainer, InputModifiers.None);
  514. ClickContainer(toContainer, InputModifiers.Shift);
  515. var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
  516. var selectAllGesture = keymap.SelectAll.First();
  517. var keyEvent = new KeyEventArgs
  518. {
  519. RoutedEvent = InputElement.KeyDownEvent,
  520. Key = selectAllGesture.Key,
  521. KeyModifiers = selectAllGesture.KeyModifiers
  522. };
  523. target.RaiseEvent(keyEvent);
  524. AssertChildrenSelected(target, rootNode);
  525. }
  526. }
  527. [Fact]
  528. public void Pressing_SelectAll_Gesture_With_Upward_Range_Selected_Should_Select_All_Nodes()
  529. {
  530. using (UnitTestApplication.Start())
  531. {
  532. var tree = CreateTestTreeData();
  533. var target = new TreeView
  534. {
  535. Template = CreateTreeViewTemplate(),
  536. Items = tree,
  537. SelectionMode = SelectionMode.Multiple
  538. };
  539. var visualRoot = new TestRoot();
  540. visualRoot.Child = target;
  541. CreateNodeDataTemplate(target);
  542. ApplyTemplates(target);
  543. var rootNode = tree[0];
  544. var from = rootNode.Children.Last();
  545. var to = rootNode.Children[0];
  546. var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
  547. var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
  548. ClickContainer(fromContainer, InputModifiers.None);
  549. ClickContainer(toContainer, InputModifiers.Shift);
  550. var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
  551. var selectAllGesture = keymap.SelectAll.First();
  552. var keyEvent = new KeyEventArgs
  553. {
  554. RoutedEvent = InputElement.KeyDownEvent,
  555. Key = selectAllGesture.Key,
  556. KeyModifiers = selectAllGesture.KeyModifiers
  557. };
  558. target.RaiseEvent(keyEvent);
  559. AssertChildrenSelected(target, rootNode);
  560. }
  561. }
  562. [Fact]
  563. public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection()
  564. {
  565. var tree = CreateTestTreeData();
  566. var target = new TreeView
  567. {
  568. Template = CreateTreeViewTemplate(),
  569. Items = tree,
  570. SelectionMode = SelectionMode.Multiple,
  571. };
  572. var visualRoot = new TestRoot();
  573. visualRoot.Child = target;
  574. CreateNodeDataTemplate(target);
  575. ApplyTemplates(target);
  576. target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
  577. target.SelectAll();
  578. AssertChildrenSelected(target, tree[0]);
  579. Assert.Equal(5, target.SelectedItems.Count);
  580. _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right);
  581. Assert.Equal(5, target.SelectedItems.Count);
  582. }
  583. [Fact]
  584. public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection()
  585. {
  586. var tree = CreateTestTreeData();
  587. var target = new TreeView
  588. {
  589. Template = CreateTreeViewTemplate(),
  590. Items = tree,
  591. SelectionMode = SelectionMode.Multiple,
  592. };
  593. var visualRoot = new TestRoot();
  594. visualRoot.Child = target;
  595. CreateNodeDataTemplate(target);
  596. ApplyTemplates(target);
  597. target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
  598. var rootNode = tree[0];
  599. var to = rootNode.Children[0];
  600. var then = rootNode.Children[1];
  601. var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(rootNode);
  602. var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
  603. var thenContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(then);
  604. ClickContainer(fromContainer, InputModifiers.None);
  605. ClickContainer(toContainer, InputModifiers.Shift);
  606. Assert.Equal(2, target.SelectedItems.Count);
  607. _mouse.Click(thenContainer, MouseButton.Right);
  608. Assert.Equal(1, target.SelectedItems.Count);
  609. }
  610. [Fact]
  611. public void Shift_Right_Click_Should_Not_Select_Multiple()
  612. {
  613. var tree = CreateTestTreeData();
  614. var target = new TreeView
  615. {
  616. Template = CreateTreeViewTemplate(),
  617. Items = tree,
  618. SelectionMode = SelectionMode.Multiple,
  619. };
  620. var visualRoot = new TestRoot();
  621. visualRoot.Child = target;
  622. CreateNodeDataTemplate(target);
  623. ApplyTemplates(target);
  624. target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
  625. var rootNode = tree[0];
  626. var from = rootNode.Children[0];
  627. var to = rootNode.Children[1];
  628. var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
  629. var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
  630. _mouse.Click(fromContainer);
  631. _mouse.Click(toContainer, MouseButton.Right, modifiers: InputModifiers.Shift);
  632. Assert.Equal(1, target.SelectedItems.Count);
  633. }
  634. [Fact]
  635. public void Ctrl_Right_Click_Should_Not_Select_Multiple()
  636. {
  637. var tree = CreateTestTreeData();
  638. var target = new TreeView
  639. {
  640. Template = CreateTreeViewTemplate(),
  641. Items = tree,
  642. SelectionMode = SelectionMode.Multiple,
  643. };
  644. var visualRoot = new TestRoot();
  645. visualRoot.Child = target;
  646. CreateNodeDataTemplate(target);
  647. ApplyTemplates(target);
  648. target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
  649. var rootNode = tree[0];
  650. var from = rootNode.Children[0];
  651. var to = rootNode.Children[1];
  652. var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
  653. var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
  654. _mouse.Click(fromContainer);
  655. _mouse.Click(toContainer, MouseButton.Right, modifiers: InputModifiers.Control);
  656. Assert.Equal(1, target.SelectedItems.Count);
  657. }
  658. [Fact]
  659. public void TreeViewItems_Level_Should_Be_Set()
  660. {
  661. var tree = CreateTestTreeData();
  662. var target = new TreeView
  663. {
  664. Template = CreateTreeViewTemplate(),
  665. Items = tree,
  666. };
  667. var visualRoot = new TestRoot();
  668. visualRoot.Child = target;
  669. CreateNodeDataTemplate(target);
  670. ApplyTemplates(target);
  671. ExpandAll(target);
  672. Assert.Equal(0, GetItem(target, 0).Level);
  673. Assert.Equal(1, GetItem(target, 0, 0).Level);
  674. Assert.Equal(1, GetItem(target, 0, 1).Level);
  675. Assert.Equal(1, GetItem(target, 0, 2).Level);
  676. Assert.Equal(2, GetItem(target, 0, 1, 0).Level);
  677. }
  678. [Fact]
  679. public void TreeViewItems_Level_Should_Be_Set_For_Derived_TreeView()
  680. {
  681. var tree = CreateTestTreeData();
  682. var target = new DerivedTreeView
  683. {
  684. Template = CreateTreeViewTemplate(),
  685. Items = tree,
  686. };
  687. var visualRoot = new TestRoot();
  688. visualRoot.Child = target;
  689. CreateNodeDataTemplate(target);
  690. ApplyTemplates(target);
  691. ExpandAll(target);
  692. Assert.Equal(0, GetItem(target, 0).Level);
  693. Assert.Equal(1, GetItem(target, 0, 0).Level);
  694. Assert.Equal(1, GetItem(target, 0, 1).Level);
  695. Assert.Equal(1, GetItem(target, 0, 2).Level);
  696. Assert.Equal(2, GetItem(target, 0, 1, 0).Level);
  697. }
  698. [Fact]
  699. public void Adding_Node_To_Removed_And_ReAdded_Parent_Should_Not_Crash()
  700. {
  701. // Issue #2985
  702. var tree = CreateTestTreeData();
  703. var target = new TreeView
  704. {
  705. Template = CreateTreeViewTemplate(),
  706. Items = tree,
  707. };
  708. var visualRoot = new TestRoot();
  709. visualRoot.Child = target;
  710. CreateNodeDataTemplate(target);
  711. ApplyTemplates(target);
  712. ExpandAll(target);
  713. var parent = tree[0];
  714. var node = parent.Children[1];
  715. parent.Children.Remove(node);
  716. parent.Children.Add(node);
  717. var item = target.ItemContainerGenerator.Index.ContainerFromItem(node);
  718. ApplyTemplates(new[] { item });
  719. // #2985 causes ArgumentException here.
  720. node.Children.Add(new Node());
  721. }
  722. [Fact]
  723. public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection()
  724. {
  725. /// Issue #2980.
  726. using (UnitTestApplication.Start(TestServices.RealStyler))
  727. {
  728. var target = new DerivedTreeView
  729. {
  730. Template = CreateTreeViewTemplate(),
  731. SelectionMode = SelectionMode.Multiple,
  732. Items = new List<Node>
  733. {
  734. new Node { Value = "Root1", },
  735. new Node { Value = "Root2", },
  736. },
  737. };
  738. var visualRoot = new TestRoot
  739. {
  740. Styles =
  741. {
  742. new Style(x => x.OfType<TreeViewItem>())
  743. {
  744. Setters =
  745. {
  746. new Setter(TreeViewItem.IsExpandedProperty, true),
  747. },
  748. },
  749. },
  750. Child = target,
  751. };
  752. CreateNodeDataTemplate(target);
  753. ApplyTemplates(target);
  754. _mouse.Click(GetItem(target, 0));
  755. _mouse.Click(GetItem(target, 1), modifiers: InputModifiers.Shift);
  756. }
  757. }
  758. [Fact]
  759. public void Removing_TreeView_From_Root_Should_Preserve_TreeViewItems()
  760. {
  761. // Issue #3328
  762. var tree = CreateTestTreeData();
  763. var target = new TreeView
  764. {
  765. Template = CreateTreeViewTemplate(),
  766. Items = tree,
  767. };
  768. var root = new TestRoot();
  769. root.Child = target;
  770. CreateNodeDataTemplate(target);
  771. ApplyTemplates(target);
  772. ExpandAll(target);
  773. Assert.Equal(5, target.ItemContainerGenerator.Index.Containers.Count());
  774. root.Child = null;
  775. Assert.Equal(5, target.ItemContainerGenerator.Index.Containers.Count());
  776. Assert.Equal(1, target.Presenter.Panel.Children.Count);
  777. var rootNode = Assert.IsType<TreeViewItem>(target.Presenter.Panel.Children[0]);
  778. Assert.Equal(3, rootNode.ItemContainerGenerator.Containers.Count());
  779. Assert.Equal(3, rootNode.Presenter.Panel.Children.Count);
  780. var child2Node = Assert.IsType<TreeViewItem>(rootNode.Presenter.Panel.Children[1]);
  781. Assert.Equal(1, child2Node.ItemContainerGenerator.Containers.Count());
  782. Assert.Equal(1, child2Node.Presenter.Panel.Children.Count);
  783. }
  784. private void ApplyTemplates(TreeView tree)
  785. {
  786. tree.ApplyTemplate();
  787. tree.Presenter.ApplyTemplate();
  788. ApplyTemplates(tree.Presenter.Panel.Children);
  789. }
  790. private void ApplyTemplates(IEnumerable<IControl> controls)
  791. {
  792. foreach (TreeViewItem control in controls)
  793. {
  794. control.Template = CreateTreeViewItemTemplate();
  795. control.ApplyTemplate();
  796. control.Presenter.ApplyTemplate();
  797. control.HeaderPresenter.ApplyTemplate();
  798. ApplyTemplates(control.Presenter.Panel.Children);
  799. }
  800. }
  801. private TreeViewItem GetItem(TreeView target, params int[] indexes)
  802. {
  803. var c = (ItemsControl)target;
  804. foreach (var index in indexes)
  805. {
  806. var item = ((IList)c.Items)[index];
  807. c = (ItemsControl)target.ItemContainerGenerator.Index.ContainerFromItem(item);
  808. }
  809. return (TreeViewItem)c;
  810. }
  811. private IList<Node> CreateTestTreeData()
  812. {
  813. return new AvaloniaList<Node>
  814. {
  815. new Node
  816. {
  817. Value = "Root",
  818. Children = new AvaloniaList<Node>
  819. {
  820. new Node
  821. {
  822. Value = "Child1",
  823. },
  824. new Node
  825. {
  826. Value = "Child2",
  827. Children = new AvaloniaList<Node>
  828. {
  829. new Node
  830. {
  831. Value = "Grandchild2a",
  832. },
  833. },
  834. },
  835. new Node
  836. {
  837. Value = "Child3",
  838. }
  839. }
  840. }
  841. };
  842. }
  843. private void CreateNodeDataTemplate(IControl control)
  844. {
  845. control.DataTemplates.Add(new TestTreeDataTemplate());
  846. }
  847. private IControlTemplate CreateTreeViewTemplate()
  848. {
  849. return new FuncControlTemplate<TreeView>((parent, scope) => new ItemsPresenter
  850. {
  851. Name = "PART_ItemsPresenter",
  852. [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
  853. }.RegisterInNameScope(scope));
  854. }
  855. private IControlTemplate CreateTreeViewItemTemplate()
  856. {
  857. return new FuncControlTemplate<TreeViewItem>((parent, scope) => new Panel
  858. {
  859. Children =
  860. {
  861. new ContentPresenter
  862. {
  863. Name = "PART_HeaderPresenter",
  864. [~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty],
  865. }.RegisterInNameScope(scope),
  866. new ItemsPresenter
  867. {
  868. Name = "PART_ItemsPresenter",
  869. [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
  870. }.RegisterInNameScope(scope)
  871. }
  872. });
  873. }
  874. private void ExpandAll(TreeView tree)
  875. {
  876. foreach (var i in tree.ItemContainerGenerator.Containers)
  877. {
  878. tree.ExpandSubTree((TreeViewItem)i.ContainerControl);
  879. }
  880. }
  881. private List<string> ExtractItemHeader(TreeView tree, int level)
  882. {
  883. return ExtractItemContent(tree.Presenter.Panel, 0, level)
  884. .Select(x => x.Header)
  885. .OfType<TextBlock>()
  886. .Select(x => x.Text)
  887. .ToList();
  888. }
  889. private IEnumerable<TreeViewItem> ExtractItemContent(IPanel panel, int currentLevel, int level)
  890. {
  891. foreach (TreeViewItem container in panel.Children)
  892. {
  893. if (container.Template == null)
  894. {
  895. container.Template = CreateTreeViewItemTemplate();
  896. container.ApplyTemplate();
  897. }
  898. if (currentLevel == level)
  899. {
  900. yield return container;
  901. }
  902. else
  903. {
  904. foreach (var child in ExtractItemContent(container.Presenter.Panel, currentLevel + 1, level))
  905. {
  906. yield return child;
  907. }
  908. }
  909. }
  910. }
  911. void ClickContainer(IControl container, InputModifiers modifiers)
  912. {
  913. _mouse.Click(container, modifiers: modifiers);
  914. }
  915. void AssertChildrenSelected(TreeView treeView, Node rootNode)
  916. {
  917. foreach (var child in rootNode.Children)
  918. {
  919. var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child);
  920. Assert.True(container.IsSelected);
  921. }
  922. }
  923. private class Node : NotifyingBase
  924. {
  925. private IAvaloniaList<Node> _children;
  926. public string Value { get; set; }
  927. public IAvaloniaList<Node> Children
  928. {
  929. get
  930. {
  931. return _children;
  932. }
  933. set
  934. {
  935. _children = value;
  936. RaisePropertyChanged(nameof(Children));
  937. }
  938. }
  939. }
  940. private class TestTreeDataTemplate : ITreeDataTemplate
  941. {
  942. public IControl Build(object param)
  943. {
  944. var node = (Node)param;
  945. return new TextBlock { Text = node.Value };
  946. }
  947. public bool SupportsRecycling => false;
  948. public InstancedBinding ItemsSelector(object item)
  949. {
  950. var obs = ExpressionObserver.Create(item, o => (o as Node).Children);
  951. return InstancedBinding.OneWay(obs);
  952. }
  953. public bool Match(object data)
  954. {
  955. return data is Node;
  956. }
  957. }
  958. private class DerivedTreeView : TreeView
  959. {
  960. }
  961. }
  962. }