ListViewWithSelection.cs 10 KB

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