CsvEditor.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. using System;
  2. using System.Data;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text.RegularExpressions;
  7. using CsvHelper;
  8. namespace UICatalog.Scenarios;
  9. [ScenarioMetadata ("Csv Editor", "Open and edit simple CSV files using the TableView class.")]
  10. [ScenarioCategory ("TableView")]
  11. [ScenarioCategory ("TextView")]
  12. [ScenarioCategory ("Controls")]
  13. [ScenarioCategory ("Dialogs")]
  14. [ScenarioCategory ("Text and Formatting")]
  15. [ScenarioCategory ("Dialogs")]
  16. [ScenarioCategory ("Arrangement")]
  17. [ScenarioCategory ("Files and IO")]
  18. public class CsvEditor : Scenario
  19. {
  20. private string _currentFile;
  21. private DataTable _currentTable;
  22. private MenuItem _miCentered;
  23. private MenuItem _miLeft;
  24. private MenuItem _miRight;
  25. private TextField _selectedCellTextField;
  26. private TableView _tableView;
  27. public override void Main ()
  28. {
  29. // Init
  30. Application.Init ();
  31. // Setup - Create a top-level application window and configure it.
  32. Toplevel appWindow = new ()
  33. {
  34. Title = $"{GetName ()}"
  35. };
  36. //appWindow.Height = Dim.Fill (1); // status bar
  37. _tableView = new () { X = 0, Y = 1, Width = Dim.Fill (), Height = Dim.Fill (2) };
  38. var fileMenu = new MenuBarItem (
  39. "_File",
  40. new MenuItem []
  41. {
  42. new ("_Open CSV", "", () => Open ()),
  43. new ("_Save", "", () => Save ()),
  44. new ("_Quit", "Quits The App", () => Quit ())
  45. }
  46. );
  47. //fileMenu.Help = "Help";
  48. var menu = new MenuBar
  49. {
  50. Menus =
  51. [
  52. fileMenu,
  53. new (
  54. "_Edit",
  55. new MenuItem []
  56. {
  57. new ("_New Column", "", () => AddColumn ()),
  58. new ("_New Row", "", () => AddRow ()),
  59. new (
  60. "_Rename Column",
  61. "",
  62. () => RenameColumn ()
  63. ),
  64. new ("_Delete Column", "", () => DeleteColum ()),
  65. new ("_Move Column", "", () => MoveColumn ()),
  66. new ("_Move Row", "", () => MoveRow ()),
  67. new ("_Sort Asc", "", () => Sort (true)),
  68. new ("_Sort Desc", "", () => Sort (false))
  69. }
  70. ),
  71. new (
  72. "_View",
  73. new []
  74. {
  75. _miLeft = new (
  76. "_Align Left",
  77. "",
  78. () => Align (Alignment.Start)
  79. ),
  80. _miRight = new (
  81. "_Align Right",
  82. "",
  83. () => Align (Alignment.End)
  84. ),
  85. _miCentered = new (
  86. "_Align Centered",
  87. "",
  88. () => Align (Alignment.Center)
  89. ),
  90. // Format requires hard typed data table, when we read a CSV everything is untyped (string) so this only works for new columns in this demo
  91. _miCentered = new (
  92. "_Set Format Pattern",
  93. "",
  94. () => SetFormat ()
  95. )
  96. }
  97. )
  98. ]
  99. };
  100. appWindow.Add (menu);
  101. _selectedCellTextField = new ()
  102. {
  103. Text = "0,0",
  104. Width = 10,
  105. Height = 1
  106. };
  107. _selectedCellTextField.TextChanged += SelectedCellLabel_TextChanged;
  108. var statusBar = new StatusBar (
  109. [
  110. new (Application.QuitKey, "Quit", Quit, "Quit!"),
  111. new (Key.O.WithCtrl, "Open", Open, "Open a file."),
  112. new (Key.S.WithCtrl, "Save", Save, "Save current."),
  113. new ()
  114. {
  115. HelpText = "Cell:",
  116. CommandView = _selectedCellTextField,
  117. AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast,
  118. Enabled = false
  119. }
  120. ])
  121. {
  122. AlignmentModes = AlignmentModes.IgnoreFirstOrLast
  123. };
  124. appWindow.Add (statusBar);
  125. appWindow.Add (_tableView);
  126. _tableView.SelectedCellChanged += OnSelectedCellChanged;
  127. _tableView.CellActivated += EditCurrentCell;
  128. _tableView.KeyDown += TableViewKeyPress;
  129. //SetupScrollBar ();
  130. // Run - Start the application.
  131. Application.Run (appWindow);
  132. appWindow.Dispose ();
  133. // Shutdown - Calling Application.Shutdown is required.
  134. Application.Shutdown ();
  135. }
  136. private void AddColumn ()
  137. {
  138. if (NoTableLoaded ())
  139. {
  140. return;
  141. }
  142. if (GetText ("Enter column name", "Name:", "", out string colName))
  143. {
  144. var col = new DataColumn (colName);
  145. int newColIdx = Math.Min (
  146. Math.Max (0, _tableView.SelectedColumn + 1),
  147. _tableView.Table.Columns
  148. );
  149. int result = MessageBox.Query (
  150. "Column Type",
  151. "Pick a data type for the column",
  152. "Date",
  153. "Integer",
  154. "Double",
  155. "Text",
  156. "Cancel"
  157. );
  158. if (result <= -1 || result >= 4)
  159. {
  160. return;
  161. }
  162. switch (result)
  163. {
  164. case 0:
  165. col.DataType = typeof (DateTime);
  166. break;
  167. case 1:
  168. col.DataType = typeof (int);
  169. break;
  170. case 2:
  171. col.DataType = typeof (double);
  172. break;
  173. case 3:
  174. col.DataType = typeof (string);
  175. break;
  176. }
  177. _currentTable.Columns.Add (col);
  178. col.SetOrdinal (newColIdx);
  179. _tableView.Update ();
  180. }
  181. }
  182. private void AddRow ()
  183. {
  184. if (NoTableLoaded ())
  185. {
  186. return;
  187. }
  188. DataRow newRow = _currentTable.NewRow ();
  189. int newRowIdx = Math.Min (Math.Max (0, _tableView.SelectedRow + 1), _tableView.Table.Rows);
  190. _currentTable.Rows.InsertAt (newRow, newRowIdx);
  191. _tableView.Update ();
  192. }
  193. private void Align (Alignment newAlignment)
  194. {
  195. if (NoTableLoaded ())
  196. {
  197. return;
  198. }
  199. ColumnStyle style = _tableView.Style.GetOrCreateColumnStyle (_tableView.SelectedColumn);
  200. style.Alignment = newAlignment;
  201. _miLeft.Checked = style.Alignment == Alignment.Start;
  202. _miRight.Checked = style.Alignment == Alignment.End;
  203. _miCentered.Checked = style.Alignment == Alignment.Center;
  204. _tableView.Update ();
  205. }
  206. private void ClearColumnStyles ()
  207. {
  208. _tableView.Style.ColumnStyles.Clear ();
  209. _tableView.Update ();
  210. }
  211. private void CloseExample () { _tableView.Table = null; }
  212. private void DeleteColum ()
  213. {
  214. if (NoTableLoaded ())
  215. {
  216. return;
  217. }
  218. if (_tableView.SelectedColumn == -1)
  219. {
  220. MessageBox.ErrorQuery ("No Column", "No column selected", "Ok");
  221. return;
  222. }
  223. try
  224. {
  225. _currentTable.Columns.RemoveAt (_tableView.SelectedColumn);
  226. _tableView.Update ();
  227. }
  228. catch (Exception ex)
  229. {
  230. MessageBox.ErrorQuery ("Could not remove column", ex.Message, "Ok");
  231. }
  232. }
  233. private void EditCurrentCell (object sender, CellActivatedEventArgs e)
  234. {
  235. if (e.Table == null)
  236. {
  237. return;
  238. }
  239. var oldValue = _currentTable.Rows [e.Row] [e.Col].ToString ();
  240. if (GetText ("Enter new value", _currentTable.Columns [e.Col].ColumnName, oldValue, out string newText))
  241. {
  242. try
  243. {
  244. _currentTable.Rows [e.Row] [e.Col] =
  245. string.IsNullOrWhiteSpace (newText) ? DBNull.Value : newText;
  246. }
  247. catch (Exception ex)
  248. {
  249. MessageBox.ErrorQuery (60, 20, "Failed to set text", ex.Message, "Ok");
  250. }
  251. _tableView.Update ();
  252. }
  253. }
  254. private bool GetText (string title, string label, string initialText, out string enteredText)
  255. {
  256. var okPressed = false;
  257. var ok = new Button { Text = "Ok", IsDefault = true };
  258. ok.Accepting += (s, e) =>
  259. {
  260. okPressed = true;
  261. Application.RequestStop ();
  262. };
  263. var cancel = new Button { Text = "Cancel" };
  264. cancel.Accepting += (s, e) => { Application.RequestStop (); };
  265. var d = new Dialog { Title = title, Buttons = [ok, cancel] };
  266. var lbl = new Label { X = 0, Y = 1, Text = label };
  267. var tf = new TextField { Text = initialText, X = 0, Y = 2, Width = Dim.Fill () };
  268. d.Add (lbl, tf);
  269. tf.SetFocus ();
  270. Application.Run (d);
  271. d.Dispose ();
  272. enteredText = okPressed ? tf.Text : null;
  273. return okPressed;
  274. }
  275. private void MoveColumn ()
  276. {
  277. if (NoTableLoaded ())
  278. {
  279. return;
  280. }
  281. if (_tableView.SelectedColumn == -1)
  282. {
  283. MessageBox.ErrorQuery ("No Column", "No column selected", "Ok");
  284. return;
  285. }
  286. try
  287. {
  288. DataColumn currentCol = _currentTable.Columns [_tableView.SelectedColumn];
  289. if (GetText ("Move Column", "New Index:", currentCol.Ordinal.ToString (), out string newOrdinal))
  290. {
  291. int newIdx = Math.Min (
  292. Math.Max (0, int.Parse (newOrdinal)),
  293. _tableView.Table.Columns - 1
  294. );
  295. currentCol.SetOrdinal (newIdx);
  296. _tableView.SetSelection (newIdx, _tableView.SelectedRow, false);
  297. _tableView.EnsureSelectedCellIsVisible ();
  298. _tableView.SetNeedsDraw ();
  299. }
  300. }
  301. catch (Exception ex)
  302. {
  303. MessageBox.ErrorQuery ("Error moving column", ex.Message, "Ok");
  304. }
  305. }
  306. private void MoveRow ()
  307. {
  308. if (NoTableLoaded ())
  309. {
  310. return;
  311. }
  312. if (_tableView.SelectedRow == -1)
  313. {
  314. MessageBox.ErrorQuery ("No Rows", "No row selected", "Ok");
  315. return;
  316. }
  317. try
  318. {
  319. int oldIdx = _tableView.SelectedRow;
  320. DataRow currentRow = _currentTable.Rows [oldIdx];
  321. if (GetText ("Move Row", "New Row:", oldIdx.ToString (), out string newOrdinal))
  322. {
  323. int newIdx = Math.Min (Math.Max (0, int.Parse (newOrdinal)), _tableView.Table.Rows - 1);
  324. if (newIdx == oldIdx)
  325. {
  326. return;
  327. }
  328. object [] arrayItems = currentRow.ItemArray;
  329. _currentTable.Rows.Remove (currentRow);
  330. // Removing and Inserting the same DataRow seems to result in it loosing its values so we have to create a new instance
  331. DataRow newRow = _currentTable.NewRow ();
  332. newRow.ItemArray = arrayItems;
  333. _currentTable.Rows.InsertAt (newRow, newIdx);
  334. _tableView.SetSelection (_tableView.SelectedColumn, newIdx, false);
  335. _tableView.EnsureSelectedCellIsVisible ();
  336. _tableView.SetNeedsDraw ();
  337. }
  338. }
  339. catch (Exception ex)
  340. {
  341. MessageBox.ErrorQuery ("Error moving column", ex.Message, "Ok");
  342. }
  343. }
  344. private bool NoTableLoaded ()
  345. {
  346. if (_tableView.Table == null)
  347. {
  348. MessageBox.ErrorQuery ("No Table Loaded", "No table has currently be opened", "Ok");
  349. return true;
  350. }
  351. return false;
  352. }
  353. private void OnSelectedCellChanged (object sender, SelectedCellChangedEventArgs e)
  354. {
  355. // only update the text box if the user is not manually editing it
  356. if (!_selectedCellTextField.HasFocus)
  357. {
  358. _selectedCellTextField.Text = $"{_tableView.SelectedRow},{_tableView.SelectedColumn}";
  359. }
  360. if (_tableView.Table == null || _tableView.SelectedColumn == -1)
  361. {
  362. return;
  363. }
  364. ColumnStyle style = _tableView.Style.GetColumnStyleIfAny (_tableView.SelectedColumn);
  365. _miLeft.Checked = style?.Alignment == Alignment.Start;
  366. _miRight.Checked = style?.Alignment == Alignment.End;
  367. _miCentered.Checked = style?.Alignment == Alignment.Center;
  368. }
  369. private void Open ()
  370. {
  371. var ofd = new FileDialog
  372. {
  373. AllowedTypes = new () { new AllowedType ("Comma Separated Values", ".csv") }
  374. };
  375. ofd.Style.OkButtonText = "Open";
  376. Application.Run (ofd);
  377. if (!ofd.Canceled && !string.IsNullOrWhiteSpace (ofd.Path))
  378. {
  379. Open (ofd.Path);
  380. }
  381. ofd.Dispose ();
  382. }
  383. private void Open (string filename)
  384. {
  385. var lineNumber = 0;
  386. _currentFile = null;
  387. try
  388. {
  389. using var reader = new CsvReader (File.OpenText (filename), CultureInfo.InvariantCulture);
  390. var dt = new DataTable ();
  391. reader.Read ();
  392. if (reader.ReadHeader ())
  393. {
  394. foreach (string h in reader.HeaderRecord)
  395. {
  396. dt.Columns.Add (h);
  397. }
  398. }
  399. while (reader.Read ())
  400. {
  401. lineNumber++;
  402. DataRow newRow = dt.Rows.Add ();
  403. for (var i = 0; i < dt.Columns.Count; i++)
  404. {
  405. newRow [i] = reader [i];
  406. }
  407. }
  408. SetTable (dt);
  409. // Only set the current filename if we successfully loaded the entire file
  410. _currentFile = filename;
  411. _selectedCellTextField.SuperView.Enabled = true;
  412. Application.TopRunnable.Title = $"{GetName ()} - {Path.GetFileName (_currentFile)}";
  413. }
  414. catch (Exception ex)
  415. {
  416. MessageBox.ErrorQuery (
  417. "Open Failed",
  418. $"Error on line {lineNumber}{Environment.NewLine}{ex.Message}",
  419. "Ok"
  420. );
  421. }
  422. }
  423. private void Quit () { Application.RequestStop (); }
  424. private void RenameColumn ()
  425. {
  426. if (NoTableLoaded ())
  427. {
  428. return;
  429. }
  430. DataColumn currentCol = _currentTable.Columns [_tableView.SelectedColumn];
  431. if (GetText ("Rename Column", "Name:", currentCol.ColumnName, out string newName))
  432. {
  433. currentCol.ColumnName = newName;
  434. _tableView.Update ();
  435. }
  436. }
  437. private void Save ()
  438. {
  439. if (_tableView.Table == null || string.IsNullOrWhiteSpace (_currentFile))
  440. {
  441. MessageBox.ErrorQuery ("No file loaded", "No file is currently loaded", "Ok");
  442. return;
  443. }
  444. using var writer = new CsvWriter (
  445. new StreamWriter (File.OpenWrite (_currentFile)),
  446. CultureInfo.InvariantCulture
  447. );
  448. foreach (string col in _currentTable.Columns.Cast<DataColumn> ().Select (c => c.ColumnName))
  449. {
  450. writer.WriteField (col);
  451. }
  452. writer.NextRecord ();
  453. foreach (DataRow row in _currentTable.Rows)
  454. {
  455. foreach (object item in row.ItemArray)
  456. {
  457. writer.WriteField (item);
  458. }
  459. writer.NextRecord ();
  460. }
  461. }
  462. private void SelectedCellLabel_TextChanged (object sender, EventArgs e)
  463. {
  464. // if user is in the text control and editing the selected cell
  465. if (!_selectedCellTextField.HasFocus)
  466. {
  467. return;
  468. }
  469. // change selected cell to the one the user has typed into the box
  470. Match match = Regex.Match (_selectedCellTextField.Text, "^(\\d+),(\\d+)$");
  471. if (match.Success)
  472. {
  473. _tableView.SelectedColumn = int.Parse (match.Groups [1].Value);
  474. _tableView.SelectedRow = int.Parse (match.Groups [2].Value);
  475. }
  476. }
  477. private void SetFormat ()
  478. {
  479. if (NoTableLoaded ())
  480. {
  481. return;
  482. }
  483. DataColumn col = _currentTable.Columns [_tableView.SelectedColumn];
  484. if (col.DataType == typeof (string))
  485. {
  486. MessageBox.ErrorQuery (
  487. "Cannot Format Column",
  488. "String columns cannot be Formatted, try adding a new column to the table with a date/numerical Type",
  489. "Ok"
  490. );
  491. return;
  492. }
  493. ColumnStyle style = _tableView.Style.GetOrCreateColumnStyle (col.Ordinal);
  494. if (GetText ("Format", "Pattern:", style.Format ?? "", out string newPattern))
  495. {
  496. style.Format = newPattern;
  497. _tableView.Update ();
  498. }
  499. }
  500. private void SetTable (DataTable dataTable) { _tableView.Table = new DataTableSource (_currentTable = dataTable); }
  501. //private void SetupScrollBar ()
  502. //{
  503. // var scrollBar = new ScrollBarView (_tableView, true);
  504. // scrollBar.ChangedPosition += (s, e) =>
  505. // {
  506. // _tableView.RowOffset = scrollBar.Position;
  507. // if (_tableView.RowOffset != scrollBar.Position)
  508. // {
  509. // scrollBar.Position = _tableView.RowOffset;
  510. // }
  511. // _tableView.SetNeedsDraw ();
  512. // };
  513. // /*
  514. // scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => {
  515. // tableView.LeftItem = scrollBar.OtherScrollBarView.Position;
  516. // if (tableView.LeftItem != scrollBar.OtherScrollBarView.Position) {
  517. // scrollBar.OtherScrollBarView.Position = tableView.LeftItem;
  518. // }
  519. // tableView.SetNeedsDraw ();
  520. // };*/
  521. // _tableView.DrawingContent += (s, e) =>
  522. // {
  523. // scrollBar.Size = _tableView.Table?.Rows ?? 0;
  524. // scrollBar.Position = _tableView.RowOffset;
  525. // //scrollBar.OtherScrollBarView.Size = tableView.Maxlength - 1;
  526. // //scrollBar.OtherScrollBarView.Position = tableView.LeftItem;
  527. // scrollBar.Refresh ();
  528. // };
  529. //}
  530. private void Sort (bool asc)
  531. {
  532. if (NoTableLoaded ())
  533. {
  534. return;
  535. }
  536. if (_tableView.SelectedColumn == -1)
  537. {
  538. MessageBox.ErrorQuery ("No Column", "No column selected", "Ok");
  539. return;
  540. }
  541. string colName = _tableView.Table.ColumnNames [_tableView.SelectedColumn];
  542. _currentTable.DefaultView.Sort = colName + (asc ? " asc" : " desc");
  543. SetTable (_currentTable.DefaultView.ToTable ());
  544. }
  545. private void TableViewKeyPress (object sender, Key e)
  546. {
  547. if (e.KeyCode == KeyCode.Delete)
  548. {
  549. if (_tableView.FullRowSelect)
  550. {
  551. // Delete button deletes all rows when in full row mode
  552. foreach (int toRemove in _tableView.GetAllSelectedCells ()
  553. .Select (p => p.Y)
  554. .Distinct ()
  555. .OrderByDescending (i => i))
  556. {
  557. _currentTable.Rows.RemoveAt (toRemove);
  558. }
  559. }
  560. else
  561. {
  562. // otherwise set all selected cells to null
  563. foreach (Point pt in _tableView.GetAllSelectedCells ())
  564. {
  565. _currentTable.Rows [pt.Y] [pt.X] = DBNull.Value;
  566. }
  567. }
  568. _tableView.Update ();
  569. e.Handled = true;
  570. }
  571. }
  572. }