TreeViewTests.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176
  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. private void ApplyTemplates(TreeView tree)
  759. {
  760. tree.ApplyTemplate();
  761. tree.Presenter.ApplyTemplate();
  762. ApplyTemplates(tree.Presenter.Panel.Children);
  763. }
  764. private void ApplyTemplates(IEnumerable<IControl> controls)
  765. {
  766. foreach (TreeViewItem control in controls)
  767. {
  768. control.Template = CreateTreeViewItemTemplate();
  769. control.ApplyTemplate();
  770. control.Presenter.ApplyTemplate();
  771. control.HeaderPresenter.ApplyTemplate();
  772. ApplyTemplates(control.Presenter.Panel.Children);
  773. }
  774. }
  775. private TreeViewItem GetItem(TreeView target, params int[] indexes)
  776. {
  777. var c = (ItemsControl)target;
  778. foreach (var index in indexes)
  779. {
  780. var item = ((IList)c.Items)[index];
  781. c = (ItemsControl)target.ItemContainerGenerator.Index.ContainerFromItem(item);
  782. }
  783. return (TreeViewItem)c;
  784. }
  785. private IList<Node> CreateTestTreeData()
  786. {
  787. return new AvaloniaList<Node>
  788. {
  789. new Node
  790. {
  791. Value = "Root",
  792. Children = new AvaloniaList<Node>
  793. {
  794. new Node
  795. {
  796. Value = "Child1",
  797. },
  798. new Node
  799. {
  800. Value = "Child2",
  801. Children = new AvaloniaList<Node>
  802. {
  803. new Node
  804. {
  805. Value = "Grandchild2a",
  806. },
  807. },
  808. },
  809. new Node
  810. {
  811. Value = "Child3",
  812. }
  813. }
  814. }
  815. };
  816. }
  817. private void CreateNodeDataTemplate(IControl control)
  818. {
  819. control.DataTemplates.Add(new TestTreeDataTemplate());
  820. }
  821. private IControlTemplate CreateTreeViewTemplate()
  822. {
  823. return new FuncControlTemplate<TreeView>((parent, scope) => new ItemsPresenter
  824. {
  825. Name = "PART_ItemsPresenter",
  826. [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
  827. }.RegisterInNameScope(scope));
  828. }
  829. private IControlTemplate CreateTreeViewItemTemplate()
  830. {
  831. return new FuncControlTemplate<TreeViewItem>((parent, scope) => new Panel
  832. {
  833. Children =
  834. {
  835. new ContentPresenter
  836. {
  837. Name = "PART_HeaderPresenter",
  838. [~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty],
  839. }.RegisterInNameScope(scope),
  840. new ItemsPresenter
  841. {
  842. Name = "PART_ItemsPresenter",
  843. [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
  844. }.RegisterInNameScope(scope)
  845. }
  846. });
  847. }
  848. private void ExpandAll(TreeView tree)
  849. {
  850. foreach (var i in tree.ItemContainerGenerator.Containers)
  851. {
  852. tree.ExpandSubTree((TreeViewItem)i.ContainerControl);
  853. }
  854. }
  855. private List<string> ExtractItemHeader(TreeView tree, int level)
  856. {
  857. return ExtractItemContent(tree.Presenter.Panel, 0, level)
  858. .Select(x => x.Header)
  859. .OfType<TextBlock>()
  860. .Select(x => x.Text)
  861. .ToList();
  862. }
  863. private IEnumerable<TreeViewItem> ExtractItemContent(IPanel panel, int currentLevel, int level)
  864. {
  865. foreach (TreeViewItem container in panel.Children)
  866. {
  867. if (container.Template == null)
  868. {
  869. container.Template = CreateTreeViewItemTemplate();
  870. container.ApplyTemplate();
  871. }
  872. if (currentLevel == level)
  873. {
  874. yield return container;
  875. }
  876. else
  877. {
  878. foreach (var child in ExtractItemContent(container.Presenter.Panel, currentLevel + 1, level))
  879. {
  880. yield return child;
  881. }
  882. }
  883. }
  884. }
  885. void ClickContainer(IControl container, InputModifiers modifiers)
  886. {
  887. _mouse.Click(container, modifiers: modifiers);
  888. }
  889. void AssertChildrenSelected(TreeView treeView, Node rootNode)
  890. {
  891. foreach (var child in rootNode.Children)
  892. {
  893. var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child);
  894. Assert.True(container.IsSelected);
  895. }
  896. }
  897. private class Node : NotifyingBase
  898. {
  899. private IAvaloniaList<Node> _children;
  900. public string Value { get; set; }
  901. public IAvaloniaList<Node> Children
  902. {
  903. get
  904. {
  905. return _children;
  906. }
  907. set
  908. {
  909. _children = value;
  910. RaisePropertyChanged(nameof(Children));
  911. }
  912. }
  913. }
  914. private class TestTreeDataTemplate : ITreeDataTemplate
  915. {
  916. public IControl Build(object param)
  917. {
  918. var node = (Node)param;
  919. return new TextBlock { Text = node.Value };
  920. }
  921. public bool SupportsRecycling => false;
  922. public InstancedBinding ItemsSelector(object item)
  923. {
  924. var obs = ExpressionObserver.Create(item, o => (o as Node).Children);
  925. return InstancedBinding.OneWay(obs);
  926. }
  927. public bool Match(object data)
  928. {
  929. return data is Node;
  930. }
  931. }
  932. private class DerivedTreeView : TreeView
  933. {
  934. }
  935. }
  936. }