ListViewWithSelection.cs 10 KB

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