BackgroundWorkerCollection.cs 11 KB

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