SelectingItemsControlTests_Multiple.cs 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Collections.ObjectModel;
  7. using System.Linq;
  8. using Avalonia.Collections;
  9. using Avalonia.Controls.Presenters;
  10. using Avalonia.Controls.Primitives;
  11. using Avalonia.Controls.Templates;
  12. using Avalonia.Data;
  13. using Avalonia.Input;
  14. using Avalonia.Interactivity;
  15. using Avalonia.UnitTests;
  16. using Xunit;
  17. namespace Avalonia.Controls.UnitTests.Primitives
  18. {
  19. public class SelectingItemsControlTests_Multiple
  20. {
  21. private MouseTestHelper _helper = new MouseTestHelper();
  22. [Fact]
  23. public void Setting_SelectedIndex_Should_Add_To_SelectedItems()
  24. {
  25. var target = new TestSelector
  26. {
  27. Items = new[] { "foo", "bar" },
  28. Template = Template(),
  29. };
  30. target.ApplyTemplate();
  31. target.SelectedIndex = 1;
  32. Assert.Equal(new[] { "bar" }, target.SelectedItems.Cast<object>().ToList());
  33. }
  34. [Fact]
  35. public void Adding_SelectedItems_Should_Set_SelectedIndex()
  36. {
  37. var target = new TestSelector
  38. {
  39. Items = new[] { "foo", "bar" },
  40. Template = Template(),
  41. };
  42. target.ApplyTemplate();
  43. target.SelectedItems.Add("bar");
  44. Assert.Equal(1, target.SelectedIndex);
  45. }
  46. [Fact]
  47. public void Assigning_Single_SelectedItems_Should_Set_SelectedIndex()
  48. {
  49. var target = new TestSelector
  50. {
  51. Items = new[] { "foo", "bar" },
  52. Template = Template(),
  53. };
  54. target.ApplyTemplate();
  55. target.Presenter.ApplyTemplate();
  56. target.SelectedItems = new AvaloniaList<object>("bar");
  57. Assert.Equal(1, target.SelectedIndex);
  58. Assert.Equal(new[] { "bar" }, target.SelectedItems);
  59. Assert.Equal(new[] { 1 }, SelectedContainers(target));
  60. }
  61. [Fact]
  62. public void Assigning_Multiple_SelectedItems_Should_Set_SelectedIndex()
  63. {
  64. // Note that we don't need SelectionMode = Multiple here. Multiple selections can always
  65. // be made in code.
  66. var target = new TestSelector
  67. {
  68. Items = new[] { "foo", "bar", "baz" },
  69. Template = Template(),
  70. };
  71. target.ApplyTemplate();
  72. target.Presenter.ApplyTemplate();
  73. target.SelectedItems = new AvaloniaList<string>("foo", "bar", "baz");
  74. Assert.Equal(0, target.SelectedIndex);
  75. Assert.Equal(new[] { "foo", "bar", "baz" }, target.SelectedItems);
  76. Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target));
  77. }
  78. [Fact]
  79. public void Selected_Items_Should_Be_Marked_When_Panel_Created_After_SelectedItems_Is_Set()
  80. {
  81. // Issue #2565.
  82. var target = new TestSelector
  83. {
  84. Items = new[] { "foo", "bar", "baz" },
  85. Template = Template(),
  86. };
  87. target.ApplyTemplate();
  88. target.SelectedItems = new AvaloniaList<string>("foo", "bar", "baz");
  89. target.Presenter.ApplyTemplate();
  90. Assert.Equal(0, target.SelectedIndex);
  91. Assert.Equal(new[] { "foo", "bar", "baz" }, target.SelectedItems);
  92. Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target));
  93. }
  94. [Fact]
  95. public void Reassigning_SelectedItems_Should_Clear_Selection()
  96. {
  97. var target = new TestSelector
  98. {
  99. Items = new[] { "foo", "bar" },
  100. Template = Template(),
  101. };
  102. target.ApplyTemplate();
  103. target.SelectedItems.Add("bar");
  104. target.SelectedItems = new AvaloniaList<object>();
  105. Assert.Equal(-1, target.SelectedIndex);
  106. Assert.Null(target.SelectedItem);
  107. }
  108. [Fact]
  109. public void Adding_First_SelectedItem_Should_Raise_SelectedIndex_SelectedItem_Changed()
  110. {
  111. var target = new TestSelector
  112. {
  113. Items = new[] { "foo", "bar" },
  114. Template = Template(),
  115. };
  116. bool indexRaised = false;
  117. bool itemRaised = false;
  118. target.PropertyChanged += (s, e) =>
  119. {
  120. indexRaised |= e.Property.Name == "SelectedIndex" &&
  121. (int)e.OldValue == -1 &&
  122. (int)e.NewValue == 1;
  123. itemRaised |= e.Property.Name == "SelectedItem" &&
  124. (string)e.OldValue == null &&
  125. (string)e.NewValue == "bar";
  126. };
  127. target.ApplyTemplate();
  128. target.SelectedItems.Add("bar");
  129. Assert.True(indexRaised);
  130. Assert.True(itemRaised);
  131. }
  132. [Fact]
  133. public void Adding_Subsequent_SelectedItems_Should_Not_Raise_SelectedIndex_SelectedItem_Changed()
  134. {
  135. var target = new TestSelector
  136. {
  137. Items = new[] { "foo", "bar" },
  138. Template = Template(),
  139. };
  140. target.ApplyTemplate();
  141. target.SelectedItems.Add("foo");
  142. bool raised = false;
  143. target.PropertyChanged += (s, e) =>
  144. raised |= e.Property.Name == "SelectedIndex" ||
  145. e.Property.Name == "SelectedItem";
  146. target.SelectedItems.Add("bar");
  147. Assert.False(raised);
  148. }
  149. [Fact]
  150. public void Removing_Last_SelectedItem_Should_Raise_SelectedIndex_Changed()
  151. {
  152. var target = new TestSelector
  153. {
  154. Items = new[] { "foo", "bar" },
  155. Template = Template(),
  156. };
  157. target.ApplyTemplate();
  158. target.SelectedItems.Add("foo");
  159. bool raised = false;
  160. target.PropertyChanged += (s, e) =>
  161. raised |= e.Property.Name == "SelectedIndex" &&
  162. (int)e.OldValue == 0 &&
  163. (int)e.NewValue == -1;
  164. target.SelectedItems.RemoveAt(0);
  165. Assert.True(raised);
  166. }
  167. [Fact]
  168. public void Adding_SelectedItems_Should_Set_Item_IsSelected()
  169. {
  170. var items = new[]
  171. {
  172. new ListBoxItem(),
  173. new ListBoxItem(),
  174. new ListBoxItem(),
  175. };
  176. var target = new TestSelector
  177. {
  178. Items = items,
  179. Template = Template(),
  180. };
  181. target.ApplyTemplate();
  182. target.Presenter.ApplyTemplate();
  183. target.SelectedItems.Add(items[0]);
  184. target.SelectedItems.Add(items[1]);
  185. var foo = target.Presenter.Panel.Children[0];
  186. Assert.True(items[0].IsSelected);
  187. Assert.True(items[1].IsSelected);
  188. Assert.False(items[2].IsSelected);
  189. }
  190. [Fact]
  191. public void Assigning_SelectedItems_Should_Set_Item_IsSelected()
  192. {
  193. var items = new[]
  194. {
  195. new ListBoxItem(),
  196. new ListBoxItem(),
  197. new ListBoxItem(),
  198. };
  199. var target = new TestSelector
  200. {
  201. Items = items,
  202. Template = Template(),
  203. };
  204. target.ApplyTemplate();
  205. target.Presenter.ApplyTemplate();
  206. target.SelectedItems = new AvaloniaList<object> { items[0], items[1] };
  207. Assert.True(items[0].IsSelected);
  208. Assert.True(items[1].IsSelected);
  209. Assert.False(items[2].IsSelected);
  210. }
  211. [Fact]
  212. public void Removing_SelectedItems_Should_Clear_Item_IsSelected()
  213. {
  214. var items = new[]
  215. {
  216. new ListBoxItem(),
  217. new ListBoxItem(),
  218. new ListBoxItem(),
  219. };
  220. var target = new TestSelector
  221. {
  222. Items = items,
  223. Template = Template(),
  224. };
  225. target.ApplyTemplate();
  226. target.Presenter.ApplyTemplate();
  227. target.SelectedItems.Add(items[0]);
  228. target.SelectedItems.Add(items[1]);
  229. target.SelectedItems.Remove(items[1]);
  230. Assert.True(items[0].IsSelected);
  231. Assert.False(items[1].IsSelected);
  232. }
  233. [Fact]
  234. public void Reassigning_SelectedItems_Should_Clear_Item_IsSelected()
  235. {
  236. var items = new[]
  237. {
  238. new ListBoxItem(),
  239. new ListBoxItem(),
  240. new ListBoxItem(),
  241. };
  242. var target = new TestSelector
  243. {
  244. Items = items,
  245. Template = Template(),
  246. };
  247. target.ApplyTemplate();
  248. target.SelectedItems.Add(items[0]);
  249. target.SelectedItems.Add(items[1]);
  250. target.SelectedItems = new AvaloniaList<object> { items[0], items[1] };
  251. Assert.False(items[0].IsSelected);
  252. Assert.False(items[1].IsSelected);
  253. }
  254. [Fact]
  255. public void Setting_SelectedIndex_Should_Unmark_Previously_Selected_Containers()
  256. {
  257. var target = new TestSelector
  258. {
  259. Items = new[] { "foo", "bar", "baz" },
  260. Template = Template(),
  261. };
  262. target.ApplyTemplate();
  263. target.Presenter.ApplyTemplate();
  264. target.SelectedItems.Add("foo");
  265. target.SelectedItems.Add("bar");
  266. Assert.Equal(new[] { 0, 1 }, SelectedContainers(target));
  267. target.SelectedIndex = 2;
  268. Assert.Equal(new[] { 2 }, SelectedContainers(target));
  269. }
  270. [Fact]
  271. public void Range_Select_Should_Select_Range()
  272. {
  273. var target = new TestSelector
  274. {
  275. Items = new[]
  276. {
  277. "foo",
  278. "bar",
  279. "baz",
  280. "qux",
  281. "qiz",
  282. "lol",
  283. },
  284. SelectionMode = SelectionMode.Multiple,
  285. Template = Template(),
  286. };
  287. target.ApplyTemplate();
  288. target.SelectedIndex = 1;
  289. target.SelectRange(3);
  290. Assert.Equal(new[] { "bar", "baz", "qux" }, target.SelectedItems.Cast<object>().ToList());
  291. }
  292. [Fact]
  293. public void Range_Select_Backwards_Should_Select_Range()
  294. {
  295. var target = new TestSelector
  296. {
  297. Items = new[]
  298. {
  299. "foo",
  300. "bar",
  301. "baz",
  302. "qux",
  303. "qiz",
  304. "lol",
  305. },
  306. SelectionMode = SelectionMode.Multiple,
  307. Template = Template(),
  308. };
  309. target.ApplyTemplate();
  310. target.SelectedIndex = 3;
  311. target.SelectRange(1);
  312. Assert.Equal(new[] { "qux", "baz", "bar" }, target.SelectedItems.Cast<object>().ToList());
  313. }
  314. [Fact]
  315. public void Second_Range_Select_Backwards_Should_Select_From_Original_Selection()
  316. {
  317. var target = new TestSelector
  318. {
  319. Items = new[]
  320. {
  321. "foo",
  322. "bar",
  323. "baz",
  324. "qux",
  325. "qiz",
  326. "lol",
  327. },
  328. SelectionMode = SelectionMode.Multiple,
  329. Template = Template(),
  330. };
  331. target.ApplyTemplate();
  332. target.SelectedIndex = 2;
  333. target.SelectRange(5);
  334. target.SelectRange(4);
  335. Assert.Equal(new[] { "baz", "qux", "qiz" }, target.SelectedItems.Cast<object>().ToList());
  336. }
  337. [Fact]
  338. public void Setting_SelectedIndex_After_Range_Should_Unmark_Previously_Selected_Containers()
  339. {
  340. var target = new TestSelector
  341. {
  342. Items = new[] { "foo", "bar", "baz", "qux" },
  343. Template = Template(),
  344. SelectedIndex = 0,
  345. SelectionMode = SelectionMode.Multiple,
  346. };
  347. target.ApplyTemplate();
  348. target.Presenter.ApplyTemplate();
  349. target.SelectRange(2);
  350. Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target));
  351. target.SelectedIndex = 3;
  352. Assert.Equal(new[] { 3 }, SelectedContainers(target));
  353. }
  354. [Fact]
  355. public void Toggling_Selection_After_Range_Should_Work()
  356. {
  357. var target = new TestSelector
  358. {
  359. Items = new[] { "foo", "bar", "baz", "foo", "bar", "baz" },
  360. Template = Template(),
  361. SelectedIndex = 0,
  362. SelectionMode = SelectionMode.Multiple,
  363. };
  364. target.ApplyTemplate();
  365. target.Presenter.ApplyTemplate();
  366. target.SelectRange(3);
  367. Assert.Equal(new[] { 0, 1, 2, 3 }, SelectedContainers(target));
  368. target.Toggle(4);
  369. Assert.Equal(new[] { 0, 1, 2, 3, 4 }, SelectedContainers(target));
  370. }
  371. [Fact]
  372. public void Suprious_SelectedIndex_Changes_Should_Not_Be_Triggered()
  373. {
  374. var target = new TestSelector
  375. {
  376. Items = new[] { "foo", "bar", "baz" },
  377. Template = Template(),
  378. };
  379. target.ApplyTemplate();
  380. var selectedIndexes = new List<int>();
  381. target.GetObservable(TestSelector.SelectedIndexProperty).Subscribe(x => selectedIndexes.Add(x));
  382. target.SelectedItems = new AvaloniaList<object> { "bar", "baz" };
  383. target.SelectedItem = "foo";
  384. Assert.Equal(0, target.SelectedIndex);
  385. Assert.Equal(new[] { -1, 1, 0 }, selectedIndexes);
  386. }
  387. [Fact]
  388. public void Can_Set_SelectedIndex_To_Another_Selected_Item()
  389. {
  390. var target = new TestSelector
  391. {
  392. Items = new[] { "foo", "bar", "baz" },
  393. Template = Template(),
  394. };
  395. target.ApplyTemplate();
  396. target.Presenter.ApplyTemplate();
  397. target.SelectedItems.Add("foo");
  398. target.SelectedItems.Add("bar");
  399. Assert.Equal(0, target.SelectedIndex);
  400. Assert.Equal(new[] { "foo", "bar" }, target.SelectedItems);
  401. Assert.Equal(new[] { 0, 1 }, SelectedContainers(target));
  402. var raised = false;
  403. target.SelectionChanged += (s, e) =>
  404. {
  405. raised = true;
  406. Assert.Empty(e.AddedItems);
  407. Assert.Equal(new[] { "foo" }, e.RemovedItems);
  408. };
  409. target.SelectedIndex = 1;
  410. Assert.True(raised);
  411. Assert.Equal(1, target.SelectedIndex);
  412. Assert.Equal(new[] { "bar" }, target.SelectedItems);
  413. Assert.Equal(new[] { 1 }, SelectedContainers(target));
  414. }
  415. /// <summary>
  416. /// Tests a problem discovered with ListBox with selection.
  417. /// </summary>
  418. /// <remarks>
  419. /// - Items is bound to DataContext first, followed by say SelectedIndex
  420. /// - When the ListBox is removed from the visual tree, DataContext becomes null (as it's
  421. /// inherited)
  422. /// - This changes Items to null, which changes SelectedIndex to null as there are no
  423. /// longer any items
  424. /// - However, the news that DataContext is now null hasn't yet reached the SelectedItems
  425. /// binding and so the unselection is sent back to the ViewModel
  426. ///
  427. /// This is a similar problem to that tested by XamlBindingTest.Should_Not_Write_To_Old_DataContext.
  428. /// However, that tests a general property binding problem: here we are writing directly
  429. /// to the SelectedItems collection - not via a binding - so it's something that the
  430. /// binding system cannot solve. Instead we solve it by not clearing SelectedItems when
  431. /// DataContext is in the process of changing.
  432. /// </remarks>
  433. [Fact]
  434. public void Should_Not_Write_To_Old_DataContext()
  435. {
  436. var vm = new OldDataContextViewModel();
  437. var target = new TestSelector();
  438. var itemsBinding = new Binding
  439. {
  440. Path = "Items",
  441. Mode = BindingMode.OneWay,
  442. };
  443. var selectedItemsBinding = new Binding
  444. {
  445. Path = "SelectedItems",
  446. Mode = BindingMode.OneWay,
  447. };
  448. // Bind Items and SelectedItems to the VM.
  449. target.Bind(TestSelector.ItemsProperty, itemsBinding);
  450. target.Bind(TestSelector.SelectedItemsProperty, selectedItemsBinding);
  451. // Set DataContext and SelectedIndex
  452. target.DataContext = vm;
  453. target.SelectedIndex = 1;
  454. // Make sure SelectedItems are written back to VM.
  455. Assert.Equal(new[] { "bar" }, vm.SelectedItems);
  456. // Clear DataContext and ensure that SelectedItems is still set in the VM.
  457. target.DataContext = null;
  458. Assert.Equal(new[] { "bar" }, vm.SelectedItems);
  459. // Ensure target's SelectedItems is now clear.
  460. Assert.Empty(target.SelectedItems);
  461. }
  462. [Fact]
  463. public void Unbound_SelectedItems_Should_Be_Cleared_When_DataContext_Cleared()
  464. {
  465. var data = new
  466. {
  467. Items = new[] { "foo", "bar", "baz" },
  468. };
  469. var target = new TestSelector
  470. {
  471. DataContext = data,
  472. Template = Template(),
  473. };
  474. var itemsBinding = new Binding { Path = "Items" };
  475. target.Bind(TestSelector.ItemsProperty, itemsBinding);
  476. Assert.Same(data.Items, target.Items);
  477. target.SelectedItems.Add("bar");
  478. target.DataContext = null;
  479. Assert.Empty(target.SelectedItems);
  480. }
  481. [Fact]
  482. public void Adding_To_SelectedItems_Should_Raise_SelectionChanged()
  483. {
  484. var items = new[] { "foo", "bar", "baz" };
  485. var target = new TestSelector
  486. {
  487. DataContext = items,
  488. Template = Template(),
  489. Items = items,
  490. };
  491. var called = false;
  492. target.SelectionChanged += (s, e) =>
  493. {
  494. Assert.Equal(new[] { "bar" }, e.AddedItems.Cast<object>().ToList());
  495. Assert.Empty(e.RemovedItems);
  496. called = true;
  497. };
  498. target.SelectedItems.Add("bar");
  499. Assert.True(called);
  500. }
  501. [Fact]
  502. public void Removing_From_SelectedItems_Should_Raise_SelectionChanged()
  503. {
  504. var items = new[] { "foo", "bar", "baz" };
  505. var target = new TestSelector
  506. {
  507. Items = items,
  508. Template = Template(),
  509. SelectedItem = "bar",
  510. };
  511. var called = false;
  512. target.SelectionChanged += (s, e) =>
  513. {
  514. Assert.Equal(new[] { "bar" }, e.RemovedItems.Cast<object>().ToList());
  515. Assert.Empty(e.AddedItems);
  516. called = true;
  517. };
  518. target.SelectedItems.Remove("bar");
  519. Assert.True(called);
  520. }
  521. [Fact]
  522. public void Assigning_SelectedItems_Should_Raise_SelectionChanged()
  523. {
  524. var items = new[] { "foo", "bar", "baz" };
  525. var target = new TestSelector
  526. {
  527. Items = items,
  528. Template = Template(),
  529. SelectedItem = "bar",
  530. };
  531. var called = false;
  532. target.SelectionChanged += (s, e) =>
  533. {
  534. Assert.Equal(new[] { "foo", "baz" }, e.AddedItems.Cast<object>());
  535. Assert.Equal(new[] { "bar" }, e.RemovedItems.Cast<object>());
  536. called = true;
  537. };
  538. target.ApplyTemplate();
  539. target.Presenter.ApplyTemplate();
  540. target.SelectedItems = new AvaloniaList<object>("foo", "baz");
  541. Assert.True(called);
  542. }
  543. [Fact]
  544. public void Shift_Selecting_From_No_Selection_Selects_From_Start()
  545. {
  546. var target = new ListBox
  547. {
  548. Template = Template(),
  549. Items = new[] { "Foo", "Bar", "Baz" },
  550. SelectionMode = SelectionMode.Multiple,
  551. };
  552. target.ApplyTemplate();
  553. target.Presenter.ApplyTemplate();
  554. _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: InputModifiers.Shift);
  555. var panel = target.Presenter.Panel;
  556. Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems);
  557. Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target));
  558. }
  559. [Fact]
  560. public void Ctrl_Selecting_Raises_SelectionChanged_Events()
  561. {
  562. var target = new ListBox
  563. {
  564. Template = Template(),
  565. Items = new[] { "Foo", "Bar", "Baz", "Qux" },
  566. SelectionMode = SelectionMode.Multiple,
  567. };
  568. target.ApplyTemplate();
  569. target.Presenter.ApplyTemplate();
  570. SelectionChangedEventArgs receivedArgs = null;
  571. target.SelectionChanged += (_, args) => receivedArgs = null;
  572. void VerifyAdded(string selection)
  573. {
  574. Assert.NotNull(receivedArgs);
  575. Assert.Equal(new[] { selection }, receivedArgs.AddedItems);
  576. Assert.Empty(receivedArgs.RemovedItems);
  577. }
  578. void VerifyRemoved(string selection)
  579. {
  580. Assert.NotNull(receivedArgs);
  581. Assert.Equal(new[] { selection }, receivedArgs.RemovedItems);
  582. Assert.Empty(receivedArgs.AddedItems);
  583. }
  584. _helper.Click((Interactive)target.Presenter.Panel.Children[1]);
  585. VerifyAdded("Bar");
  586. receivedArgs = null;
  587. _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: InputModifiers.Control);
  588. VerifyAdded("Baz");
  589. receivedArgs = null;
  590. _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: InputModifiers.Control);
  591. VerifyAdded("Qux");
  592. receivedArgs = null;
  593. _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: InputModifiers.Control);
  594. VerifyRemoved("Bar");
  595. }
  596. [Fact]
  597. public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection()
  598. {
  599. var target = new ListBox
  600. {
  601. Template = Template(),
  602. Items = new[] { "Foo", "Bar", "Baz", "Qux" },
  603. SelectionMode = SelectionMode.Multiple,
  604. };
  605. target.ApplyTemplate();
  606. target.Presenter.ApplyTemplate();
  607. _helper.Click((Interactive)target.Presenter.Panel.Children[1]);
  608. _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: InputModifiers.Control);
  609. _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: InputModifiers.Control);
  610. Assert.Equal(1, target.SelectedIndex);
  611. Assert.Equal("Bar", target.SelectedItem);
  612. Assert.Equal(new[] { "Bar", "Baz", "Qux" }, target.SelectedItems);
  613. _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: InputModifiers.Control);
  614. Assert.Equal(2, target.SelectedIndex);
  615. Assert.Equal("Baz", target.SelectedItem);
  616. Assert.Equal(new[] { "Baz", "Qux" }, target.SelectedItems);
  617. }
  618. [Fact]
  619. public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same()
  620. {
  621. var target = new ListBox
  622. {
  623. Template = Template(),
  624. Items = new[] { "Foo", "Bar", "Baz" },
  625. SelectionMode = SelectionMode.Multiple,
  626. };
  627. target.ApplyTemplate();
  628. target.Presenter.ApplyTemplate();
  629. _helper.Click((Interactive)target.Presenter.Panel.Children[1]);
  630. _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: InputModifiers.Control);
  631. Assert.Equal(1, target.SelectedIndex);
  632. Assert.Equal("Bar", target.SelectedItem);
  633. _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: InputModifiers.Control);
  634. Assert.Equal(1, target.SelectedIndex);
  635. Assert.Equal("Bar", target.SelectedItem);
  636. }
  637. [Fact]
  638. public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present()
  639. {
  640. var target = new ListBox
  641. {
  642. Template = Template(),
  643. Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
  644. SelectionMode = SelectionMode.Multiple,
  645. };
  646. target.ApplyTemplate();
  647. target.Presenter.ApplyTemplate();
  648. _helper.Click((Interactive)target.Presenter.Panel.Children[3]);
  649. _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: InputModifiers.Control);
  650. var panel = target.Presenter.Panel;
  651. Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
  652. Assert.Equal(new[] { 3, 4 }, SelectedContainers(target));
  653. }
  654. [Fact]
  655. public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present()
  656. {
  657. var target = new ListBox
  658. {
  659. Template = Template(),
  660. Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
  661. SelectionMode = SelectionMode.Multiple,
  662. };
  663. target.ApplyTemplate();
  664. target.Presenter.ApplyTemplate();
  665. _helper.Click((Interactive)target.Presenter.Panel.Children[3]);
  666. _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: InputModifiers.Shift);
  667. var panel = target.Presenter.Panel;
  668. Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems);
  669. Assert.Equal(new[] { 3, 4, 5 }, SelectedContainers(target));
  670. }
  671. [Fact]
  672. public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present()
  673. {
  674. var target = new ListBox
  675. {
  676. Template = Template(),
  677. Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
  678. SelectionMode = SelectionMode.Multiple,
  679. };
  680. target.ApplyTemplate();
  681. target.Presenter.ApplyTemplate();
  682. _helper.Click((Interactive)target.Presenter.Panel.Children[0]);
  683. _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: InputModifiers.Shift);
  684. var panel = target.Presenter.Panel;
  685. Assert.Equal(new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, target.SelectedItems);
  686. Assert.Equal(new[] { 0, 1, 2, 3, 4, 5 }, SelectedContainers(target));
  687. }
  688. [Fact]
  689. public void Shift_Selecting_Raises_SelectionChanged_Events()
  690. {
  691. var target = new ListBox
  692. {
  693. Template = Template(),
  694. Items = new[] { "Foo", "Bar", "Baz", "Qux" },
  695. SelectionMode = SelectionMode.Multiple,
  696. };
  697. target.ApplyTemplate();
  698. target.Presenter.ApplyTemplate();
  699. SelectionChangedEventArgs receivedArgs = null;
  700. target.SelectionChanged += (_, args) => receivedArgs = null;
  701. void VerifyAdded(params string[] selection)
  702. {
  703. Assert.NotNull(receivedArgs);
  704. Assert.Equal(selection, receivedArgs.AddedItems);
  705. Assert.Empty(receivedArgs.RemovedItems);
  706. }
  707. void VerifyRemoved(string selection)
  708. {
  709. Assert.NotNull(receivedArgs);
  710. Assert.Equal(new[] { selection }, receivedArgs.RemovedItems);
  711. Assert.Empty(receivedArgs.AddedItems);
  712. }
  713. _helper.Click((Interactive)target.Presenter.Panel.Children[1]);
  714. VerifyAdded("Bar");
  715. receivedArgs = null;
  716. _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: InputModifiers.Shift);
  717. VerifyAdded("Baz" ,"Qux");
  718. receivedArgs = null;
  719. _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: InputModifiers.Shift);
  720. VerifyRemoved("Qux");
  721. }
  722. [Fact]
  723. public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order()
  724. {
  725. var target = new ListBox
  726. {
  727. Template = Template(),
  728. Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
  729. SelectionMode = SelectionMode.Multiple,
  730. };
  731. target.ApplyTemplate();
  732. target.Presenter.ApplyTemplate();
  733. _helper.Click((Interactive)target.Presenter.Panel.Children[0]);
  734. Assert.Equal(new[] { "Foo" }, target.SelectedItems);
  735. _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: InputModifiers.Control);
  736. Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
  737. _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: InputModifiers.Control);
  738. Assert.Equal(new[] { "Foo", "Bar", "Foo" }, target.SelectedItems);
  739. _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: InputModifiers.Control);
  740. Assert.Equal(new[] { "Foo", "Bar", "Foo", "Bar" }, target.SelectedItems);
  741. }
  742. [Fact]
  743. public void SelectAll_Sets_SelectedIndex_And_SelectedItem()
  744. {
  745. var target = new TestSelector
  746. {
  747. Template = Template(),
  748. Items = new[] { "Foo", "Bar", "Baz" },
  749. SelectionMode = SelectionMode.Multiple,
  750. };
  751. target.ApplyTemplate();
  752. target.Presenter.ApplyTemplate();
  753. target.SelectAll();
  754. Assert.Equal(0, target.SelectedIndex);
  755. Assert.Equal("Foo", target.SelectedItem);
  756. }
  757. [Fact]
  758. public void SelectAll_Raises_SelectionChanged_Event()
  759. {
  760. var target = new TestSelector
  761. {
  762. Template = Template(),
  763. Items = new[] { "Foo", "Bar", "Baz" },
  764. SelectionMode = SelectionMode.Multiple,
  765. };
  766. target.ApplyTemplate();
  767. target.Presenter.ApplyTemplate();
  768. SelectionChangedEventArgs receivedArgs = null;
  769. target.SelectionChanged += (_, e) => receivedArgs = e;
  770. target.SelectAll();
  771. Assert.NotNull(receivedArgs);
  772. Assert.Equal(target.Items, receivedArgs.AddedItems);
  773. Assert.Empty(receivedArgs.RemovedItems);
  774. }
  775. [Fact]
  776. public void UnselectAll_Clears_SelectedIndex_And_SelectedItem()
  777. {
  778. var target = new TestSelector
  779. {
  780. Template = Template(),
  781. Items = new[] { "Foo", "Bar", "Baz" },
  782. SelectionMode = SelectionMode.Multiple,
  783. SelectedIndex = 0,
  784. };
  785. target.ApplyTemplate();
  786. target.Presenter.ApplyTemplate();
  787. target.UnselectAll();
  788. Assert.Equal(-1, target.SelectedIndex);
  789. Assert.Equal(null, target.SelectedItem);
  790. }
  791. [Fact]
  792. public void SelectAll_Handles_Duplicate_Items()
  793. {
  794. var target = new TestSelector
  795. {
  796. Template = Template(),
  797. Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
  798. SelectionMode = SelectionMode.Multiple,
  799. };
  800. target.ApplyTemplate();
  801. target.Presenter.ApplyTemplate();
  802. target.SelectAll();
  803. Assert.Equal(new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, target.SelectedItems);
  804. }
  805. [Fact]
  806. public void Adding_Item_Before_SelectedItems_Should_Update_Selection()
  807. {
  808. var items = new ObservableCollection<string>
  809. {
  810. "Foo",
  811. "Bar",
  812. "Baz"
  813. };
  814. var target = new ListBox
  815. {
  816. Template = Template(),
  817. Items = items,
  818. SelectionMode = SelectionMode.Multiple,
  819. };
  820. target.ApplyTemplate();
  821. target.Presenter.ApplyTemplate();
  822. target.SelectAll();
  823. items.Insert(0, "Qux");
  824. Assert.Equal(1, target.SelectedIndex);
  825. Assert.Equal("Foo", target.SelectedItem);
  826. Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems);
  827. Assert.Equal(new[] { 1, 2, 3 }, SelectedContainers(target));
  828. }
  829. [Fact]
  830. public void Removing_Item_Before_SelectedItem_Should_Update_Selection()
  831. {
  832. var items = new ObservableCollection<string>
  833. {
  834. "Foo",
  835. "Bar",
  836. "Baz"
  837. };
  838. var target = new TestSelector
  839. {
  840. Template = Template(),
  841. Items = items,
  842. SelectionMode = SelectionMode.Multiple,
  843. };
  844. target.ApplyTemplate();
  845. target.Presenter.ApplyTemplate();
  846. target.SelectedIndex = 1;
  847. target.SelectRange(2);
  848. Assert.Equal(new[] { "Bar", "Baz" }, target.SelectedItems);
  849. items.RemoveAt(0);
  850. Assert.Equal(0, target.SelectedIndex);
  851. Assert.Equal("Bar", target.SelectedItem);
  852. Assert.Equal(new[] { "Bar", "Baz" }, target.SelectedItems);
  853. Assert.Equal(new[] { 0, 1 }, SelectedContainers(target));
  854. }
  855. [Fact]
  856. public void Removing_SelectedItem_With_Multiple_Selection_Active_Should_Update_Selection()
  857. {
  858. var items = new ObservableCollection<string>
  859. {
  860. "Foo",
  861. "Bar",
  862. "Baz"
  863. };
  864. var target = new ListBox
  865. {
  866. Template = Template(),
  867. Items = items,
  868. SelectionMode = SelectionMode.Multiple,
  869. };
  870. target.ApplyTemplate();
  871. target.Presenter.ApplyTemplate();
  872. target.SelectAll();
  873. items.RemoveAt(0);
  874. Assert.Equal(0, target.SelectedIndex);
  875. Assert.Equal("Bar", target.SelectedItem);
  876. Assert.Equal(new[] { "Bar", "Baz" }, target.SelectedItems);
  877. Assert.Equal(new[] { 0, 1 }, SelectedContainers(target));
  878. }
  879. [Fact]
  880. public void Replacing_Selected_Item_Should_Update_SelectedItems()
  881. {
  882. var items = new ObservableCollection<string>
  883. {
  884. "Foo",
  885. "Bar",
  886. "Baz"
  887. };
  888. var target = new ListBox
  889. {
  890. Template = Template(),
  891. Items = items,
  892. SelectionMode = SelectionMode.Multiple,
  893. };
  894. target.ApplyTemplate();
  895. target.Presenter.ApplyTemplate();
  896. target.SelectAll();
  897. items[1] = "Qux";
  898. Assert.Equal(new[] { "Foo", "Qux", "Baz" }, target.SelectedItems);
  899. }
  900. [Fact]
  901. public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection()
  902. {
  903. var target = new ListBox
  904. {
  905. Template = Template(),
  906. Items = new[] { "Foo", "Bar", "Baz" },
  907. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
  908. SelectionMode = SelectionMode.Multiple,
  909. };
  910. target.ApplyTemplate();
  911. target.Presenter.ApplyTemplate();
  912. target.SelectAll();
  913. Assert.Equal(3, target.SelectedItems.Count);
  914. _helper.Click((Interactive)target.Presenter.Panel.Children[0]);
  915. Assert.Equal(1, target.SelectedItems.Count);
  916. Assert.Equal(new[] { "Foo", }, target.SelectedItems);
  917. Assert.Equal(new[] { 0 }, SelectedContainers(target));
  918. }
  919. [Fact]
  920. public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection()
  921. {
  922. var target = new ListBox
  923. {
  924. Template = Template(),
  925. Items = new[] { "Foo", "Bar", "Baz" },
  926. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
  927. SelectionMode = SelectionMode.Multiple,
  928. };
  929. target.ApplyTemplate();
  930. target.Presenter.ApplyTemplate();
  931. target.SelectAll();
  932. Assert.Equal(3, target.SelectedItems.Count);
  933. _helper.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right);
  934. Assert.Equal(3, target.SelectedItems.Count);
  935. }
  936. [Fact]
  937. public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection()
  938. {
  939. var target = new ListBox
  940. {
  941. Template = Template(),
  942. Items = new[] { "Foo", "Bar", "Baz" },
  943. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
  944. SelectionMode = SelectionMode.Multiple,
  945. };
  946. target.ApplyTemplate();
  947. target.Presenter.ApplyTemplate();
  948. _helper.Click((Interactive)target.Presenter.Panel.Children[0]);
  949. _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: InputModifiers.Shift);
  950. Assert.Equal(2, target.SelectedItems.Count);
  951. _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right);
  952. Assert.Equal(1, target.SelectedItems.Count);
  953. }
  954. [Fact]
  955. public void Adding_Selected_ItemContainers_Should_Update_Selection()
  956. {
  957. var items = new AvaloniaList<ItemContainer>(new[]
  958. {
  959. new ItemContainer(),
  960. new ItemContainer(),
  961. });
  962. var target = new TestSelector
  963. {
  964. Items = items,
  965. SelectionMode = SelectionMode.Multiple,
  966. Template = Template(),
  967. };
  968. target.ApplyTemplate();
  969. target.Presenter.ApplyTemplate();
  970. items.Add(new ItemContainer { IsSelected = true });
  971. items.Add(new ItemContainer { IsSelected = true });
  972. Assert.Equal(2, target.SelectedIndex);
  973. Assert.Equal(items[2], target.SelectedItem);
  974. Assert.Equal(new[] { items[2], items[3] }, target.SelectedItems);
  975. }
  976. [Fact]
  977. public void Shift_Right_Click_Should_Not_Select_Multiple()
  978. {
  979. var target = new ListBox
  980. {
  981. Template = Template(),
  982. Items = new[] { "Foo", "Bar", "Baz" },
  983. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
  984. SelectionMode = SelectionMode.Multiple,
  985. };
  986. target.ApplyTemplate();
  987. target.Presenter.ApplyTemplate();
  988. _helper.Click((Interactive)target.Presenter.Panel.Children[0]);
  989. _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: InputModifiers.Shift);
  990. Assert.Equal(1, target.SelectedItems.Count);
  991. }
  992. [Fact]
  993. public void Ctrl_Right_Click_Should_Not_Select_Multiple()
  994. {
  995. var target = new ListBox
  996. {
  997. Template = Template(),
  998. Items = new[] { "Foo", "Bar", "Baz" },
  999. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
  1000. SelectionMode = SelectionMode.Multiple,
  1001. };
  1002. target.ApplyTemplate();
  1003. target.Presenter.ApplyTemplate();
  1004. _helper.Click((Interactive)target.Presenter.Panel.Children[0]);
  1005. _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: InputModifiers.Control);
  1006. Assert.Equal(1, target.SelectedItems.Count);
  1007. }
  1008. private IEnumerable<int> SelectedContainers(SelectingItemsControl target)
  1009. {
  1010. return target.Presenter.Panel.Children
  1011. .Select((x, i) => x.Classes.Contains(":selected") ? i : -1)
  1012. .Where(x => x != -1);
  1013. }
  1014. private FuncControlTemplate Template()
  1015. {
  1016. return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>
  1017. new ItemsPresenter
  1018. {
  1019. Name = "PART_ItemsPresenter",
  1020. [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
  1021. [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
  1022. }.RegisterInNameScope(scope));
  1023. }
  1024. private class TestSelector : SelectingItemsControl
  1025. {
  1026. public static readonly new AvaloniaProperty<IList> SelectedItemsProperty =
  1027. SelectingItemsControl.SelectedItemsProperty;
  1028. public new IList SelectedItems
  1029. {
  1030. get { return base.SelectedItems; }
  1031. set { base.SelectedItems = value; }
  1032. }
  1033. public new SelectionMode SelectionMode
  1034. {
  1035. get { return base.SelectionMode; }
  1036. set { base.SelectionMode = value; }
  1037. }
  1038. public new void SelectAll() => base.SelectAll();
  1039. public new void UnselectAll() => base.UnselectAll();
  1040. public void SelectRange(int index) => UpdateSelection(index, true, true);
  1041. public void Toggle(int index) => UpdateSelection(index, true, false, true);
  1042. }
  1043. private class OldDataContextViewModel
  1044. {
  1045. public OldDataContextViewModel()
  1046. {
  1047. Items = new List<string> { "foo", "bar" };
  1048. SelectedItems = new List<string>();
  1049. }
  1050. public List<string> Items { get; }
  1051. public List<string> SelectedItems { get; }
  1052. }
  1053. private class ItemContainer : Control, ISelectable
  1054. {
  1055. public string Value { get; set; }
  1056. public bool IsSelected { get; set; }
  1057. }
  1058. }
  1059. }