TableEditor.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using Terminal.Gui;
  5. using System.Linq;
  6. using System.Globalization;
  7. namespace UICatalog.Scenarios {
  8. [ScenarioMetadata (Name: "TableEditor", Description: "A Terminal.Gui DataTable editor via TableView")]
  9. [ScenarioCategory ("Controls")]
  10. [ScenarioCategory ("Dialogs")]
  11. [ScenarioCategory ("Text")]
  12. [ScenarioCategory ("Dialogs")]
  13. [ScenarioCategory ("TopLevel")]
  14. public class TableEditor : Scenario
  15. {
  16. TableView tableView;
  17. private MenuItem miAlwaysShowHeaders;
  18. private MenuItem miHeaderOverline;
  19. private MenuItem miHeaderMidline;
  20. private MenuItem miHeaderUnderline;
  21. private MenuItem miCellLines;
  22. private MenuItem miFullRowSelect;
  23. private MenuItem miExpandLastColumn;
  24. public override void Setup ()
  25. {
  26. Win.Title = this.GetName();
  27. Win.Y = 1; // menu
  28. Win.Height = Dim.Fill (1); // status bar
  29. Top.LayoutSubviews ();
  30. this.tableView = new TableView () {
  31. X = 0,
  32. Y = 0,
  33. Width = Dim.Fill (),
  34. Height = Dim.Fill (1),
  35. };
  36. var menu = new MenuBar (new MenuBarItem [] {
  37. new MenuBarItem ("_File", new MenuItem [] {
  38. new MenuItem ("_OpenBigExample", "", () => OpenExample(true)),
  39. new MenuItem ("_OpenSmallExample", "", () => OpenExample(false)),
  40. new MenuItem ("_CloseExample", "", () => CloseExample()),
  41. new MenuItem ("_Quit", "", () => Quit()),
  42. }),
  43. new MenuBarItem ("_View", new MenuItem [] {
  44. miAlwaysShowHeaders = new MenuItem ("_AlwaysShowHeaders", "", () => ToggleAlwaysShowHeader()){Checked = tableView.Style.AlwaysShowHeaders, CheckType = MenuItemCheckStyle.Checked },
  45. miHeaderOverline = new MenuItem ("_HeaderOverLine", "", () => ToggleOverline()){Checked = tableView.Style.ShowHorizontalHeaderOverline, CheckType = MenuItemCheckStyle.Checked },
  46. miHeaderMidline = new MenuItem ("_HeaderMidLine", "", () => ToggleHeaderMidline()){Checked = tableView.Style.ShowVerticalHeaderLines, CheckType = MenuItemCheckStyle.Checked },
  47. miHeaderUnderline =new MenuItem ("_HeaderUnderLine", "", () => ToggleUnderline()){Checked = tableView.Style.ShowHorizontalHeaderUnderline, CheckType = MenuItemCheckStyle.Checked },
  48. miFullRowSelect =new MenuItem ("_FullRowSelect", "", () => ToggleFullRowSelect()){Checked = tableView.FullRowSelect, CheckType = MenuItemCheckStyle.Checked },
  49. miCellLines =new MenuItem ("_CellLines", "", () => ToggleCellLines()){Checked = tableView.Style.ShowVerticalCellLines, CheckType = MenuItemCheckStyle.Checked },
  50. miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn()){Checked = tableView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked },
  51. new MenuItem ("_AllLines", "", () => ToggleAllCellLines()),
  52. new MenuItem ("_NoLines", "", () => ToggleNoCellLines()),
  53. new MenuItem ("_ClearColumnStyles", "", () => ClearColumnStyles()),
  54. }),
  55. });
  56. Top.Add (menu);
  57. var statusBar = new StatusBar (new StatusItem [] {
  58. new StatusItem(Key.F2, "~F2~ OpenExample", () => OpenExample(true)),
  59. new StatusItem(Key.F3, "~F3~ CloseExample", () => CloseExample()),
  60. new StatusItem(Key.F4, "~F4~ OpenSimple", () => OpenSimple(true)),
  61. new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
  62. });
  63. Top.Add (statusBar);
  64. Win.Add (tableView);
  65. var selectedCellLabel = new Label(){
  66. X = 0,
  67. Y = Pos.Bottom(tableView),
  68. Text = "0,0",
  69. Width = Dim.Fill(),
  70. TextAlignment = TextAlignment.Right
  71. };
  72. Win.Add(selectedCellLabel);
  73. tableView.SelectedCellChanged += (e)=>{selectedCellLabel.Text = $"{tableView.SelectedRow},{tableView.SelectedColumn}";};
  74. tableView.CellActivated += EditCurrentCell;
  75. tableView.KeyPress += TableViewKeyPress;
  76. SetupScrollBar();
  77. }
  78. private void SetupScrollBar ()
  79. {
  80. var _scrollBar = new ScrollBarView (tableView, true);
  81. _scrollBar.ChangedPosition += () => {
  82. tableView.RowOffset = _scrollBar.Position;
  83. if (tableView.RowOffset != _scrollBar.Position) {
  84. _scrollBar.Position = tableView.RowOffset;
  85. }
  86. tableView.SetNeedsDisplay ();
  87. };
  88. /*
  89. _scrollBar.OtherScrollBarView.ChangedPosition += () => {
  90. _listView.LeftItem = _scrollBar.OtherScrollBarView.Position;
  91. if (_listView.LeftItem != _scrollBar.OtherScrollBarView.Position) {
  92. _scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
  93. }
  94. _listView.SetNeedsDisplay ();
  95. };*/
  96. tableView.DrawContent += (e) => {
  97. _scrollBar.Size = tableView.Table?.Rows?.Count ??0;
  98. _scrollBar.Position = tableView.RowOffset;
  99. // _scrollBar.OtherScrollBarView.Size = _listView.Maxlength - 1;
  100. // _scrollBar.OtherScrollBarView.Position = _listView.LeftItem;
  101. _scrollBar.Refresh ();
  102. };
  103. }
  104. private void TableViewKeyPress (View.KeyEventEventArgs e)
  105. {
  106. if(e.KeyEvent.Key == Key.DeleteChar){
  107. if(tableView.FullRowSelect)
  108. {
  109. // Delete button deletes all rows when in full row mode
  110. foreach(int toRemove in tableView.GetAllSelectedCells().Select(p=>p.Y).Distinct().OrderByDescending(i=>i))
  111. tableView.Table.Rows.RemoveAt(toRemove);
  112. }
  113. else{
  114. // otherwise set all selected cells to null
  115. foreach(var pt in tableView.GetAllSelectedCells())
  116. {
  117. tableView.Table.Rows[pt.Y][pt.X] = DBNull.Value;
  118. }
  119. }
  120. tableView.Update();
  121. e.Handled = true;
  122. }
  123. }
  124. private void ClearColumnStyles ()
  125. {
  126. tableView.Style.ColumnStyles.Clear();
  127. tableView.Update();
  128. }
  129. private void ToggleAlwaysShowHeader ()
  130. {
  131. miAlwaysShowHeaders.Checked = !miAlwaysShowHeaders.Checked;
  132. tableView.Style.AlwaysShowHeaders = miAlwaysShowHeaders.Checked;
  133. tableView.Update();
  134. }
  135. private void ToggleOverline ()
  136. {
  137. miHeaderOverline.Checked = !miHeaderOverline.Checked;
  138. tableView.Style.ShowHorizontalHeaderOverline = miHeaderOverline.Checked;
  139. tableView.Update();
  140. }
  141. private void ToggleHeaderMidline ()
  142. {
  143. miHeaderMidline.Checked = !miHeaderMidline.Checked;
  144. tableView.Style.ShowVerticalHeaderLines = miHeaderMidline.Checked;
  145. tableView.Update();
  146. }
  147. private void ToggleUnderline ()
  148. {
  149. miHeaderUnderline.Checked = !miHeaderUnderline.Checked;
  150. tableView.Style.ShowHorizontalHeaderUnderline = miHeaderUnderline.Checked;
  151. tableView.Update();
  152. }
  153. private void ToggleFullRowSelect ()
  154. {
  155. miFullRowSelect.Checked = !miFullRowSelect.Checked;
  156. tableView.FullRowSelect= miFullRowSelect.Checked;
  157. tableView.Update();
  158. }
  159. private void ToggleExpandLastColumn()
  160. {
  161. miExpandLastColumn.Checked = !miExpandLastColumn.Checked;
  162. tableView.Style.ExpandLastColumn = miExpandLastColumn.Checked;
  163. tableView.Update();
  164. }
  165. private void ToggleCellLines()
  166. {
  167. miCellLines.Checked = !miCellLines.Checked;
  168. tableView.Style.ShowVerticalCellLines = miCellLines.Checked;
  169. tableView.Update();
  170. }
  171. private void ToggleAllCellLines()
  172. {
  173. tableView.Style.ShowHorizontalHeaderOverline = true;
  174. tableView.Style.ShowVerticalHeaderLines = true;
  175. tableView.Style.ShowHorizontalHeaderUnderline = true;
  176. tableView.Style.ShowVerticalCellLines = true;
  177. miHeaderOverline.Checked = true;
  178. miHeaderMidline.Checked = true;
  179. miHeaderUnderline.Checked = true;
  180. miCellLines.Checked = true;
  181. tableView.Update();
  182. }
  183. private void ToggleNoCellLines()
  184. {
  185. tableView.Style.ShowHorizontalHeaderOverline = false;
  186. tableView.Style.ShowVerticalHeaderLines = false;
  187. tableView.Style.ShowHorizontalHeaderUnderline = false;
  188. tableView.Style.ShowVerticalCellLines = false;
  189. miHeaderOverline.Checked = false;
  190. miHeaderMidline.Checked = false;
  191. miHeaderUnderline.Checked = false;
  192. miCellLines.Checked = false;
  193. tableView.Update();
  194. }
  195. private void CloseExample ()
  196. {
  197. tableView.Table = null;
  198. }
  199. private void Quit ()
  200. {
  201. Application.RequestStop ();
  202. }
  203. private void OpenExample (bool big)
  204. {
  205. tableView.Table = BuildDemoDataTable(big ? 30 : 5, big ? 1000 : 5);
  206. SetDemoTableStyles();
  207. }
  208. private void SetDemoTableStyles ()
  209. {
  210. var alignMid = new TableView.ColumnStyle () {
  211. Alignment = TextAlignment.Centered
  212. };
  213. var alignRight = new TableView.ColumnStyle () {
  214. Alignment = TextAlignment.Right
  215. };
  216. var dateFormatStyle = new TableView.ColumnStyle () {
  217. Alignment = TextAlignment.Right,
  218. RepresentationGetter = (v)=> v is DateTime d ? d.ToString("yyyy-MM-dd"):v.ToString()
  219. };
  220. var negativeRight = new TableView.ColumnStyle () {
  221. Format = "0.##",
  222. MinWidth = 10,
  223. AlignmentGetter = (v)=>v is double d ?
  224. // align negative values right
  225. d < 0 ? TextAlignment.Right :
  226. // align positive values left
  227. TextAlignment.Left:
  228. // not a double
  229. TextAlignment.Left
  230. };
  231. tableView.Style.ColumnStyles.Add(tableView.Table.Columns["DateCol"],dateFormatStyle);
  232. tableView.Style.ColumnStyles.Add(tableView.Table.Columns["DoubleCol"],negativeRight);
  233. tableView.Style.ColumnStyles.Add(tableView.Table.Columns["NullsCol"],alignMid);
  234. tableView.Style.ColumnStyles.Add(tableView.Table.Columns["IntCol"],alignRight);
  235. tableView.Update();
  236. }
  237. private void OpenSimple (bool big)
  238. {
  239. tableView.Table = BuildSimpleDataTable(big ? 30 : 5, big ? 1000 : 5);
  240. }
  241. private void EditCurrentCell (TableView.CellActivatedEventArgs e)
  242. {
  243. if(e.Table == null)
  244. return;
  245. var oldValue = e.Table.Rows[e.Row][e.Col].ToString();
  246. bool okPressed = false;
  247. var ok = new Button ("Ok", is_default: true);
  248. ok.Clicked += () => { okPressed = true; Application.RequestStop (); };
  249. var cancel = new Button ("Cancel");
  250. cancel.Clicked += () => { Application.RequestStop (); };
  251. var d = new Dialog ("Enter new value", 60, 20, ok, cancel);
  252. var lbl = new Label() {
  253. X = 0,
  254. Y = 1,
  255. Text = e.Table.Columns[e.Col].ColumnName
  256. };
  257. var tf = new TextField()
  258. {
  259. Text = oldValue,
  260. X = 0,
  261. Y = 2,
  262. Width = Dim.Fill()
  263. };
  264. d.Add (lbl,tf);
  265. tf.SetFocus();
  266. Application.Run (d);
  267. if(okPressed) {
  268. try {
  269. e.Table.Rows[e.Row][e.Col] = string.IsNullOrWhiteSpace(tf.Text.ToString()) ? DBNull.Value : (object)tf.Text;
  270. }
  271. catch(Exception ex) {
  272. MessageBox.ErrorQuery(60,20,"Failed to set text", ex.Message,"Ok");
  273. }
  274. tableView.Update();
  275. }
  276. }
  277. /// <summary>
  278. /// Generates a new demo <see cref="DataTable"/> with the given number of <paramref name="cols"/> (min 5) and <paramref name="rows"/>
  279. /// </summary>
  280. /// <param name="cols"></param>
  281. /// <param name="rows"></param>
  282. /// <returns></returns>
  283. public static DataTable BuildDemoDataTable(int cols, int rows)
  284. {
  285. var dt = new DataTable();
  286. int explicitCols = 6;
  287. dt.Columns.Add(new DataColumn("StrCol",typeof(string)));
  288. dt.Columns.Add(new DataColumn("DateCol",typeof(DateTime)));
  289. dt.Columns.Add(new DataColumn("IntCol",typeof(int)));
  290. dt.Columns.Add(new DataColumn("DoubleCol",typeof(double)));
  291. dt.Columns.Add(new DataColumn("NullsCol",typeof(string)));
  292. dt.Columns.Add(new DataColumn("Unicode",typeof(string)));
  293. for(int i=0;i< cols -explicitCols; i++) {
  294. dt.Columns.Add("Column" + (i+explicitCols));
  295. }
  296. var r = new Random(100);
  297. for(int i=0;i< rows;i++) {
  298. List<object> row = new List<object>(){
  299. "Some long text that is super cool",
  300. new DateTime(2000+i,12,25),
  301. r.Next(i),
  302. (r.NextDouble()*i)-0.5 /*add some negatives to demo styles*/,
  303. DBNull.Value,
  304. "Les Mise" + Char.ConvertFromUtf32(Int32.Parse("0301", NumberStyles.HexNumber)) + "rables"
  305. };
  306. for(int j=0;j< cols -explicitCols; j++) {
  307. row.Add("SomeValue" + r.Next(100));
  308. }
  309. dt.Rows.Add(row.ToArray());
  310. }
  311. return dt;
  312. }
  313. /// <summary>
  314. /// Builds a simple table in which cell values contents are the index of the cell. This helps testing that scrolling etc is working correctly and not skipping out any rows/columns when paging
  315. /// </summary>
  316. /// <param name="cols"></param>
  317. /// <param name="rows"></param>
  318. /// <returns></returns>
  319. public static DataTable BuildSimpleDataTable(int cols, int rows)
  320. {
  321. var dt = new DataTable();
  322. for(int c = 0; c < cols; c++) {
  323. dt.Columns.Add("Col"+c);
  324. }
  325. for(int r = 0; r < rows; r++) {
  326. var newRow = dt.NewRow();
  327. for(int c = 0; c < cols; c++) {
  328. newRow[c] = $"R{r}C{c}";
  329. }
  330. dt.Rows.Add(newRow);
  331. }
  332. return dt;
  333. }
  334. }
  335. }