TreeViewTests.cs 31 KB

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