BackgroundWorkerCollection.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using Terminal.Gui;
  7. namespace UICatalog {
  8. [ScenarioMetadata (Name: "BackgroundWorker Collection", Description: "A persisting multi Toplevel BackgroundWorker threading")]
  9. [ScenarioCategory ("Threading")]
  10. [ScenarioCategory ("TopLevel")]
  11. [ScenarioCategory ("Dialogs")]
  12. [ScenarioCategory ("Controls")]
  13. class BackgroundWorkerCollection : Scenario {
  14. public override void Init (Toplevel top, ColorScheme colorScheme)
  15. {
  16. Application.Top.Dispose ();
  17. Application.Run<MdiMain> ();
  18. Application.Top.Dispose ();
  19. }
  20. public override void Run ()
  21. {
  22. }
  23. class MdiMain : Toplevel {
  24. private WorkerApp workerApp;
  25. private bool canOpenWorkerApp;
  26. MenuBar menu;
  27. public MdiMain ()
  28. {
  29. Data = "MdiMain";
  30. IsMdiContainer = true;
  31. workerApp = new WorkerApp () { Visible = false };
  32. menu = new MenuBar (new MenuBarItem [] {
  33. new MenuBarItem ("_Options", new MenuItem [] {
  34. new MenuItem ("_Run Worker", "", () => workerApp.RunWorker(), null, null, Key.CtrlMask | Key.R),
  35. new MenuItem ("_Cancel Worker", "", () => workerApp.CancelWorker(), null, null, Key.CtrlMask | Key.C),
  36. null,
  37. new MenuItem ("_Quit", "", () => Quit(), null, null, Key.CtrlMask | Key.Q)
  38. }),
  39. new MenuBarItem ("_View", new MenuItem [] { }),
  40. new MenuBarItem ("_Window", new MenuItem [] { })
  41. });
  42. menu.MenuOpening += Menu_MenuOpening;
  43. Add (menu);
  44. var statusBar = new StatusBar (new [] {
  45. new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Exit", () => Quit()),
  46. new StatusItem(Key.CtrlMask | Key.R, "~^R~ Run Worker", () => workerApp.RunWorker()),
  47. new StatusItem(Key.CtrlMask | Key.C, "~^C~ Cancel Worker", () => workerApp.CancelWorker())
  48. });
  49. Add (statusBar);
  50. Activate += MdiMain_Activate;
  51. Deactivate += MdiMain_Deactivate;
  52. Closed += MdiMain_Closed;
  53. Application.Iteration += () => {
  54. if (canOpenWorkerApp && !workerApp.Running && Application.MdiTop.Running) {
  55. Application.Run (workerApp);
  56. }
  57. };
  58. }
  59. private void MdiMain_Closed (Toplevel obj)
  60. {
  61. workerApp.Dispose ();
  62. Dispose ();
  63. }
  64. private void Menu_MenuOpening (MenuOpeningEventArgs menu)
  65. {
  66. if (!canOpenWorkerApp) {
  67. canOpenWorkerApp = true;
  68. return;
  69. }
  70. if (menu.CurrentMenu.Title == "_Window") {
  71. menu.NewMenuBarItem = OpenedWindows ();
  72. } else if (menu.CurrentMenu.Title == "_View") {
  73. menu.NewMenuBarItem = View ();
  74. }
  75. }
  76. private void MdiMain_Deactivate (Toplevel top)
  77. {
  78. workerApp.WriteLog ($"{top.Data} deactivate.");
  79. }
  80. private void MdiMain_Activate (Toplevel top)
  81. {
  82. workerApp.WriteLog ($"{top.Data} activate.");
  83. }
  84. private MenuBarItem View ()
  85. {
  86. List<MenuItem> menuItems = new List<MenuItem> ();
  87. var item = new MenuItem () {
  88. Title = "WorkerApp",
  89. CheckType = MenuItemCheckStyle.Checked
  90. };
  91. var top = Application.MdiChildes?.Find ((x) => x.Data.ToString () == "WorkerApp");
  92. if (top != null) {
  93. item.Checked = top.Visible;
  94. }
  95. item.Action += () => {
  96. var top = Application.MdiChildes.Find ((x) => x.Data.ToString () == "WorkerApp");
  97. item.Checked = top.Visible = !item.Checked;
  98. if (top.Visible) {
  99. top.ShowChild ();
  100. } else {
  101. Application.MdiTop.SetNeedsDisplay ();
  102. }
  103. };
  104. menuItems.Add (item);
  105. return new MenuBarItem ("_View",
  106. new List<MenuItem []> () { menuItems.Count == 0 ? new MenuItem [] { } : menuItems.ToArray () });
  107. }
  108. private MenuBarItem OpenedWindows ()
  109. {
  110. var index = 1;
  111. List<MenuItem> menuItems = new List<MenuItem> ();
  112. var sortedChildes = Application.MdiChildes;
  113. sortedChildes.Sort (new ToplevelComparer ());
  114. foreach (var top in sortedChildes) {
  115. if (top.Data.ToString () == "WorkerApp" && !top.Visible) {
  116. continue;
  117. }
  118. var item = new MenuItem ();
  119. item.Title = top is Window ? $"{index} {((Window)top).Title}" : $"{index} {top.Data}";
  120. index++;
  121. item.CheckType |= MenuItemCheckStyle.Checked;
  122. var topTitle = top is Window ? ((Window)top).Title : top.Data.ToString ();
  123. var itemTitle = item.Title.Substring (index.ToString ().Length + 1);
  124. if (top == top.GetTopMdiChild () && topTitle == itemTitle) {
  125. item.Checked = true;
  126. } else {
  127. item.Checked = false;
  128. }
  129. item.Action += () => {
  130. top.ShowChild ();
  131. };
  132. menuItems.Add (item);
  133. }
  134. if (menuItems.Count == 0) {
  135. return new MenuBarItem ("_Window", "", null);
  136. } else {
  137. return new MenuBarItem ("_Window", new List<MenuItem []> () { menuItems.ToArray () });
  138. }
  139. }
  140. private void Quit ()
  141. {
  142. RequestStop ();
  143. }
  144. }
  145. class WorkerApp : Toplevel {
  146. private List<string> log = new List<string> ();
  147. private ListView listLog;
  148. private Dictionary<Staging, BackgroundWorker> stagingWorkers;
  149. private List<StagingUIController> stagingsUI;
  150. public WorkerApp ()
  151. {
  152. Data = "WorkerApp";
  153. Width = Dim.Percent (80);
  154. Height = Dim.Percent (50);
  155. ColorScheme = Colors.Base;
  156. var label = new Label ("Worker collection Log") {
  157. X = Pos.Center (),
  158. Y = 0
  159. };
  160. Add (label);
  161. listLog = new ListView (log) {
  162. X = 0,
  163. Y = Pos.Bottom (label),
  164. Width = Dim.Fill (),
  165. Height = Dim.Fill ()
  166. };
  167. Add (listLog);
  168. }
  169. public void RunWorker ()
  170. {
  171. var stagingUI = new StagingUIController () { Modal = true };
  172. Staging staging = null;
  173. var worker = new BackgroundWorker () { WorkerSupportsCancellation = true };
  174. worker.DoWork += (s, e) => {
  175. var stageResult = new List<string> ();
  176. for (int i = 0; i < 500; i++) {
  177. stageResult.Add (
  178. $"Worker {i} started at {DateTime.Now}");
  179. e.Result = stageResult;
  180. Thread.Sleep (1);
  181. if (worker.CancellationPending) {
  182. e.Cancel = true;
  183. return;
  184. }
  185. }
  186. };
  187. worker.RunWorkerCompleted += (s, e) => {
  188. if (e.Error != null) {
  189. // Failed
  190. WriteLog ($"Exception occurred {e.Error.Message} on Worker {staging.StartStaging}.{staging.StartStaging:fff} at {DateTime.Now}");
  191. } else if (e.Cancelled) {
  192. // Canceled
  193. WriteLog ($"Worker {staging.StartStaging}.{staging.StartStaging:fff} was canceled at {DateTime.Now}!");
  194. } else {
  195. // Passed
  196. WriteLog ($"Worker {staging.StartStaging}.{staging.StartStaging:fff} was completed at {DateTime.Now}.");
  197. Application.Refresh ();
  198. var stagingUI = new StagingUIController (staging, e.Result as List<string>) {
  199. Modal = false,
  200. Title = $"Worker started at {staging.StartStaging}.{staging.StartStaging:fff}",
  201. Data = $"{staging.StartStaging}.{staging.StartStaging:fff}"
  202. };
  203. stagingUI.ReportClosed += StagingUI_ReportClosed;
  204. if (stagingsUI == null) {
  205. stagingsUI = new List<StagingUIController> ();
  206. }
  207. stagingsUI.Add (stagingUI);
  208. stagingWorkers.Remove (staging);
  209. stagingUI.Run ();
  210. }
  211. };
  212. Application.Run (stagingUI);
  213. if (stagingUI.Staging != null && stagingUI.Staging.StartStaging != null) {
  214. staging = new Staging (stagingUI.Staging.StartStaging);
  215. WriteLog ($"Worker is started at {staging.StartStaging}.{staging.StartStaging:fff}");
  216. if (stagingWorkers == null) {
  217. stagingWorkers = new Dictionary<Staging, BackgroundWorker> ();
  218. }
  219. stagingWorkers.Add (staging, worker);
  220. worker.RunWorkerAsync ();
  221. stagingUI.Dispose ();
  222. }
  223. }
  224. private void StagingUI_ReportClosed (StagingUIController obj)
  225. {
  226. WriteLog ($"Report {obj.Staging.StartStaging}.{obj.Staging.StartStaging:fff} closed.");
  227. stagingsUI.Remove (obj);
  228. }
  229. public void CancelWorker ()
  230. {
  231. if (stagingWorkers == null || stagingWorkers.Count == 0) {
  232. WriteLog ($"Worker is not running at {DateTime.Now}!");
  233. return;
  234. }
  235. foreach (var sw in stagingWorkers) {
  236. var key = sw.Key;
  237. var value = sw.Value;
  238. if (!key.Completed) {
  239. value.CancelAsync ();
  240. }
  241. WriteLog ($"Worker {key.StartStaging}.{key.StartStaging:fff} is canceling at {DateTime.Now}!");
  242. stagingWorkers.Remove (sw.Key);
  243. }
  244. }
  245. public void WriteLog (string msg)
  246. {
  247. log.Add (msg);
  248. listLog.MoveEnd ();
  249. }
  250. }
  251. class StagingUIController : Window {
  252. private Label label;
  253. private ListView listView;
  254. private Button start;
  255. private Button close;
  256. public Staging Staging { get; private set; }
  257. public event Action<StagingUIController> ReportClosed;
  258. public StagingUIController (Staging staging, List<string> list) : this ()
  259. {
  260. Staging = staging;
  261. label.Text = "Work list:";
  262. listView.SetSource (list);
  263. start.Visible = false;
  264. Id = "";
  265. }
  266. public StagingUIController ()
  267. {
  268. X = Pos.Center ();
  269. Y = Pos.Center ();
  270. Width = Dim.Percent (85);
  271. Height = Dim.Percent (85);
  272. ColorScheme = Colors.Dialog;
  273. Title = "Run Worker";
  274. label = new Label ("Press start to do the work or close to exit.") {
  275. X = Pos.Center (),
  276. Y = 1,
  277. ColorScheme = Colors.Dialog
  278. };
  279. Add (label);
  280. listView = new ListView () {
  281. X = 0,
  282. Y = 2,
  283. Width = Dim.Fill (),
  284. Height = Dim.Fill (2)
  285. };
  286. Add (listView);
  287. start = new Button ("Start") { IsDefault = true };
  288. start.Clicked += () => {
  289. Staging = new Staging (DateTime.Now);
  290. RequestStop ();
  291. };
  292. Add (start);
  293. close = new Button ("Close");
  294. close.Clicked += OnReportClosed;
  295. Add (close);
  296. KeyPress += (e) => {
  297. if (e.KeyEvent.Key == Key.Esc) {
  298. OnReportClosed ();
  299. }
  300. };
  301. LayoutStarted += (_) => {
  302. var btnsWidth = start.Bounds.Width + close.Bounds.Width + 2 - 1;
  303. var shiftLeft = Math.Max ((Bounds.Width - btnsWidth) / 2 - 2, 0);
  304. shiftLeft += close.Bounds.Width + 1;
  305. close.X = Pos.AnchorEnd (shiftLeft);
  306. close.Y = Pos.AnchorEnd (1);
  307. shiftLeft += start.Bounds.Width + 1;
  308. start.X = Pos.AnchorEnd (shiftLeft);
  309. start.Y = Pos.AnchorEnd (1);
  310. };
  311. }
  312. private void OnReportClosed ()
  313. {
  314. if (Staging.StartStaging != null) {
  315. ReportClosed?.Invoke (this);
  316. }
  317. RequestStop ();
  318. }
  319. public void Run ()
  320. {
  321. Application.Run (this);
  322. }
  323. }
  324. class Staging {
  325. public DateTime? StartStaging { get; private set; }
  326. public bool Completed { get; }
  327. public Staging (DateTime? startStaging, bool completed = false)
  328. {
  329. StartStaging = startStaging;
  330. Completed = completed;
  331. }
  332. }
  333. }
  334. }