123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.ComponentModel;
- using System.Diagnostics;
- using System.Threading;
- using Terminal.Gui;
- namespace UICatalog.Scenarios;
- [ScenarioMetadata ("BackgroundWorker Collection", "A persisting multi Toplevel BackgroundWorker threading")]
- [ScenarioCategory ("Threading")]
- [ScenarioCategory ("Top Level Windows")]
- [ScenarioCategory ("Dialogs")]
- [ScenarioCategory ("Controls")]
- public class BackgroundWorkerCollection : Scenario
- {
- public override void Main ()
- {
- Application.Run<OverlappedMain> ().Dispose ();
- #if DEBUG_IDISPOSABLE
- if (Application.OverlappedChildren is { })
- {
- Debug.Assert (Application.OverlappedChildren?.Count == 0);
- Debug.Assert (Application.Top == Application.OverlappedTop);
- }
- #endif
- Application.Shutdown ();
- }
- private class OverlappedMain : Toplevel
- {
- private readonly MenuBar _menu;
- private WorkerApp _workerApp;
- public OverlappedMain ()
- {
- Arrangement = ViewArrangement.Movable;
- Data = "OverlappedMain";
- IsOverlappedContainer = true;
- _workerApp = new WorkerApp { Visible = false };
- _workerApp.Border.Thickness = new (0, 1, 0, 0);
- _workerApp.Border.LineStyle = LineStyle.Dashed;
- _menu = new MenuBar
- {
- Menus =
- [
- new MenuBarItem (
- "_Options",
- new MenuItem []
- {
- new (
- "_Run Worker",
- "",
- () => _workerApp.RunWorker (),
- null,
- null,
- KeyCode.CtrlMask | KeyCode.R
- ),
- new (
- "_Cancel Worker",
- "",
- () => _workerApp.CancelWorker (),
- null,
- null,
- KeyCode.CtrlMask | KeyCode.C
- ),
- null,
- new (
- "_Quit",
- "",
- () => Quit (),
- null,
- null,
- (KeyCode)Application.QuitKey
- )
- }
- ),
- new MenuBarItem ("_View", new MenuItem [] { }),
- new MenuBarItem ("_Window", new MenuItem [] { })
- ]
- };
- ;
- _menu.MenuOpening += Menu_MenuOpening;
- Add (_menu);
- var statusBar = new StatusBar (
- new []
- {
- new StatusItem (Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit ()),
- new StatusItem (
- KeyCode.CtrlMask | KeyCode.R,
- "~^R~ Run Worker",
- () => _workerApp.RunWorker ()
- ),
- new StatusItem (
- KeyCode.CtrlMask | KeyCode.C,
- "~^C~ Cancel Worker",
- () => _workerApp.CancelWorker ()
- )
- }
- );
- Add (statusBar);
- Ready += OverlappedMain_Ready;
- Activate += OverlappedMain_Activate;
- Deactivate += OverlappedMain_Deactivate;
- }
- private void OverlappedMain_Ready (object sender, EventArgs e)
- {
- if (_workerApp?.Running == false)
- {
- Application.Run (_workerApp);
- }
- }
- private void Menu_MenuOpening (object sender, MenuOpeningEventArgs menu)
- {
- if (menu.CurrentMenu.Title == "_Window")
- {
- menu.NewMenuBarItem = OpenedWindows ();
- }
- else if (menu.CurrentMenu.Title == "_View")
- {
- menu.NewMenuBarItem = View ();
- }
- }
- private MenuBarItem OpenedWindows ()
- {
- var index = 1;
- List<MenuItem> menuItems = new ();
- List<Toplevel> sortedChildren = Application.OverlappedChildren;
- sortedChildren.Sort (new ToplevelComparer ());
- foreach (Toplevel top in sortedChildren)
- {
- if (top.Data.ToString () == "WorkerApp" && !top.Visible)
- {
- continue;
- }
- var item = new MenuItem ();
- item.Title = top is Window ? $"{index} {((Window)top).Title}" : $"{index} {top.Data}";
- index++;
- item.CheckType |= MenuItemCheckStyle.Checked;
- string topTitle = top is Window ? ((Window)top).Title : top.Data.ToString ();
- string itemTitle = item.Title.Substring (index.ToString ().Length + 1);
- if (top == Application.GetTopOverlappedChild () && topTitle == itemTitle)
- {
- item.Checked = true;
- }
- else
- {
- item.Checked = false;
- }
- item.Action += () => { Application.MoveToOverlappedChild (top); };
- menuItems.Add (item);
- }
- if (menuItems.Count == 0)
- {
- return new MenuBarItem ("_Window", "", null);
- }
- return new MenuBarItem ("_Window", new List<MenuItem []> { menuItems.ToArray () });
- }
- private void OverlappedMain_Activate (object sender, ToplevelEventArgs top)
- {
- _workerApp?.WriteLog ($"{(top.Toplevel is null ? ((Toplevel)sender).Data : top.Toplevel.Data)} activate.");
- }
- private void OverlappedMain_Deactivate (object sender, ToplevelEventArgs top)
- {
- _workerApp?.WriteLog ($"{top.Toplevel.Data} deactivate.");
- }
- private void Quit () { RequestStop (); }
- private MenuBarItem View ()
- {
- List<MenuItem> menuItems = new ();
- var item = new MenuItem { Title = "WorkerApp", CheckType = MenuItemCheckStyle.Checked };
- Toplevel top = Application.OverlappedChildren?.Find (x => x.Data.ToString () == "WorkerApp");
- if (top != null)
- {
- item.Checked = top.Visible;
- }
- item.Action += () =>
- {
- Toplevel top = Application.OverlappedChildren.Find (x => x.Data.ToString () == "WorkerApp");
- item.Checked = top.Visible = (bool)!item.Checked;
- if (top.Visible)
- {
- Application.MoveToOverlappedChild (top);
- }
- else
- {
- Application.OverlappedTop.SetNeedsDisplay ();
- }
- };
- menuItems.Add (item);
- return new MenuBarItem (
- "_View",
- new List<MenuItem []> { menuItems.Count == 0 ? new MenuItem [] { } : menuItems.ToArray () }
- );
- }
- /// <inheritdoc />
- protected override void Dispose (bool disposing)
- {
- _workerApp?.Dispose ();
- _workerApp = null;
- base.Dispose (disposing);
- }
- }
- private class Staging
- {
- public Staging (DateTime? startStaging, bool completed = false)
- {
- StartStaging = startStaging;
- Completed = completed;
- }
- public bool Completed { get; }
- public DateTime? StartStaging { get; }
- }
- private class StagingUIController : Window
- {
- private readonly Button _close;
- private readonly Label _label;
- private readonly ListView _listView;
- private readonly Button _start;
- public StagingUIController (Staging staging, ObservableCollection<string> list) : this ()
- {
- Staging = staging;
- _label.Text = "Work list:";
- _listView.Enabled = true;
- _listView.SetSource (list);
- _start.Visible = false;
- Id = "";
- }
- public StagingUIController ()
- {
- Arrangement = ViewArrangement.Movable;
- X = Pos.Center ();
- Y = Pos.Center ();
- Width = Dim.Percent (85);
- Height = Dim.Percent (85);
- ColorScheme = Colors.ColorSchemes ["Dialog"];
- Title = "Run Worker";
- _label = new Label
- {
- X = Pos.Center (),
- Y = 1,
- ColorScheme = Colors.ColorSchemes ["Dialog"],
- Text = "Press start to do the work or close to quit."
- };
- Add (_label);
- _listView = new ListView { X = 0, Y = 2, Width = Dim.Fill (), Height = Dim.Fill (2), Enabled = false };
- Add (_listView);
- _start = new Button { Text = "Start", IsDefault = true, ClearOnVisibleFalse = false };
- _start.Accept += (s, e) =>
- {
- Staging = new Staging (DateTime.Now);
- RequestStop ();
- };
- Add (_start);
- _close = new Button { Text = "Close" };
- _close.Accept += OnReportClosed;
- Add (_close);
- KeyDown += (s, e) =>
- {
- if (e == Application.QuitKey)
- {
- OnReportClosed (this, EventArgs.Empty);
- }
- };
- LayoutStarted += (s, e) =>
- {
- int btnsWidth = _start.Frame.Width + _close.Frame.Width + 2 - 1;
- int shiftLeft = Math.Max ((Viewport.Width - btnsWidth) / 2 - 2, 0);
- shiftLeft += _close.Frame.Width + 1;
- _close.X = Pos.AnchorEnd (shiftLeft);
- _close.Y = Pos.AnchorEnd (1);
- shiftLeft += _start.Frame.Width + 1;
- _start.X = Pos.AnchorEnd (shiftLeft);
- _start.Y = Pos.AnchorEnd (1);
- };
- }
- public Staging Staging { get; private set; }
- public event Action<StagingUIController> ReportClosed;
- private void OnReportClosed (object sender, EventArgs e)
- {
- if (Staging?.StartStaging != null)
- {
- ReportClosed?.Invoke (this);
- }
- RequestStop ();
- }
- }
- private class WorkerApp : Toplevel
- {
- private readonly ListView _listLog;
- private readonly ObservableCollection<string> _log = [];
- private List<StagingUIController> _stagingsUi;
- private Dictionary<Staging, BackgroundWorker> _stagingWorkers;
- public WorkerApp ()
- {
- Arrangement = ViewArrangement.Movable;
- Data = "WorkerApp";
- Title = "Worker collection Log";
- Width = Dim.Percent (80);
- Height = Dim.Percent (50);
- ColorScheme = Colors.ColorSchemes ["Base"];
- _listLog = new ListView
- {
- X = 0,
- Y = 0,
- Width = Dim.Fill (),
- Height = Dim.Fill (),
- Source = new ListWrapper<string> (_log)
- };
- Add (_listLog);
- // We don't want WorkerApp to respond to the quitkey
- KeyBindings.Remove (Application.QuitKey);
- Closing += WorkerApp_Closing;
- Closed += WorkerApp_Closed;
- }
- private void WorkerApp_Closed (object sender, ToplevelEventArgs e)
- {
- CancelWorker ();
- }
- private void WorkerApp_Closing (object sender, ToplevelClosingEventArgs e)
- {
- Toplevel top = Application.OverlappedChildren.Find (x => x.Data.ToString () == "WorkerApp");
- if (Visible && top == this)
- {
- Visible = false;
- e.Cancel = true;
- Application.OverlappedMoveNext ();
- }
- }
- public void CancelWorker ()
- {
- if (_stagingWorkers == null || _stagingWorkers.Count == 0)
- {
- WriteLog ($"Worker is not running at {DateTime.Now}!");
- return;
- }
- foreach (KeyValuePair<Staging, BackgroundWorker> sw in _stagingWorkers)
- {
- Staging key = sw.Key;
- BackgroundWorker value = sw.Value;
- if (!key.Completed)
- {
- value.CancelAsync ();
- }
- WriteLog (
- $"Worker {key.StartStaging}.{key.StartStaging:fff} is canceling at {DateTime.Now}!"
- );
- _stagingWorkers.Remove (sw.Key);
- }
- }
- public void RunWorker ()
- {
- var stagingUI = new StagingUIController { Modal = true };
- Staging staging = null;
- var worker = new BackgroundWorker { WorkerSupportsCancellation = true };
- worker.DoWork += (s, e) =>
- {
- List<string> stageResult = new ();
- for (var i = 0; i < 500; i++)
- {
- stageResult.Add (
- $"Worker {i} started at {DateTime.Now}"
- );
- e.Result = stageResult;
- Thread.Sleep (1);
- if (worker.CancellationPending)
- {
- e.Cancel = true;
- return;
- }
- }
- };
- worker.RunWorkerCompleted += (s, e) =>
- {
- if (e.Error != null)
- {
- // Failed
- WriteLog (
- $"Exception occurred {e.Error.Message} on Worker {staging.StartStaging}.{staging.StartStaging:fff} at {DateTime.Now}"
- );
- }
- else if (e.Cancelled)
- {
- // Canceled
- WriteLog (
- $"Worker {staging.StartStaging}.{staging.StartStaging:fff} was canceled at {DateTime.Now}!"
- );
- }
- else
- {
- // Passed
- WriteLog (
- $"Worker {staging.StartStaging}.{staging.StartStaging:fff} was completed at {DateTime.Now}."
- );
- Application.Refresh ();
- var stagingUI = new StagingUIController (staging, e.Result as ObservableCollection<string>)
- {
- Modal = false,
- Title =
- $"Worker started at {staging.StartStaging}.{staging.StartStaging:fff}",
- Data = $"{staging.StartStaging}.{staging.StartStaging:fff}"
- };
- stagingUI.ReportClosed += StagingUI_ReportClosed;
- if (_stagingsUi == null)
- {
- _stagingsUi = new List<StagingUIController> ();
- }
- _stagingsUi.Add (stagingUI);
- _stagingWorkers.Remove (staging);
- #if DEBUG_IDISPOSABLE
- if (Application.OverlappedTop is null)
- {
- stagingUI.Dispose ();
- return;
- }
- #endif
- Application.Run (stagingUI);
- }
- };
- Application.Run (stagingUI);
- if (stagingUI.Staging != null && stagingUI.Staging.StartStaging != null)
- {
- staging = new Staging (stagingUI.Staging.StartStaging);
- stagingUI.Dispose ();
- WriteLog ($"Worker is started at {staging.StartStaging}.{staging.StartStaging:fff}");
- if (_stagingWorkers == null)
- {
- _stagingWorkers = new Dictionary<Staging, BackgroundWorker> ();
- }
- _stagingWorkers.Add (staging, worker);
- worker.RunWorkerAsync ();
- }
- else
- {
- stagingUI.Dispose ();
- }
- }
- public void WriteLog (string msg)
- {
- _log.Add (msg);
- _listLog.MoveDown ();
- }
- private void StagingUI_ReportClosed (StagingUIController obj)
- {
- WriteLog ($"Report {obj.Staging.StartStaging}.{obj.Staging.StartStaging:fff} closed.");
- _stagingsUi.Remove (obj);
- obj.Dispose ();
- }
- }
- }
|