TreeViewTests.cs 52 KB

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