TreeViewTests.cs 37 KB

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