TreeViewTests.cs 46 KB

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