ListViewWithSelection.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Collections.ObjectModel;
  5. using System.Collections.Specialized;
  6. using System.ComponentModel;
  7. using System.Text;
  8. using JetBrains.Annotations;
  9. using Terminal.Gui;
  10. namespace UICatalog.Scenarios;
  11. [ScenarioMetadata ("List View With Selection", "ListView with columns and selection")]
  12. [ScenarioCategory ("Controls")]
  13. [ScenarioCategory ("ListView")]
  14. public class ListViewWithSelection : Scenario
  15. {
  16. private CheckBox _allowMarkingCB;
  17. private CheckBox _allowMultipleCB;
  18. private CheckBox _customRenderCB;
  19. private ListView _listView;
  20. private ObservableCollection<Scenario> _scenarios;
  21. private Window _appWindow;
  22. private ObservableCollection<string> _eventList = new ();
  23. private ListView _eventListView;
  24. /// <inheritdoc />
  25. public override void Main ()
  26. {
  27. Application.Init ();
  28. _appWindow = new ()
  29. {
  30. Title = GetQuitKeyAndName (),
  31. };
  32. _scenarios = GetScenarios ();
  33. _customRenderCB = new CheckBox { X = 0, Y = 0, Text = "Use custom _rendering" };
  34. _appWindow.Add (_customRenderCB);
  35. _customRenderCB.CheckedStateChanging += _customRenderCB_Toggle;
  36. _allowMarkingCB = new CheckBox
  37. {
  38. X = Pos.Right (_customRenderCB) + 1, Y = 0, Text = "Allow _Marking", AllowCheckStateNone = false
  39. };
  40. _appWindow.Add (_allowMarkingCB);
  41. _allowMarkingCB.CheckedStateChanging += AllowMarkingCB_Toggle;
  42. _allowMultipleCB = new CheckBox
  43. {
  44. X = Pos.Right (_allowMarkingCB) + 1,
  45. Y = 0,
  46. Visible = _allowMarkingCB.CheckedState == CheckState.Checked,
  47. Text = "Allow Multi-_Select"
  48. };
  49. _appWindow.Add (_allowMultipleCB);
  50. _allowMultipleCB.CheckedStateChanging += AllowMultipleCB_Toggle;
  51. _listView = new ListView
  52. {
  53. Title = "_ListView",
  54. X = 0,
  55. Y = Pos.Bottom(_allowMarkingCB),
  56. Height = Dim.Fill (),
  57. Width = Dim.Func (() => _listView?.MaxLength ?? 10),
  58. AllowsMarking = false,
  59. AllowsMultipleSelection = false
  60. };
  61. _listView.Border.Thickness = new Thickness (0, 1, 0, 0);
  62. _listView.RowRender += ListView_RowRender;
  63. _appWindow.Add (_listView);
  64. var scrollBar = new ScrollBarView (_listView, true);
  65. scrollBar.ChangedPosition += (s, e) =>
  66. {
  67. _listView.TopItem = scrollBar.Position;
  68. if (_listView.TopItem != scrollBar.Position)
  69. {
  70. scrollBar.Position = _listView.TopItem;
  71. }
  72. _listView.SetNeedsDisplay ();
  73. };
  74. scrollBar.OtherScrollBarView.ChangedPosition += (s, e) =>
  75. {
  76. _listView.LeftItem = scrollBar.OtherScrollBarView.Position;
  77. if (_listView.LeftItem != scrollBar.OtherScrollBarView.Position)
  78. {
  79. scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
  80. }
  81. _listView.SetNeedsDisplay ();
  82. };
  83. _listView.DrawContent += (s, e) =>
  84. {
  85. scrollBar.Size = _listView.Source.Count;
  86. scrollBar.Position = _listView.TopItem;
  87. scrollBar.OtherScrollBarView.Size = _listView.MaxLength;
  88. scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
  89. scrollBar.Refresh ();
  90. };
  91. _listView.SetSource (_scenarios);
  92. var k = "_Keep Content Always In Viewport";
  93. var keepCheckBox = new CheckBox
  94. {
  95. X = Pos.Right(_allowMultipleCB) + 1,
  96. Y = 0,
  97. Text = k,
  98. CheckedState = scrollBar.AutoHideScrollBars ? CheckState.Checked : CheckState.UnChecked
  99. };
  100. keepCheckBox.CheckedStateChanging += (s, e) => scrollBar.KeepContentAlwaysInViewport = e.NewValue == CheckState.Checked;
  101. _appWindow.Add (keepCheckBox);
  102. _eventList = new ();
  103. _eventListView = new ListView
  104. {
  105. X = Pos.Right (_listView) + 1,
  106. Y = Pos.Top (_listView),
  107. Width = Dim.Fill (),
  108. Height = Dim.Fill (),
  109. Source = new ListWrapper<string> (_eventList)
  110. };
  111. _eventListView.ColorScheme = Colors.ColorSchemes ["TopLevel"];
  112. _appWindow.Add (_eventListView);
  113. _listView.SelectedItemChanged += (s, a) => LogEvent (s as View, a, "SelectedItemChanged");
  114. _listView.OpenSelectedItem += (s, a) => LogEvent (s as View, a, "OpenSelectedItem");
  115. _listView.CollectionChanged += (s, a) => LogEvent (s as View, a, "CollectionChanged");
  116. _listView.Accepting += (s, a) => LogEvent (s as View, a, "Accept");
  117. _listView.Selecting += (s, a) => LogEvent (s as View, a, "Select");
  118. bool? LogEvent (View sender, EventArgs args, string message)
  119. {
  120. var msg = $"{message,-7}: {args}";
  121. _eventList.Add (msg);
  122. _eventListView.MoveDown ();
  123. return null;
  124. }
  125. Application.Run (_appWindow);
  126. _appWindow.Dispose ();
  127. Application.Shutdown ();
  128. }
  129. private void _customRenderCB_Toggle (object sender, CancelEventArgs<CheckState> stateEventArgs)
  130. {
  131. if (stateEventArgs.CurrentValue == CheckState.Checked)
  132. {
  133. _listView.SetSource (_scenarios);
  134. }
  135. else
  136. {
  137. _listView.Source = new ScenarioListDataSource (_scenarios);
  138. }
  139. _appWindow.SetNeedsDisplay ();
  140. }
  141. private void AllowMarkingCB_Toggle (object sender, [NotNull] CancelEventArgs<CheckState> stateEventArgs)
  142. {
  143. _listView.AllowsMarking = stateEventArgs.NewValue == CheckState.Checked;
  144. _allowMultipleCB.Visible = _listView.AllowsMarking;
  145. _appWindow.SetNeedsDisplay ();
  146. }
  147. private void AllowMultipleCB_Toggle (object sender, [NotNull] CancelEventArgs<CheckState> stateEventArgs)
  148. {
  149. _listView.AllowsMultipleSelection = stateEventArgs.NewValue == CheckState.Checked;
  150. _appWindow.SetNeedsDisplay ();
  151. }
  152. private void ListView_RowRender (object sender, ListViewRowEventArgs obj)
  153. {
  154. if (obj.Row == _listView.SelectedItem)
  155. {
  156. return;
  157. }
  158. if (_listView.AllowsMarking && _listView.Source.IsMarked (obj.Row))
  159. {
  160. obj.RowAttribute = new Attribute (Color.Black, Color.White);
  161. return;
  162. }
  163. if (obj.Row % 2 == 0)
  164. {
  165. obj.RowAttribute = new Attribute (Color.Green, Color.Black);
  166. }
  167. else
  168. {
  169. obj.RowAttribute = new Attribute (Color.Black, Color.Green);
  170. }
  171. }
  172. // This is basically the same implementation used by the UICatalog main window
  173. internal class ScenarioListDataSource : IListDataSource
  174. {
  175. private readonly int _nameColumnWidth = 30;
  176. private int _count;
  177. private BitArray _marks;
  178. private ObservableCollection<Scenario> _scenarios;
  179. public ScenarioListDataSource (ObservableCollection<Scenario> itemList) { Scenarios = itemList; }
  180. public ObservableCollection<Scenario> Scenarios
  181. {
  182. get => _scenarios;
  183. set
  184. {
  185. if (value != null)
  186. {
  187. _count = value.Count;
  188. _marks = new BitArray (_count);
  189. _scenarios = value;
  190. Length = GetMaxLengthItem ();
  191. }
  192. }
  193. }
  194. public bool IsMarked (int item)
  195. {
  196. if (item >= 0 && item < _count)
  197. {
  198. return _marks [item];
  199. }
  200. return false;
  201. }
  202. #pragma warning disable CS0067
  203. /// <inheritdoc />
  204. public event NotifyCollectionChangedEventHandler CollectionChanged;
  205. #pragma warning restore CS0067
  206. public int Count => Scenarios?.Count ?? 0;
  207. public int Length { get; private set; }
  208. public bool SuspendCollectionChangedEvent { get => throw new System.NotImplementedException (); set => throw new System.NotImplementedException (); }
  209. public void Render (
  210. ListView container,
  211. ConsoleDriver driver,
  212. bool selected,
  213. int item,
  214. int col,
  215. int line,
  216. int width,
  217. int start = 0
  218. )
  219. {
  220. container.Move (col, line);
  221. // Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
  222. string s = string.Format (
  223. string.Format ("{{0,{0}}}", -_nameColumnWidth),
  224. Scenarios [item].GetName ()
  225. );
  226. RenderUstr (driver, $"{s} ({Scenarios [item].GetDescription ()})", col, line, width, start);
  227. }
  228. public void SetMark (int item, bool value)
  229. {
  230. if (item >= 0 && item < _count)
  231. {
  232. _marks [item] = value;
  233. }
  234. }
  235. public IList ToList () { return Scenarios; }
  236. private int GetMaxLengthItem ()
  237. {
  238. if (_scenarios?.Count == 0)
  239. {
  240. return 0;
  241. }
  242. var maxLength = 0;
  243. for (var i = 0; i < _scenarios.Count; i++)
  244. {
  245. string s = string.Format (
  246. $"{{0,{-_nameColumnWidth}}}",
  247. Scenarios [i].GetName ()
  248. );
  249. var sc = $"{s} {Scenarios [i].GetDescription ()}";
  250. int l = sc.Length;
  251. if (l > maxLength)
  252. {
  253. maxLength = l;
  254. }
  255. }
  256. return maxLength;
  257. }
  258. // A slightly adapted method from: https://github.com/gui-cs/Terminal.Gui/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461
  259. private void RenderUstr (ConsoleDriver driver, string ustr, int col, int line, int width, int start = 0)
  260. {
  261. var used = 0;
  262. int index = start;
  263. while (index < ustr.Length)
  264. {
  265. (Rune rune, int size) = ustr.DecodeRune (index, index - ustr.Length);
  266. int count = rune.GetColumns ();
  267. if (used + count >= width)
  268. {
  269. break;
  270. }
  271. driver.AddRune (rune);
  272. used += count;
  273. index += size;
  274. }
  275. while (used < width)
  276. {
  277. driver.AddRune ((Rune)' ');
  278. used++;
  279. }
  280. }
  281. public void Dispose ()
  282. {
  283. _scenarios = null;
  284. }
  285. }
  286. }