TreeViewTests.cs 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933
  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.Primitives;
  9. using Avalonia.Controls.Templates;
  10. using Avalonia.Data;
  11. using Avalonia.Data.Core;
  12. using Avalonia.Headless;
  13. using Avalonia.Input;
  14. using Avalonia.Input.Platform;
  15. using Avalonia.Layout;
  16. using Avalonia.LogicalTree;
  17. using Avalonia.Markup.Xaml.Templates;
  18. using Avalonia.Platform;
  19. using Avalonia.Styling;
  20. using Avalonia.UnitTests;
  21. using Avalonia.VisualTree;
  22. using Xunit;
  23. #nullable enable
  24. namespace Avalonia.Controls.UnitTests
  25. {
  26. public class TreeViewTests : ScopedTestBase
  27. {
  28. private readonly MouseTestHelper _mouse = new();
  29. [Fact]
  30. public void Items_Should_Be_Created()
  31. {
  32. using var app = Start();
  33. var target = CreateTarget();
  34. Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0));
  35. Assert.Equal(new[] { "Child1", "Child2", "Child3" }, ExtractItemHeader(target, 1));
  36. Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2));
  37. }
  38. [Fact]
  39. public void Items_Should_Be_Created_Using_ItemTemplate_If_Present()
  40. {
  41. using var app = Start();
  42. var itemTemplate = new FuncTreeDataTemplate<Node>(
  43. (_, _) => new Canvas(),
  44. x => x.Children);
  45. var target = CreateTarget(itemTemplate: itemTemplate);
  46. var items = target.GetRealizedTreeContainers()
  47. .OfType<TreeViewItem>()
  48. .ToList();
  49. Assert.Equal(5, items.Count);
  50. Assert.All(items, x => Assert.IsType<Canvas>(x.HeaderPresenter?.Child));
  51. }
  52. [Fact]
  53. public void Items_Should_Be_Created_Using_ItemTemplate_If_Present_2()
  54. {
  55. using var app = Start();
  56. var itemTemplate = new TreeDataTemplate
  57. {
  58. Content = (IServiceProvider? _) => new TemplateResult<Control>(new Canvas(), new NameScope()),
  59. ItemsSource = new Binding("Children"),
  60. };
  61. var target = CreateTarget(itemTemplate: itemTemplate);
  62. var items = target.GetRealizedTreeContainers()
  63. .OfType<TreeViewItem>()
  64. .ToList();
  65. Assert.Equal(5, items.Count);
  66. Assert.All(items, x => Assert.IsType<Canvas>(x.HeaderPresenter?.Child));
  67. }
  68. [Fact]
  69. public void Items_Should_Be_Created_Using_ItemConatinerTheme_If_Present()
  70. {
  71. var theme = CreateTreeViewItemControlTheme();
  72. var itemTemplate = new FuncTreeDataTemplate<Node>(
  73. (_, _) => new Canvas(),
  74. x => x.Children);
  75. var target = CreateTarget(
  76. itemContainerTheme: theme,
  77. itemTemplate: itemTemplate);
  78. var items = target.GetRealizedTreeContainers()
  79. .OfType<TreeViewItem>()
  80. .ToList();
  81. Assert.Equal(5, items.Count);
  82. Assert.All(items, x => Assert.Same(theme, x.ItemContainerTheme));
  83. }
  84. [Fact]
  85. public void Finds_Correct_DataTemplate_When_Application_DataTemplate_Is_Present()
  86. {
  87. // #10398
  88. using var app = Start();
  89. Avalonia.Application.Current!.DataTemplates.Add(new FuncDataTemplate<object>((x, _) => new Canvas()));
  90. AvaloniaLocator.CurrentMutable.Bind<IGlobalDataTemplates>().ToConstant(Avalonia.Application.Current);
  91. var target = CreateTarget();
  92. Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0));
  93. Assert.Equal(new[] { "Child1", "Child2", "Child3" }, ExtractItemHeader(target, 1));
  94. Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2));
  95. }
  96. [Fact]
  97. public void Root_ItemContainerGenerator_Containers_Should_Be_Root_Containers()
  98. {
  99. using var app = Start();
  100. var target = CreateTarget();
  101. var container = (TreeViewItem)target.GetRealizedContainers().Single();
  102. var header = Assert.IsType<TextBlock>(container.HeaderPresenter?.Child);
  103. Assert.Equal("Root", header?.Text);
  104. }
  105. [Fact]
  106. public void Root_TreeContainerFromItem_Should_Return_Descendant_Item()
  107. {
  108. using var app = Start();
  109. var data = CreateTestTreeData();
  110. var target = CreateTarget(data: data);
  111. var container = target.TreeContainerFromItem(data[0].Children[1].Children[0]);
  112. Assert.NotNull(container);
  113. var header = ((TreeViewItem)container).HeaderPresenter!;
  114. var headerContent = ((TextBlock)header.Child!).Text;
  115. Assert.Equal("Grandchild2a", headerContent);
  116. }
  117. [Fact]
  118. public void Clicking_Item_Should_Select_It()
  119. {
  120. using var app = Start();
  121. var data = CreateTestTreeData();
  122. var target = CreateTarget(data: data);
  123. var item = data[0].Children[1].Children[0];
  124. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  125. _mouse.Click(container);
  126. Assert.Equal(item, target.SelectedItem);
  127. Assert.True(container.IsSelected);
  128. }
  129. [Fact]
  130. public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_It()
  131. {
  132. using var app = Start();
  133. var data = CreateTestTreeData();
  134. var target = CreateTarget(data: data);
  135. var item = data[0].Children[1].Children[0];
  136. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  137. target.SelectedItem = item;
  138. Assert.True(container.IsSelected);
  139. _mouse.Click(container, modifiers: KeyModifiers.Control);
  140. Assert.Null(target.SelectedItem);
  141. Assert.False(container.IsSelected);
  142. }
  143. [Fact]
  144. public void Clicking_WithControlModifier_Not_Selected_Item_Should_Select_It()
  145. {
  146. using var app = Start();
  147. var data = CreateTestTreeData();
  148. var target = CreateTarget(data: data);
  149. var item1 = data[0].Children[1].Children[0];
  150. var container1 = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item1));
  151. var item2 = data[0].Children[1];
  152. var container2 = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item2));
  153. target.SelectedItem = item1;
  154. Assert.True(container1.IsSelected);
  155. _mouse.Click(container2, modifiers: KeyModifiers.Control);
  156. Assert.Equal(item2, target.SelectedItem);
  157. Assert.False(container1.IsSelected);
  158. Assert.True(container2.IsSelected);
  159. }
  160. [Fact]
  161. public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_And_Remove_From_SelectedItems()
  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 item1 = rootNode.Children[0];
  168. var item2 = rootNode.Children.Last();
  169. var item1Container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item1));
  170. var item2Container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item2));
  171. ClickContainer(item1Container, KeyModifiers.Control);
  172. Assert.True(item1Container.IsSelected);
  173. ClickContainer(item2Container, KeyModifiers.Control);
  174. Assert.True(item2Container.IsSelected);
  175. Assert.Equal(new[] { item1, item2 }, target.SelectedItems.OfType<Node>());
  176. ClickContainer(item1Container, KeyModifiers.Control);
  177. Assert.False(item1Container.IsSelected);
  178. Assert.DoesNotContain(item1, target.SelectedItems.OfType<Node>());
  179. }
  180. [Fact]
  181. public void Clicking_WithShiftModifier_DownDirection_Should_Select_Range_Of_Items()
  182. {
  183. using var app = Start();
  184. var data = CreateTestTreeData();
  185. var target = CreateTarget(data: data, multiSelect: true);
  186. var rootNode = data[0];
  187. var from = rootNode.Children[0];
  188. var to = rootNode.Children.Last();
  189. var fromContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(from));
  190. var toContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(to));
  191. ClickContainer(fromContainer, KeyModifiers.None);
  192. Assert.True(fromContainer.IsSelected);
  193. ClickContainer(toContainer, KeyModifiers.Shift);
  194. AssertAllChildContainersSelected(target, rootNode);
  195. }
  196. [Fact]
  197. public void Clicking_WithShiftModifier_UpDirection_Should_Select_Range_Of_Items()
  198. {
  199. using var app = Start();
  200. var data = CreateTestTreeData();
  201. var target = CreateTarget(data: data, multiSelect: true);
  202. var rootNode = data[0];
  203. var from = rootNode.Children.Last();
  204. var to = rootNode.Children[0];
  205. var fromContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(from));
  206. var toContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(to));
  207. ClickContainer(fromContainer, KeyModifiers.None);
  208. Assert.True(fromContainer.IsSelected);
  209. ClickContainer(toContainer, KeyModifiers.Shift);
  210. AssertAllChildContainersSelected(target, rootNode);
  211. }
  212. [Fact]
  213. public void Clicking_First_Item_Of_SelectedItems_Should_Select_Only_It()
  214. {
  215. using var app = Start();
  216. var data = CreateTestTreeData();
  217. var target = CreateTarget(data: data, multiSelect: true);
  218. var rootNode = data[0];
  219. var from = rootNode.Children.Last();
  220. var to = rootNode.Children[0];
  221. var fromContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(from));
  222. var toContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(to));
  223. ClickContainer(fromContainer, KeyModifiers.None);
  224. ClickContainer(toContainer, KeyModifiers.Shift);
  225. AssertAllChildContainersSelected(target, rootNode);
  226. ClickContainer(fromContainer, KeyModifiers.None);
  227. Assert.True(fromContainer.IsSelected);
  228. foreach (var child in rootNode.Children)
  229. {
  230. if (child == from)
  231. continue;
  232. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(child));
  233. Assert.False(container.IsSelected);
  234. }
  235. }
  236. [Fact]
  237. public void Double_Clicking_Item_Header_Should_Expand_It()
  238. {
  239. using var app = Start();
  240. var data = CreateTestTreeData();
  241. var target = CreateTarget(data: data);
  242. CollapseAll(target);
  243. var item = data[0].Children[1];
  244. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  245. var header = container.HeaderPresenter?.Child;
  246. Assert.False(container.IsExpanded);
  247. Assert.NotNull(header);
  248. _mouse.DoubleClick(header);
  249. Assert.True(container.IsExpanded);
  250. }
  251. [Fact]
  252. public void Double_Clicking_Item_Header_With_No_Children_Does_Not_Expand_It()
  253. {
  254. using var app = Start();
  255. {
  256. var data = CreateTestTreeData();
  257. var target = CreateTarget(data: data);
  258. CollapseAll(target);
  259. var item = data[0].Children[1].Children[0];
  260. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  261. var header = container.HeaderPresenter?.Child;
  262. Assert.False(container.IsExpanded);
  263. Assert.NotNull(header);
  264. _mouse.DoubleClick(header);
  265. Assert.False(container.IsExpanded);
  266. }
  267. }
  268. [Fact]
  269. public void Double_Clicking_Item_Header_Should_Collapse_It()
  270. {
  271. using var app = Start();
  272. var data = CreateTestTreeData();
  273. var target = CreateTarget(data: data);
  274. var item = data[0].Children[1];
  275. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  276. var header = container.HeaderPresenter?.Child;
  277. Assert.True(container.IsExpanded);
  278. Assert.NotNull(header);
  279. _mouse.DoubleClick(header);
  280. Assert.False(container.IsExpanded);
  281. }
  282. [Fact]
  283. public void Enter_Key_Should_Collapse_TreeViewItem()
  284. {
  285. using var app = Start();
  286. var data = CreateTestTreeData();
  287. var target = CreateTarget(data: data);
  288. var item = data[0];
  289. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  290. var header = container.HeaderPresenter?.Child;
  291. Assert.True(container.IsExpanded);
  292. Assert.NotNull(header);
  293. container.RaiseEvent(new KeyEventArgs
  294. {
  295. RoutedEvent = InputElement.KeyDownEvent,
  296. Key = Key.Enter,
  297. });
  298. Assert.False(container.IsExpanded);
  299. }
  300. [Fact]
  301. public void Enter_Plus_Ctrl_Key_Should_Collapse_TreeViewItem_Recursively()
  302. {
  303. using var app = Start();
  304. var data = CreateTestTreeData();
  305. var target = CreateTarget(data: data);
  306. var item = data[0];
  307. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  308. var header = container.HeaderPresenter?.Child;
  309. Assert.True(container.IsExpanded);
  310. Assert.NotNull(header);
  311. container.RaiseEvent(new KeyEventArgs
  312. {
  313. RoutedEvent = InputElement.KeyDownEvent,
  314. Key = Key.Enter,
  315. KeyModifiers = KeyModifiers.Control,
  316. });
  317. Assert.False(container.IsExpanded);
  318. AssertEachItemWithChildrenIsCollapsed(item);
  319. void AssertEachItemWithChildrenIsCollapsed(Node node)
  320. {
  321. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(node));
  322. if (node.Children?.Count > 0)
  323. {
  324. Assert.False(container.IsExpanded);
  325. foreach (var c in node.Children)
  326. {
  327. AssertEachItemWithChildrenIsCollapsed(c);
  328. }
  329. }
  330. else
  331. {
  332. Assert.True(container.IsExpanded);
  333. }
  334. }
  335. }
  336. [Fact]
  337. public void Enter_Key_Should_Expand_TreeViewItem()
  338. {
  339. using var app = Start();
  340. var data = CreateTestTreeData();
  341. var target = CreateTarget(data: data);
  342. CollapseAll(target);
  343. var item = data[0];
  344. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  345. var header = container.HeaderPresenter?.Child;
  346. Assert.False(container.IsExpanded);
  347. Assert.NotNull(header);
  348. container.RaiseEvent(new KeyEventArgs
  349. {
  350. RoutedEvent = InputElement.KeyDownEvent,
  351. Key = Key.Enter,
  352. });
  353. Assert.True(container.IsExpanded);
  354. }
  355. [Fact]
  356. public void Enter_Plus_Ctrl_Key_Should_Expand_TreeViewItem_Recursively()
  357. {
  358. using var app = Start();
  359. var data = CreateTestTreeData();
  360. var target = CreateTarget(data: data);
  361. CollapseAll(target);
  362. var item = data[0];
  363. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  364. var header = container.HeaderPresenter?.Child;
  365. Assert.False(container.IsExpanded);
  366. Assert.NotNull(header);
  367. container.RaiseEvent(new KeyEventArgs
  368. {
  369. RoutedEvent = InputElement.KeyDownEvent,
  370. Key = Key.Enter,
  371. KeyModifiers = KeyModifiers.Control,
  372. });
  373. Assert.True(container.IsExpanded);
  374. AssertEachItemWithChildrenIsExpanded(item);
  375. void AssertEachItemWithChildrenIsExpanded(Node node)
  376. {
  377. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(node));
  378. if (node.Children?.Count > 0)
  379. {
  380. Assert.True(container.IsExpanded);
  381. foreach (var c in node.Children)
  382. {
  383. AssertEachItemWithChildrenIsExpanded(c);
  384. }
  385. }
  386. else
  387. {
  388. Assert.False(container.IsExpanded);
  389. }
  390. }
  391. }
  392. [Fact]
  393. public void Space_Key_Should_Collapse_TreeViewItem()
  394. {
  395. using var app = Start();
  396. var data = CreateTestTreeData();
  397. var target = CreateTarget(data: data);
  398. var item = data[0];
  399. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  400. var header = container.HeaderPresenter?.Child;
  401. Assert.True(container.IsExpanded);
  402. Assert.NotNull(header);
  403. container.RaiseEvent(new KeyEventArgs
  404. {
  405. RoutedEvent = InputElement.KeyDownEvent,
  406. Key = Key.Enter,
  407. });
  408. Assert.False(container.IsExpanded);
  409. }
  410. [Fact]
  411. public void Space_Plus_Ctrl_Key_Should_Collapse_TreeViewItem_Recursively()
  412. {
  413. using var app = Start();
  414. var data = CreateTestTreeData();
  415. var target = CreateTarget(data: data);
  416. var item = data[0];
  417. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  418. var header = container.HeaderPresenter?.Child;
  419. Assert.True(container.IsExpanded);
  420. Assert.NotNull(header);
  421. container.RaiseEvent(new KeyEventArgs
  422. {
  423. RoutedEvent = InputElement.KeyDownEvent,
  424. Key = Key.Enter,
  425. KeyModifiers = KeyModifiers.Control,
  426. });
  427. Assert.False(container.IsExpanded);
  428. AssertEachItemWithChildrenIsCollapsed(item);
  429. void AssertEachItemWithChildrenIsCollapsed(Node node)
  430. {
  431. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(node));
  432. Assert.NotNull(container);
  433. if (node.Children?.Count > 0)
  434. {
  435. Assert.False(container.IsExpanded);
  436. foreach (var c in node.Children)
  437. {
  438. AssertEachItemWithChildrenIsCollapsed(c);
  439. }
  440. }
  441. else
  442. {
  443. Assert.True(container.IsExpanded);
  444. }
  445. }
  446. }
  447. [Fact]
  448. public void Space_Key_Should_Expand_TreeViewItem()
  449. {
  450. using var app = Start();
  451. {
  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. });
  465. Assert.True(container.IsExpanded);
  466. }
  467. }
  468. [Fact]
  469. public void Space_plus_Ctrl_Key_Should_Expand_TreeViewItem_Recursively()
  470. {
  471. using var app = Start();
  472. var data = CreateTestTreeData();
  473. var target = CreateTarget(data: data);
  474. CollapseAll(target);
  475. var item = data[0];
  476. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  477. var header = container.HeaderPresenter?.Child;
  478. Assert.False(container.IsExpanded);
  479. Assert.NotNull(header);
  480. container.RaiseEvent(new KeyEventArgs
  481. {
  482. RoutedEvent = InputElement.KeyDownEvent,
  483. Key = Key.Enter,
  484. KeyModifiers = KeyModifiers.Control,
  485. });
  486. Assert.True(container.IsExpanded);
  487. AssertEachItemWithChildrenIsExpanded(item);
  488. void AssertEachItemWithChildrenIsExpanded(Node node)
  489. {
  490. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(node));
  491. Assert.NotNull(container);
  492. if (node.Children?.Count > 0)
  493. {
  494. Assert.True(container.IsExpanded);
  495. foreach (var c in node.Children)
  496. {
  497. AssertEachItemWithChildrenIsExpanded(c);
  498. }
  499. }
  500. else
  501. {
  502. Assert.False(container.IsExpanded);
  503. }
  504. }
  505. }
  506. [Fact]
  507. public void Numpad_Star_Should_Expand_All_Children_Recursively()
  508. {
  509. using var app = Start();
  510. var data = CreateTestTreeData();
  511. var target = CreateTarget(data: data);
  512. CollapseAll(target);
  513. var item = data[0];
  514. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  515. Assert.NotNull(container);
  516. container.RaiseEvent(new KeyEventArgs
  517. {
  518. RoutedEvent = InputElement.KeyDownEvent,
  519. Key = Key.Multiply,
  520. });
  521. AssertEachItemWithChildrenIsExpanded(item);
  522. void AssertEachItemWithChildrenIsExpanded(Node node)
  523. {
  524. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(node));
  525. Assert.NotNull(container);
  526. if (node.Children?.Count > 0)
  527. {
  528. Assert.True(container.IsExpanded);
  529. foreach (var c in node.Children)
  530. {
  531. AssertEachItemWithChildrenIsExpanded(c);
  532. }
  533. }
  534. else
  535. {
  536. Assert.False(container.IsExpanded);
  537. }
  538. }
  539. }
  540. [Fact]
  541. public void Numpad_Slash_Should_Collapse_All_Children_Recursively()
  542. {
  543. using var app = Start();
  544. var data = CreateTestTreeData();
  545. var target = CreateTarget(data: data);
  546. var item = data[0];
  547. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  548. Assert.NotNull(container);
  549. container.RaiseEvent(new KeyEventArgs
  550. {
  551. RoutedEvent = InputElement.KeyDownEvent,
  552. Key = Key.Divide,
  553. });
  554. AssertEachItemWithChildrenIsCollapsed(item);
  555. void AssertEachItemWithChildrenIsCollapsed(Node node)
  556. {
  557. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(node));
  558. Assert.NotNull(container);
  559. if (node.Children?.Count > 0)
  560. {
  561. Assert.False(container.IsExpanded);
  562. foreach (var c in node.Children)
  563. {
  564. AssertEachItemWithChildrenIsCollapsed(c);
  565. }
  566. }
  567. else
  568. {
  569. Assert.True(container.IsExpanded);
  570. }
  571. }
  572. }
  573. [Fact]
  574. public void Setting_SelectedItem_Should_Set_Container_Selected()
  575. {
  576. using var app = Start();
  577. var data = CreateTestTreeData();
  578. var target = CreateTarget(data: data);
  579. var item = data[0].Children[1].Children[0];
  580. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  581. Assert.NotNull(container);
  582. target.SelectedItem = item;
  583. Assert.True(container.IsSelected);
  584. }
  585. [Fact]
  586. public void Setting_SelectedItem_Should_Raise_SelectedItemChanged_Event()
  587. {
  588. using var app = Start();
  589. var data = CreateTestTreeData();
  590. var target = CreateTarget(data: data);
  591. var item = data[0].Children[1].Children[0];
  592. var called = false;
  593. target.SelectionChanged += (s, e) =>
  594. {
  595. Assert.Empty(e.RemovedItems);
  596. Assert.Equal(1, e.AddedItems.Count);
  597. Assert.Same(item, e.AddedItems[0]);
  598. called = true;
  599. };
  600. target.SelectedItem = item;
  601. Assert.True(called);
  602. }
  603. [Fact]
  604. public void Removing_Selected_Root_Item_Should_Clear_Selection()
  605. {
  606. using var app = Start();
  607. var data = CreateTestTreeData();
  608. var target = CreateTarget(data: data);
  609. var item = data[0];
  610. target.SelectedItem = item;
  611. data.RemoveAt(0);
  612. Assert.Null(target.SelectedItem);
  613. Assert.Empty(target.SelectedItems);
  614. }
  615. [Fact]
  616. public void Resetting_Root_Items_Should_Clear_Selection()
  617. {
  618. using var app = Start();
  619. var data = CreateTestTreeData();
  620. var target = CreateTarget(data: data);
  621. var item = data[0];
  622. target.SelectedItem = item;
  623. data.Clear();
  624. Assert.Null(target.SelectedItem);
  625. Assert.Empty(target.SelectedItems);
  626. }
  627. [Fact]
  628. public void Removing_Selected_Child_Item_Should_Clear_Selection()
  629. {
  630. using var app = Start();
  631. var data = CreateTestTreeData();
  632. var target = CreateTarget(data: data);
  633. var item = data[0].Children[1];
  634. target.SelectedItem = item;
  635. data[0].Children.RemoveAt(1);
  636. Assert.Null(target.SelectedItem);
  637. Assert.Empty(target.SelectedItems);
  638. }
  639. [Fact]
  640. public void Replacing_Selected_Child_Item_Should_Clear_Selection()
  641. {
  642. using var app = Start();
  643. var data = CreateTestTreeData();
  644. var target = CreateTarget(data: data);
  645. var item = data[0].Children[1];
  646. target.SelectedItem = item;
  647. data[0].Children[1] = new Node();
  648. Assert.Null(target.SelectedItem);
  649. Assert.Empty(target.SelectedItems);
  650. }
  651. [Fact]
  652. public void Clearing_Child_Items_Should_Clear_Selection()
  653. {
  654. using var app = Start();
  655. var data = CreateTestTreeData();
  656. var target = CreateTarget(data: data);
  657. var item = data[0].Children[1];
  658. target.SelectedItem = item;
  659. data[0].Children.Clear();
  660. Assert.Null(target.SelectedItem);
  661. Assert.Empty(target.SelectedItems);
  662. }
  663. [Fact]
  664. public void SelectedItem_Should_Be_Valid_When_SelectedItemChanged_Event_Raised()
  665. {
  666. using var app = Start();
  667. var data = CreateTestTreeData();
  668. var target = CreateTarget(data: data);
  669. var item = data[0].Children[1].Children[0];
  670. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  671. Assert.NotNull(container);
  672. var called = false;
  673. target.SelectionChanged += (s, e) =>
  674. {
  675. Assert.Same(item, e.AddedItems[0]);
  676. Assert.Same(item, target.SelectedItem);
  677. called = true;
  678. };
  679. _mouse.Click(container);
  680. Assert.Equal(item, target.SelectedItem);
  681. Assert.True(container.IsSelected);
  682. Assert.True(called);
  683. }
  684. [Fact]
  685. public void Bound_SelectedItem_Should_Not_Be_Cleared_when_Changing_Selection()
  686. {
  687. using var app = Start();
  688. var dataContext = new TestDataContext();
  689. var target = CreateTarget();
  690. target.DataContext = dataContext;
  691. target.Bind(TreeView.ItemsSourceProperty, new Binding("Items"));
  692. target.Bind(TreeView.SelectedItemProperty, new Binding("SelectedItem"));
  693. var selectedValues = new List<object?>();
  694. dataContext.PropertyChanged += (_, e) =>
  695. {
  696. if (e.PropertyName == nameof(TestDataContext.SelectedItem))
  697. selectedValues.Add(dataContext.SelectedItem);
  698. };
  699. selectedValues.Add(dataContext.SelectedItem);
  700. _mouse.Click(target.Presenter!.Panel!.Children[0], MouseButton.Left);
  701. _mouse.Click(target.Presenter!.Panel!.Children[2], MouseButton.Left);
  702. Assert.Equal(3, selectedValues.Count);
  703. Assert.Equal(new[] { null, "Item 0", "Item 2" }, selectedValues.ToArray());
  704. }
  705. [Fact]
  706. public void Expanding_SelectedItem_To_Be_Visible_Should_Result_In_Selected_Container()
  707. {
  708. using var app = Start();
  709. var data = CreateTestTreeData();
  710. var target = CreateTarget(data: data, expandAll: false);
  711. target.SelectedItem = data[0].Children[1];
  712. var rootItem = Assert.IsType<TreeViewItem>(target.ContainerFromIndex(0));
  713. rootItem.IsExpanded = true;
  714. Layout(target);
  715. var container = Assert.IsType<TreeViewItem>(rootItem.ContainerFromIndex(1));
  716. Assert.True(container.IsSelected);
  717. }
  718. [Fact]
  719. public void LogicalChildren_Should_Be_Set()
  720. {
  721. using var app = Start();
  722. var target = CreateTarget(data: null);
  723. target.ItemsSource = new[] { "Foo", "Bar", "Baz " };
  724. Layout(target);
  725. var result = target.GetLogicalChildren()
  726. .OfType<TreeViewItem>()
  727. .Select(x => x.HeaderPresenter?.Child)
  728. .OfType<TextBlock>()
  729. .Select(x => x.Text)
  730. .ToList();
  731. Assert.Equal(new[] { "Foo", "Bar", "Baz " }, result);
  732. }
  733. [Fact]
  734. public void DataContexts_Should_Be_Correctly_Set()
  735. {
  736. using var app = Start();
  737. var items = new object[]
  738. {
  739. "Foo",
  740. new Node { Value = "Bar" },
  741. new TextBlock { Text = "Baz" },
  742. new TreeViewItem { Header = "Qux" },
  743. };
  744. var target = CreateTarget();
  745. var root = Assert.IsType<TestRoot>(target.GetVisualRoot());
  746. root.DataTemplates.Add(new FuncDataTemplate<Node>((x, _) => new Button { Content = x }));
  747. target.DataContext = "Base";
  748. target.ItemsSource = items;
  749. var dataContexts = target.Presenter!.Panel!.Children
  750. .Cast<Control>()
  751. .Select(x => x.DataContext)
  752. .ToList();
  753. Assert.Equal(
  754. new object[] { items[0], items[1], "Base", "Base" },
  755. dataContexts);
  756. }
  757. [Fact]
  758. public void Control_Item_Should_Not_Be_NameScope()
  759. {
  760. using var app = Start();
  761. var target = CreateTarget(data: null);
  762. var item = new TreeViewItem();
  763. target.Items!.Add(item);
  764. Assert.Same(item, target.LogicalChildren[0]);
  765. Assert.Null(NameScope.GetNameScope((TreeViewItem)item));
  766. }
  767. [Fact]
  768. public void Should_React_To_Children_Changing()
  769. {
  770. using var app = Start();
  771. var data = CreateTestTreeData();
  772. var target = CreateTarget(data: data);
  773. Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0));
  774. Assert.Equal(new[] { "Child1", "Child2", "Child3" }, ExtractItemHeader(target, 1));
  775. Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2));
  776. // Make sure that the binding to Node.Children does not get collected.
  777. GC.Collect();
  778. data[0].Children = new AvaloniaList<Node>
  779. {
  780. new Node
  781. {
  782. Value = "NewChild1",
  783. }
  784. };
  785. Layout(target);
  786. Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0));
  787. Assert.Equal(new[] { "NewChild1" }, ExtractItemHeader(target, 1));
  788. }
  789. [Fact]
  790. public void Keyboard_Navigation_Should_Move_To_Last_Selected_Node()
  791. {
  792. using var app = Start();
  793. var navigation = AvaloniaLocator.Current.GetRequiredService<IKeyboardNavigationHandler>();
  794. var data = CreateTestTreeData();
  795. var target = new TreeView
  796. {
  797. ItemsSource = data,
  798. };
  799. var button = new Button();
  800. var root = CreateRoot(new StackPanel
  801. {
  802. Children = { target, button },
  803. });
  804. var focus = root.FocusManager;
  805. root.LayoutManager.ExecuteInitialLayoutPass();
  806. ExpandAll(target);
  807. var item = data[0].Children[0];
  808. var node = target.TreeContainerFromItem(item);
  809. Assert.NotNull(node);
  810. target.SelectedItem = item;
  811. node.Focus();
  812. Assert.Same(node, focus.GetFocusedElement());
  813. navigation.Move(focus.GetFocusedElement()!, NavigationDirection.Next);
  814. Assert.Same(button, focus.GetFocusedElement());
  815. navigation.Move(focus.GetFocusedElement()!, NavigationDirection.Next);
  816. Assert.Same(node, focus.GetFocusedElement());
  817. }
  818. [Fact]
  819. public void Keyboard_Navigation_Should_Not_Crash_If_Selected_Item_Is_not_In_Tree()
  820. {
  821. using var app = Start();
  822. var data = CreateTestTreeData();
  823. var selectedNode = new Node { Value = "Out of Tree Selected Item" };
  824. var target = new TreeView
  825. {
  826. Template = CreateTreeViewTemplate(),
  827. ItemsSource = data,
  828. SelectedItem = selectedNode
  829. };
  830. var button = new Button();
  831. var root = CreateRoot(new StackPanel
  832. {
  833. Children = { target, button },
  834. });
  835. var focus = root.FocusManager;
  836. root.LayoutManager.ExecuteInitialLayoutPass();
  837. ExpandAll(target);
  838. var item = data[0].Children[0];
  839. var node = target.TreeContainerFromItem(item);
  840. Assert.NotNull(node);
  841. target.SelectedItem = selectedNode;
  842. node.Focus();
  843. Assert.Same(node, focus.GetFocusedElement());
  844. }
  845. [Fact]
  846. public void Pressing_SelectAll_Gesture_Should_Select_All_Nodes()
  847. {
  848. using var app = Start();
  849. var data = CreateTestTreeData();
  850. var target = CreateTarget(data: data, multiSelect: true);
  851. var rootNode = data[0];
  852. var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
  853. var selectAllGesture = keymap.SelectAll.First();
  854. var keyEvent = new KeyEventArgs
  855. {
  856. RoutedEvent = InputElement.KeyDownEvent,
  857. Key = selectAllGesture.Key,
  858. KeyModifiers = selectAllGesture.KeyModifiers
  859. };
  860. target.RaiseEvent(keyEvent);
  861. AssertAllChildContainersSelected(target, rootNode);
  862. }
  863. [Fact]
  864. public void Pressing_SelectAll_Gesture_With_Downward_Range_Selected_Should_Select_All_Nodes()
  865. {
  866. using var app = Start();
  867. var data = CreateTestTreeData();
  868. var target = CreateTarget(data: data, multiSelect: true);
  869. var rootNode = data[0];
  870. var from = rootNode.Children[0];
  871. var to = rootNode.Children.Last();
  872. var fromContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(from));
  873. var toContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(to));
  874. ClickContainer(fromContainer, KeyModifiers.None);
  875. ClickContainer(toContainer, KeyModifiers.Shift);
  876. var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
  877. var selectAllGesture = keymap.SelectAll.First();
  878. var keyEvent = new KeyEventArgs
  879. {
  880. RoutedEvent = InputElement.KeyDownEvent,
  881. Key = selectAllGesture.Key,
  882. KeyModifiers = selectAllGesture.KeyModifiers
  883. };
  884. target.RaiseEvent(keyEvent);
  885. AssertAllChildContainersSelected(target, rootNode);
  886. }
  887. [Fact]
  888. public void Pressing_SelectAll_Gesture_With_Upward_Range_Selected_Should_Select_All_Nodes()
  889. {
  890. using var app = Start();
  891. var data = CreateTestTreeData();
  892. var target = CreateTarget(data: data, multiSelect: true);
  893. var rootNode = data[0];
  894. var from = rootNode.Children.Last();
  895. var to = rootNode.Children[0];
  896. var fromContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(from));
  897. var toContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(to));
  898. ClickContainer(fromContainer, KeyModifiers.None);
  899. ClickContainer(toContainer, KeyModifiers.Shift);
  900. var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
  901. var selectAllGesture = keymap.SelectAll.First();
  902. var keyEvent = new KeyEventArgs
  903. {
  904. RoutedEvent = InputElement.KeyDownEvent,
  905. Key = selectAllGesture.Key,
  906. KeyModifiers = selectAllGesture.KeyModifiers
  907. };
  908. target.RaiseEvent(keyEvent);
  909. AssertAllChildContainersSelected(target, rootNode);
  910. }
  911. [Fact]
  912. public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection()
  913. {
  914. using var app = Start();
  915. var data = CreateTestTreeData();
  916. var target = CreateTarget(data: data, multiSelect: true);
  917. target.SelectAll();
  918. AssertAllChildContainersSelected(target, data[0]);
  919. Assert.Equal(5, target.SelectedItems.Count);
  920. _mouse.Click(target.Presenter!.Panel!.Children[0], MouseButton.Right);
  921. Assert.Equal(5, target.SelectedItems.Count);
  922. }
  923. [Fact]
  924. public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection()
  925. {
  926. using var app = Start();
  927. var data = CreateTestTreeData();
  928. var target = CreateTarget(data: data, multiSelect: true);
  929. var rootNode = data[0];
  930. var to = rootNode.Children[0];
  931. var then = rootNode.Children[1];
  932. var fromContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(rootNode));
  933. var toContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(to));
  934. var thenContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(then));
  935. ClickContainer(fromContainer, KeyModifiers.None);
  936. ClickContainer(toContainer, KeyModifiers.Shift);
  937. Assert.Equal(2, target.SelectedItems.Count);
  938. _mouse.Click(thenContainer, MouseButton.Right);
  939. Assert.Equal(1, target.SelectedItems.Count);
  940. }
  941. [Fact]
  942. public void Shift_Right_Click_Should_Not_Select_Multiple()
  943. {
  944. using var app = Start();
  945. var data = CreateTestTreeData();
  946. var target = CreateTarget(data: data, multiSelect: true);
  947. var rootNode = data[0];
  948. var from = rootNode.Children[0];
  949. var to = rootNode.Children[1];
  950. var fromContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(from));
  951. var toContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(to));
  952. _mouse.Click(fromContainer);
  953. _mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Shift);
  954. Assert.Equal(1, target.SelectedItems.Count);
  955. }
  956. [Fact]
  957. public void Ctrl_Right_Click_Should_Not_Select_Multiple()
  958. {
  959. using var app = Start();
  960. var data = CreateTestTreeData();
  961. var target = CreateTarget(data: data, multiSelect: true);
  962. var rootNode = data[0];
  963. var from = rootNode.Children[0];
  964. var to = rootNode.Children[1];
  965. var fromContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(from));
  966. var toContainer = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(to));
  967. _mouse.Click(fromContainer);
  968. _mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Control);
  969. Assert.Equal(1, target.SelectedItems.Count);
  970. }
  971. [Fact]
  972. public void TreeViewItems_Level_Should_Be_Set()
  973. {
  974. using var app = Start();
  975. var data = CreateTestTreeData();
  976. var target = CreateTarget(data: data);
  977. Assert.Equal(0, GetItem(target, 0).Level);
  978. Assert.Equal(1, GetItem(target, 0, 0).Level);
  979. Assert.Equal(1, GetItem(target, 0, 1).Level);
  980. Assert.Equal(1, GetItem(target, 0, 2).Level);
  981. Assert.Equal(2, GetItem(target, 0, 1, 0).Level);
  982. }
  983. [Fact]
  984. public void TreeViewItems_Level_Should_Be_Set_For_Derived_TreeView()
  985. {
  986. using var app = Start();
  987. var tree = CreateTestTreeData();
  988. var target = new DerivedTreeView
  989. {
  990. Template = CreateTreeViewTemplate(),
  991. ItemsSource = tree,
  992. };
  993. var root = CreateRoot(target);
  994. root.LayoutManager.ExecuteInitialLayoutPass();
  995. ExpandAll(target);
  996. Assert.Equal(0, GetItem(target, 0).Level);
  997. Assert.Equal(1, GetItem(target, 0, 0).Level);
  998. Assert.Equal(1, GetItem(target, 0, 1).Level);
  999. Assert.Equal(1, GetItem(target, 0, 2).Level);
  1000. Assert.Equal(2, GetItem(target, 0, 1, 0).Level);
  1001. }
  1002. [Fact]
  1003. public void Adding_Node_To_Removed_And_ReAdded_Parent_Should_Not_Crash()
  1004. {
  1005. // Issue #2985
  1006. using var app = Start();
  1007. var data = CreateTestTreeData();
  1008. var target = CreateTarget(data: data);
  1009. var parent = data[0];
  1010. var node = parent.Children[1];
  1011. parent.Children.Remove(node);
  1012. parent.Children.Add(node);
  1013. // #2985 causes ArgumentException here.
  1014. node.Children.Add(new Node());
  1015. }
  1016. [Fact]
  1017. public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection()
  1018. {
  1019. // Issue #2980.
  1020. using var app = Start();
  1021. var data = new List<Node>
  1022. {
  1023. new Node { Value = "Root1", },
  1024. new Node { Value = "Root2", },
  1025. };
  1026. var style = new Style(x => x.OfType<TreeViewItem>())
  1027. {
  1028. Setters =
  1029. {
  1030. new Setter(TreeViewItem.IsExpandedProperty, true),
  1031. },
  1032. };
  1033. var target = CreateTarget(data: data, styles: new[] { style }, multiSelect: true);
  1034. _mouse.Click(GetItem(target, 0));
  1035. _mouse.Click(GetItem(target, 1), modifiers: KeyModifiers.Shift);
  1036. }
  1037. [Fact]
  1038. public void Removing_TreeView_From_Root_Should_Preserve_TreeViewItems()
  1039. {
  1040. // Issue #3328
  1041. using var app = Start();
  1042. var data = CreateTestTreeData();
  1043. var target = CreateTarget(data: data);
  1044. var root = Assert.IsType<TestRoot>(target.GetVisualRoot());
  1045. Assert.Equal(5, target.GetRealizedTreeContainers().Count());
  1046. root.Child = null;
  1047. Assert.Equal(5, target.GetRealizedTreeContainers().Count());
  1048. Assert.Equal(1, target.Presenter!.Panel!.Children.Count);
  1049. var rootNode = Assert.IsType<TreeViewItem>(target.Presenter.Panel.Children[0]);
  1050. Assert.Equal(3, rootNode.GetRealizedContainers().Count());
  1051. Assert.Equal(3, rootNode.Presenter!.Panel!.Children.Count);
  1052. var child2Node = Assert.IsType<TreeViewItem>(rootNode.Presenter.Panel.Children[1]);
  1053. Assert.Equal(1, child2Node.GetRealizedContainers().Count());
  1054. Assert.Equal(1, child2Node.Presenter!.Panel!.Children.Count);
  1055. }
  1056. [Fact]
  1057. public void Clearing_TreeView_Items_Clears_Index()
  1058. {
  1059. // Issue #3551
  1060. using var app = Start();
  1061. var data = CreateTestTreeData();
  1062. var target = CreateTarget(data: data);
  1063. var root = Assert.IsType<TestRoot>(target.GetVisualRoot());
  1064. var rootNode = data[0];
  1065. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(rootNode));
  1066. Assert.NotNull(container);
  1067. root.Child = null;
  1068. data.Clear();
  1069. Assert.Empty(target.GetRealizedContainers());
  1070. }
  1071. [Fact]
  1072. public void Can_Use_Derived_TreeViewItem()
  1073. {
  1074. var tree = CreateTestTreeData();
  1075. var target = new DerivedTreeViewWithDerivedTreeViewItems
  1076. {
  1077. Template = CreateTreeViewTemplate(),
  1078. ItemsSource = tree,
  1079. };
  1080. var root = CreateRoot(target);
  1081. root.LayoutManager.ExecuteInitialLayoutPass();
  1082. ExpandAll(target);
  1083. // Verify that all items are DerivedTreeViewItem
  1084. foreach (var container in target.GetRealizedTreeContainers())
  1085. {
  1086. Assert.IsType<DerivedTreeViewItem>(container);
  1087. }
  1088. }
  1089. [Fact]
  1090. public void Can_Bind_Initial_Selected_State_Via_ItemContainerTheme()
  1091. {
  1092. using var app = Start();
  1093. var data = CreateTestTreeData();
  1094. var selected = new[] { data[0], data[0].Children[1] };
  1095. foreach (var node in selected)
  1096. node.IsSelected = true;
  1097. var itemTheme = new ControlTheme(typeof(TreeViewItem))
  1098. {
  1099. BasedOn = CreateTreeViewItemControlTheme(),
  1100. Setters =
  1101. {
  1102. new Setter(SelectingItemsControl.IsSelectedProperty, new Binding("IsSelected")),
  1103. }
  1104. };
  1105. var target = CreateTarget(data: data, itemContainerTheme: itemTheme, multiSelect: true);
  1106. AssertDataSelection(data, selected);
  1107. AssertContainerSelection(target, selected);
  1108. Assert.Equal(selected[0], target.SelectedItem);
  1109. Assert.Equal(selected, target.SelectedItems);
  1110. }
  1111. [Fact]
  1112. public void Can_Bind_Initial_Selected_State_Via_Style()
  1113. {
  1114. using var app = Start();
  1115. var data = CreateTestTreeData();
  1116. var selected = new[] { data[0], data[0].Children[1] };
  1117. foreach (var node in selected)
  1118. node.IsSelected = true;
  1119. var style = new Style(x => x.OfType<TreeViewItem>())
  1120. {
  1121. Setters =
  1122. {
  1123. new Setter(SelectingItemsControl.IsSelectedProperty, new Binding("IsSelected")),
  1124. }
  1125. };
  1126. var target = CreateTarget(data: data, multiSelect: true, styles: new[] { style });
  1127. AssertDataSelection(data, selected);
  1128. AssertContainerSelection(target, selected);
  1129. Assert.Equal(selected[0], target.SelectedItem);
  1130. Assert.Equal(selected, target.SelectedItems);
  1131. }
  1132. [Fact]
  1133. public void Selection_State_Is_Updated_Via_IsSelected_Binding()
  1134. {
  1135. using var app = Start();
  1136. var data = CreateTestTreeData();
  1137. var selected = new[] { data[0], data[0].Children[1] };
  1138. selected[0].IsSelected = true;
  1139. var itemTheme = new ControlTheme(typeof(TreeViewItem))
  1140. {
  1141. BasedOn = CreateTreeViewItemControlTheme(),
  1142. Setters =
  1143. {
  1144. new Setter(SelectingItemsControl.IsSelectedProperty, new Binding("IsSelected")),
  1145. }
  1146. };
  1147. var target = CreateTarget(data: data, itemContainerTheme: itemTheme, multiSelect: true);
  1148. selected[1].IsSelected = true;
  1149. AssertDataSelection(data, selected);
  1150. AssertContainerSelection(target, selected);
  1151. Assert.Equal(selected[0], target.SelectedItem);
  1152. Assert.Equal(selected, target.SelectedItems);
  1153. }
  1154. [Fact]
  1155. public void Selection_State_Is_Updated_Via_IsSelected_Binding_On_Expand()
  1156. {
  1157. using var app = Start();
  1158. var data = CreateTestTreeData();
  1159. var selected = new[] { data[0], data[0].Children[1] };
  1160. foreach (var node in selected)
  1161. node.IsSelected = true;
  1162. var itemTheme = new ControlTheme(typeof(TreeViewItem))
  1163. {
  1164. BasedOn = CreateTreeViewItemControlTheme(),
  1165. Setters =
  1166. {
  1167. new Setter(SelectingItemsControl.IsSelectedProperty, new Binding("IsSelected")),
  1168. }
  1169. };
  1170. var target = CreateTarget(
  1171. data: data,
  1172. expandAll: false,
  1173. itemContainerTheme: itemTheme,
  1174. multiSelect: true);
  1175. var rootContainer = Assert.IsType<TreeViewItem>(target.ContainerFromIndex(0));
  1176. // Root TreeViewItem isn't expanded so selection for child won't have been picked
  1177. // up by IsSelected binding yet.
  1178. AssertContainerSelection(target, new[] { selected[0] });
  1179. Assert.Equal(selected[0], target.SelectedItem);
  1180. Assert.Equal(new[] { selected[0] }, target.SelectedItems);
  1181. rootContainer.IsExpanded = true;
  1182. Layout(target);
  1183. // Root is expanded so now all expected items will be selected.
  1184. AssertDataSelection(data, selected);
  1185. AssertContainerSelection(target, selected);
  1186. Assert.Equal(selected[0], target.SelectedItem);
  1187. Assert.Equal(selected, target.SelectedItems);
  1188. }
  1189. [Fact]
  1190. public void Selection_State_Is_Updated_Via_IsSelected_Binding_On_Expand_Single_Select()
  1191. {
  1192. using var app = Start();
  1193. var data = CreateTestTreeData();
  1194. var selected = new[] { data[0], data[0].Children[1] };
  1195. foreach (var node in selected)
  1196. node.IsSelected = true;
  1197. var itemTheme = new ControlTheme(typeof(TreeViewItem))
  1198. {
  1199. BasedOn = CreateTreeViewItemControlTheme(),
  1200. Setters =
  1201. {
  1202. new Setter(SelectingItemsControl.IsSelectedProperty, new Binding("IsSelected")),
  1203. }
  1204. };
  1205. var target = CreateTarget(
  1206. data: data,
  1207. expandAll: false,
  1208. itemContainerTheme: itemTheme);
  1209. var rootContainer = Assert.IsType<TreeViewItem>(target.ContainerFromIndex(0));
  1210. // Root TreeViewItem isn't expanded so selection for child won't have been picked
  1211. // up by IsSelected binding yet.
  1212. AssertContainerSelection(target, new[] { selected[0] });
  1213. Assert.Equal(selected[0], target.SelectedItem);
  1214. Assert.Equal(new[] { selected[0] }, target.SelectedItems);
  1215. rootContainer.IsExpanded = true;
  1216. Layout(target);
  1217. // Root is expanded and newly revealed selected node will replace current selection
  1218. // given that we're in SelectionMode == Single.
  1219. selected = new[] { selected[1] };
  1220. AssertDataSelection(data, selected);
  1221. AssertContainerSelection(target, selected);
  1222. Assert.Equal(selected[0], target.SelectedItem);
  1223. Assert.Equal(selected, target.SelectedItems);
  1224. }
  1225. [Fact]
  1226. public void CollapseEvent_Can_Be_Captured_By_TreeView_When_Collapsing_TreeViewItem()
  1227. {
  1228. using var app = Start();
  1229. var data = CreateTestTreeData();
  1230. var target = CreateTarget(data: data);
  1231. var item = data[0];
  1232. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  1233. var raised = false;
  1234. object? source = null;
  1235. target.AddHandler(TreeViewItem.CollapsedEvent, (o, e) =>
  1236. {
  1237. raised = true;
  1238. source = e.Source;
  1239. });
  1240. container.IsExpanded = false;
  1241. Assert.True(raised);
  1242. Assert.Equal(container, source);
  1243. }
  1244. [Fact]
  1245. public void CollapseEvent_Should_Be_Raised_When_Collapsing_TreeViewItem()
  1246. {
  1247. using var app = Start();
  1248. var data = CreateTestTreeData();
  1249. var target = CreateTarget(data: data);
  1250. var item = data[0];
  1251. var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
  1252. var raised = false;
  1253. object? source = null;
  1254. container.AddHandler(TreeViewItem.CollapsedEvent, (o, e) =>
  1255. {
  1256. raised = true;
  1257. source = e.Source;
  1258. });
  1259. container.IsExpanded = false;
  1260. Assert.True(raised);
  1261. Assert.Equal(container, source);
  1262. }
  1263. private static TreeView CreateTarget(Optional<IList<Node>?> data = default,
  1264. bool expandAll = true,
  1265. ControlTheme? itemContainerTheme = null,
  1266. IDataTemplate? itemTemplate = null,
  1267. bool multiSelect = false,
  1268. IEnumerable<Style>? styles = null)
  1269. {
  1270. var target = new TreeView
  1271. {
  1272. ItemContainerTheme = itemContainerTheme,
  1273. ItemsSource = data.HasValue ? data.Value : CreateTestTreeData(),
  1274. ItemTemplate = itemTemplate,
  1275. SelectionMode = multiSelect ? SelectionMode.Multiple : SelectionMode.Single,
  1276. };
  1277. var root = CreateRoot(target);
  1278. if (styles is not null)
  1279. root.Styles.AddRange(styles);
  1280. root.LayoutManager.ExecuteInitialLayoutPass();
  1281. if (expandAll)
  1282. {
  1283. ExpandAll(target);
  1284. }
  1285. return target;
  1286. }
  1287. private static TestRoot CreateRoot(Control child)
  1288. {
  1289. return new TestRoot
  1290. {
  1291. Resources =
  1292. {
  1293. { typeof(TreeView), CreateTreeViewControlTheme() },
  1294. { typeof(TreeViewItem), CreateTreeViewItemControlTheme() },
  1295. },
  1296. DataTemplates =
  1297. {
  1298. new TreeDataTemplate
  1299. {
  1300. DataType = typeof(Node),
  1301. ItemsSource = new Binding(nameof(Node.Children)),
  1302. Content = (IServiceProvider? _) => new TemplateResult<Control>(
  1303. new TextBlock
  1304. {
  1305. [!TextBlock.TextProperty] = new Binding(nameof(Node.Value)),
  1306. },
  1307. new NameScope())
  1308. },
  1309. },
  1310. Child = child,
  1311. };
  1312. }
  1313. private static AvaloniaList<Node> CreateTestTreeData()
  1314. {
  1315. return new AvaloniaList<Node>
  1316. {
  1317. new Node
  1318. {
  1319. Value = "Root",
  1320. Children = new AvaloniaList<Node>
  1321. {
  1322. new Node
  1323. {
  1324. Value = "Child1",
  1325. },
  1326. new Node
  1327. {
  1328. Value = "Child2",
  1329. Children = new AvaloniaList<Node>
  1330. {
  1331. new Node
  1332. {
  1333. Value = "Grandchild2a",
  1334. },
  1335. },
  1336. },
  1337. new Node
  1338. {
  1339. Value = "Child3",
  1340. }
  1341. }
  1342. }
  1343. };
  1344. }
  1345. private static ControlTheme CreateTreeViewControlTheme()
  1346. {
  1347. return new ControlTheme(typeof(TreeView))
  1348. {
  1349. Setters =
  1350. {
  1351. new Setter(TreeView.TemplateProperty, CreateTreeViewTemplate()),
  1352. },
  1353. };
  1354. }
  1355. private static ControlTheme CreateTreeViewItemControlTheme()
  1356. {
  1357. return new ControlTheme(typeof(TreeViewItem))
  1358. {
  1359. Setters =
  1360. {
  1361. new Setter(TreeView.TemplateProperty, CreateTreeViewItemTemplate()),
  1362. },
  1363. };
  1364. }
  1365. private static IControlTemplate CreateTreeViewTemplate()
  1366. {
  1367. return new FuncControlTemplate<TreeView>((parent, scope) => new ItemsPresenter
  1368. {
  1369. Name = "PART_ItemsPresenter",
  1370. }.RegisterInNameScope(scope));
  1371. }
  1372. private static IControlTemplate CreateTreeViewItemTemplate()
  1373. {
  1374. return new FuncControlTemplate<TreeViewItem>((parent, scope) => new Panel
  1375. {
  1376. Children =
  1377. {
  1378. new Border
  1379. {
  1380. Name = "PART_Header",
  1381. Child = new ContentPresenter
  1382. {
  1383. Name = "PART_HeaderPresenter",
  1384. [~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty],
  1385. [~ContentPresenter.ContentTemplateProperty] = parent[~TreeViewItem.HeaderTemplateProperty],
  1386. }.RegisterInNameScope(scope)
  1387. }.RegisterInNameScope(scope),
  1388. new ItemsPresenter
  1389. {
  1390. Name = "PART_ItemsPresenter",
  1391. [~ItemsPresenter.IsVisibleProperty] = parent[~TreeViewItem.IsExpandedProperty],
  1392. }.RegisterInNameScope(scope)
  1393. }
  1394. });
  1395. }
  1396. private static void ExpandAll(TreeView tree)
  1397. {
  1398. foreach (var i in tree.GetRealizedContainers())
  1399. {
  1400. tree.ExpandSubTree((TreeViewItem)i);
  1401. }
  1402. }
  1403. private static void CollapseAll(TreeView tree)
  1404. {
  1405. foreach (var i in tree.GetRealizedContainers())
  1406. {
  1407. tree.CollapseSubTree((TreeViewItem)i);
  1408. }
  1409. }
  1410. private static TreeViewItem GetItem(TreeView target, params int[] indexes)
  1411. {
  1412. var c = (ItemsControl)target;
  1413. foreach (var index in indexes)
  1414. {
  1415. var item = c.ItemsView[index]!;
  1416. c = (ItemsControl)target.TreeContainerFromItem(item)!;
  1417. }
  1418. return (TreeViewItem)c;
  1419. }
  1420. private static List<string?> ExtractItemHeader(TreeView tree, int level)
  1421. {
  1422. return ExtractItemContent(tree.Presenter?.Panel, 0, level)
  1423. .Select(x => x.HeaderPresenter?.Child)
  1424. .OfType<TextBlock>()
  1425. .Select(x => x.Text)
  1426. .ToList();
  1427. }
  1428. private static IEnumerable<TreeViewItem> ExtractItemContent(Panel? panel, int currentLevel, int level)
  1429. {
  1430. if (panel is null)
  1431. yield break;
  1432. foreach (var c in panel.Children)
  1433. {
  1434. var container = Assert.IsType<TreeViewItem>(c);
  1435. if (currentLevel == level)
  1436. {
  1437. yield return container;
  1438. }
  1439. else if (container.Presenter?.Panel is { } childPanel)
  1440. {
  1441. foreach (var child in ExtractItemContent(childPanel, currentLevel + 1, level))
  1442. {
  1443. yield return child;
  1444. }
  1445. }
  1446. }
  1447. }
  1448. private void Layout(Control c)
  1449. {
  1450. (c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
  1451. }
  1452. private void ClickContainer(Control container, KeyModifiers modifiers)
  1453. {
  1454. _mouse.Click(container, modifiers: modifiers);
  1455. }
  1456. private void AssertContainerSelection(TreeView treeView, params Node[] expected)
  1457. {
  1458. static void Evaluate(Control container, HashSet<Node> remaining)
  1459. {
  1460. var treeViewItem = Assert.IsType<TreeViewItem>(container);
  1461. var node = (Node)container.DataContext!;
  1462. Assert.Equal(remaining.Contains(node), treeViewItem.IsSelected);
  1463. remaining.Remove(node);
  1464. foreach (var child in treeViewItem.GetRealizedContainers())
  1465. {
  1466. Evaluate(child, remaining);
  1467. }
  1468. }
  1469. var remaining = expected.ToHashSet();
  1470. foreach (var container in treeView.GetRealizedContainers())
  1471. Evaluate(container, remaining);
  1472. Assert.Empty(remaining);
  1473. }
  1474. private void AssertAllChildContainersSelected(TreeView treeView, Node node)
  1475. {
  1476. Assert.NotNull(node.Children);
  1477. foreach (var child in node.Children)
  1478. {
  1479. var container = Assert.IsType<TreeViewItem>(treeView.TreeContainerFromItem(child));
  1480. Assert.True(container.IsSelected);
  1481. }
  1482. }
  1483. private void AssertDataSelection(IEnumerable<Node> data, params Node[] expected)
  1484. {
  1485. static void Evaluate(Node rootNode, HashSet<Node> remaining)
  1486. {
  1487. Assert.Equal(remaining.Contains(rootNode), rootNode.IsSelected);
  1488. remaining.Remove(rootNode);
  1489. if (rootNode.Children is null)
  1490. return;
  1491. foreach (var child in rootNode.Children)
  1492. {
  1493. Evaluate(child, remaining);
  1494. }
  1495. }
  1496. var remaining = expected.ToHashSet();
  1497. foreach (var node in data)
  1498. Evaluate(node, remaining);
  1499. Assert.Empty(remaining);
  1500. }
  1501. private IDisposable Start()
  1502. {
  1503. return UnitTestApplication.Start(
  1504. TestServices.MockThreadingInterface.With(
  1505. fontManagerImpl: new HeadlessFontManagerStub(),
  1506. keyboardDevice: () => new KeyboardDevice(),
  1507. keyboardNavigation: () => new KeyboardNavigationHandler(),
  1508. inputManager: new InputManager(),
  1509. renderInterface: new HeadlessPlatformRenderInterface(),
  1510. textShaperImpl: new HeadlessTextShaperStub(),
  1511. assetLoader: new StandardAssetLoader()));
  1512. }
  1513. private class Node : NotifyingBase
  1514. {
  1515. private IAvaloniaList<Node> _children = new AvaloniaList<Node>();
  1516. private bool _isSelected;
  1517. public string? Value { get; set; }
  1518. public IAvaloniaList<Node> Children
  1519. {
  1520. get => _children;
  1521. set
  1522. {
  1523. _children = value;
  1524. RaisePropertyChanged(nameof(Children));
  1525. }
  1526. }
  1527. public bool IsSelected
  1528. {
  1529. get => _isSelected;
  1530. set
  1531. {
  1532. if (_isSelected != value)
  1533. {
  1534. _isSelected = value;
  1535. RaisePropertyChanged();
  1536. }
  1537. }
  1538. }
  1539. public override string ToString() => Value ?? string.Empty;
  1540. }
  1541. private class DerivedTreeView : TreeView
  1542. {
  1543. }
  1544. private class DerivedTreeViewWithDerivedTreeViewItems : TreeView
  1545. {
  1546. protected internal override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
  1547. {
  1548. return new DerivedTreeViewItem();
  1549. }
  1550. }
  1551. private class DerivedTreeViewItem : TreeViewItem
  1552. {
  1553. }
  1554. private class TestDataContext : INotifyPropertyChanged
  1555. {
  1556. private string? _selectedItem;
  1557. public TestDataContext()
  1558. {
  1559. Items = new ObservableCollection<string>(Enumerable.Range(0, 5).Select(i => $"Item {i}"));
  1560. }
  1561. public ObservableCollection<string>? Items { get; }
  1562. public string? SelectedItem
  1563. {
  1564. get { return _selectedItem; }
  1565. set
  1566. {
  1567. _selectedItem = value;
  1568. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
  1569. }
  1570. }
  1571. public event PropertyChangedEventHandler? PropertyChanged;
  1572. }
  1573. }
  1574. }