Files
Terminal.Gui/UICatalog/Scenarios/BackgroundWorkerCollection.cs
Tig 16055c53b0 Fixes #3039. Fix View.HotKey (#3249)
* Added View.DefaultCommand etc... Started on dedicated scenario

* Fixed un-shifted hotkeys -> Fixed Key Equals. Fixed WindowsDriver passing wrong key. Etc.

* Fixed Key Bindings and HotKeys

* Fixed Key Bindings and HotKeys

* Label now correctly supports hotkey

* Disabled unix hot keys because they are annoying and get in the way

* Updated nuget. fixed warnings

* Trying to fix ci/ci issue

* Trying to fix ci/ci issue

* Trying to fix ci/ci issue

* Changed TextChangingEventArgs to inherit from CancelEventArgs

* TextChangingEventArgs -> TextEventArgs

* Simplified Text events by having only on args class

* Fixed unit tests fail

* Simplified by removing TitleEventArgs

* POC of Title being primary for hotkey. Label and Button hacked to work

* POC of Title being primary for hotkey. Label and Button hacked to work - all unit tests pass

* Dropped Microsoft.NETFramework.ReferenceAssemblies

* Fixed Dialogs scenario hotkeys

* Fixed build warnings

* Fixed Border Title render bug

* Regiggering default command handling

* Regiggering default command handling

* Checkbox clean up

* Added StateEventArgs POC

* Command.Default -> Command.HotKey

* Command.Default -> Command.HotKey - fixed TableView

* Command.Default -> Command.HotKey - fixed TableView

* Updated reactive example

* Fixed Toplevel.BringOverlappedTopToFront - was reordering SubViews when it shouldn't

* WIP - broke

* Finished impl of StateEventArgs

* Deleted ToggleEventArgs.cs. Added StateEventArgs.cs

* XML doc fix

* Removed old code

* Removed commented out code

* Label.Clicked -> Label.Accept (missed this before)

* Removed Labels as Buttons scenario as it's not really  useful

* Moved SubView tests to own file

* Moved SubView tests to own file

* Simplified Text test

* Added OnAccept test

* Deleted DefaultCommand

* Modernized CheckBox

* New button test

* Cleaned up RadioGroup; added tests

* KeyCode->Key in ListView

* Added ListView unit tests

* ListView now does Accept correctly

* TreeView now does Accept correctly

* Cleaned up some TextField tests

* TextView now handles Accept properly; updated CharMap and Adornments scenarios to test

* Fixed ComboBox to deal with TextView now handles Accept properly; updated CharMap and Adornments scenarios to test

* Removed un-needed using statement
2024-02-22 15:11:26 -07:00

485 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
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 Run () { Application.Run<OverlappedMain> (); }
private class OverlappedMain : Toplevel
{
private readonly MenuBar _menu;
private readonly WorkerApp _workerApp;
private bool _canOpenWorkerApp;
public OverlappedMain ()
{
Data = "OverlappedMain";
IsOverlappedContainer = true;
_workerApp = new WorkerApp { Visible = false };
_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);
Activate += OverlappedMain_Activate;
Deactivate += OverlappedMain_Deactivate;
Closed += OverlappedMain_Closed;
Application.Iteration += (s, a) =>
{
if (_canOpenWorkerApp && !_workerApp.Running && Application.OverlappedTop.Running)
{
Application.Run (_workerApp);
}
};
}
private void Menu_MenuOpening (object sender, MenuOpeningEventArgs menu)
{
if (!_canOpenWorkerApp)
{
_canOpenWorkerApp = true;
return;
}
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.Data} activate."); }
private void OverlappedMain_Closed (object sender, ToplevelEventArgs e)
{
_workerApp.Dispose ();
Dispose ();
}
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 () }
);
}
}
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, List<string> list) : this ()
{
Staging = staging;
_label.Text = "Work list:";
_listView.SetSource (list);
_start.Visible = false;
Id = "";
}
public StagingUIController ()
{
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) };
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.KeyCode == KeyCode.Esc)
{
OnReportClosed (this, EventArgs.Empty);
}
};
LayoutStarted += (s, e) =>
{
int btnsWidth = _start.Frame.Width + _close.Frame.Width + 2 - 1;
int shiftLeft = Math.Max ((Bounds.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;
public void Run () { Application.Run (this); }
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 List<string> _log = [];
private List<StagingUIController> _stagingsUi;
private Dictionary<Staging, BackgroundWorker> _stagingWorkers;
public WorkerApp ()
{
Data = "WorkerApp";
Width = Dim.Percent (80);
Height = Dim.Percent (50);
ColorScheme = Colors.ColorSchemes ["Base"];
var label = new Label { X = Pos.Center (), Y = 0, Text = "Worker collection Log" };
Add (label);
_listLog = new ListView
{
X = 0,
Y = Pos.Bottom (label),
Width = Dim.Fill (),
Height = Dim.Fill (),
Source = new ListWrapper (_log)
};
Add (_listLog);
}
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 List<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);
stagingUI.Run ();
}
};
Application.Run (stagingUI);
if (stagingUI.Staging != null && stagingUI.Staging.StartStaging != null)
{
staging = new Staging (stagingUI.Staging.StartStaging);
WriteLog ($"Worker is started at {staging.StartStaging}.{staging.StartStaging:fff}");
if (_stagingWorkers == null)
{
_stagingWorkers = new Dictionary<Staging, BackgroundWorker> ();
}
_stagingWorkers.Add (staging, worker);
worker.RunWorkerAsync ();
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);
}
}
}