From 06383a4742363acd1bade56617effb8985adc28f Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Sun, 24 May 2020 19:18:28 -0600 Subject: [PATCH 1/5] added two demos --- Terminal.Gui/Core.cs | 6 + UICatalog/Scenarios/Progress.cs | 237 ++++++++++++++++++++++---------- 2 files changed, 167 insertions(+), 76 deletions(-) diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 3e0032e14..c6568fb3e 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -2421,6 +2421,10 @@ namespace Terminal.Gui { /// public static void Shutdown () { + // Shutdown is the bookend for Init. As such it needs to clean up all resources + // Init created. Apps that do any threading will need to code defensively for this. + // e.g. see Issue #537 + // TODO: Some of this state is actually related to Begin/End (not Init/Shutdown) and should be moved to `RunState` (#520) foreach (var t in toplevels) { t.Running = false; } @@ -2428,6 +2432,8 @@ namespace Terminal.Gui { Current = null; CurrentView = null; Top = null; + + // MainLoop = null; Driver.End (); diff --git a/UICatalog/Scenarios/Progress.cs b/UICatalog/Scenarios/Progress.cs index 223053939..a62456ce0 100644 --- a/UICatalog/Scenarios/Progress.cs +++ b/UICatalog/Scenarios/Progress.cs @@ -1,6 +1,8 @@ -using System; +using NStack; +using System; using System.Threading; using Terminal.Gui; +using System.Linq; namespace UICatalog { // @@ -11,99 +13,182 @@ namespace UICatalog { [ScenarioCategory ("Threading")] class Progress : Scenario { - private ProgressBar _activityProgressBar; - private ProgressBar _pulseProgressBar; - private Timer _timer; - private object _timeoutToken = null; + class ProgressDemo : FrameView, IDisposable { + internal ProgressBar ActivityProgressBar { get; private set; } + internal ProgressBar PulseProgressBar { get; private set; } + bool _disposedValue; + const int _verticalSpace = 1; + internal Action StartBtnClick; + internal Action StopBtnClick; + internal Action PulseBtnClick; + + internal ProgressDemo (ustring title) : base (title) + { + ColorScheme = Colors.Dialog; + + var leftFrame = new FrameView ("Settings") { + X = 0, + Y = 0, + Height = Dim.Percent (100), + Width = Dim.Percent (30) + }; + Add (leftFrame); + + var startButton = new Button ("Start Timer") { + X = Pos.Right (leftFrame) + 1, + Y = 0, + Clicked = () => StartBtnClick?.Invoke () + }; + var pulseButton = new Button ("Pulse") { + X = Pos.Right (startButton) + 2, + Y = Pos.Y (startButton), + Clicked = () => PulseBtnClick.Invoke () + }; + var stopbutton = new Button ("Stop Timer") { + X = Pos.Right (pulseButton) + 2, + Y = Pos.Top (pulseButton), + Clicked = () => StopBtnClick.Invoke () + }; + + Add (startButton); + Add (pulseButton); + Add (stopbutton); + + ActivityProgressBar = new ProgressBar () { + X = Pos.Right (leftFrame) + 1, + Y = Pos.Bottom (startButton) + 1, + Width = Dim.Fill (), + Height = 1, + Fraction = 0.25F, + ColorScheme = Colors.Error + }; + Add (ActivityProgressBar); + + PulseProgressBar = new ProgressBar () { + X = Pos.Right (leftFrame) + 1, + Y = Pos.Bottom (ActivityProgressBar) + 1, + Width = Dim.Fill (), + Height = 1, + ColorScheme = Colors.Error + }; + Add (PulseProgressBar); + + // Set height to height of controls + spacing + frame + Height = 2 + _verticalSpace + Dim.Height (startButton) + _verticalSpace + Dim.Height (ActivityProgressBar) + _verticalSpace + Dim.Height (PulseProgressBar) + _verticalSpace; + + } + + protected virtual void Dispose (bool disposing) + { + if (!_disposedValue) { + if (disposing) { + + } + _disposedValue = true; + } + } + + public void Dispose () + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose (disposing: true); + GC.SuppressFinalize (this); + } + } + + private Timer _systemTimer = null; + private int _systemTimerTick = 100; // ms + private object _mainLoopTimeout = null; + private int _mainLooopTimeoutTick = 1000; // ms public override void Setup () { - var pulseButton = new Button ("Pulse") { - X = Pos.Center (), - Y = Pos.Center () - 3, - Clicked = () => Pulse () + // Demo #1 - Use System.Timer (and threading) + var systemTimerDemo = new ProgressDemo ("System.Timer (threads)") { + X = 0, + Y = 0, + Width = Dim.Percent (100), + }; + systemTimerDemo.StartBtnClick = () => { + _systemTimer?.Dispose (); + _systemTimer = null; + + systemTimerDemo.ActivityProgressBar.Fraction = 0F; + systemTimerDemo.PulseProgressBar.Fraction = 0F; + + _systemTimer = new Timer ((o) => { + // Note the check for Mainloop being valid. System.Timers can run after they are Disposed. + // This code must be defensive for that. + Application.MainLoop?.Invoke (() => systemTimerDemo.PulseBtnClick ()); + }, null, 0, _systemTimerTick); }; - var startButton = new Button ("Start Timer") { - Y = Pos.Y(pulseButton), - Clicked = () => Start () + systemTimerDemo.PulseBtnClick = () => { + if (systemTimerDemo.ActivityProgressBar.Fraction + 0.01F >= 1) { + systemTimerDemo.ActivityProgressBar.Fraction = 0F; + } else { + systemTimerDemo.ActivityProgressBar.Fraction += 0.01F; + } + systemTimerDemo.PulseProgressBar.Pulse (); + }; + systemTimerDemo.StopBtnClick = () => { + _systemTimer?.Dispose (); + _systemTimer = null; + + systemTimerDemo.ActivityProgressBar.Fraction = 1F; + systemTimerDemo.PulseProgressBar.Fraction = 1F; }; - var stopbutton = new Button ("Stop Timer") { - Y = Pos.Y (pulseButton), - Clicked = () => Stop() + + Win.Add (systemTimerDemo); + + // Demo #2 - Use Application.MainLoop.AddTimeout (no threads) + var mainLoopTimeoutDemo = new ProgressDemo ("Application.AddTimer (no threads)") { + X = 0, + Y = Pos.Bottom (systemTimerDemo), + Width = Dim.Percent (100), }; + mainLoopTimeoutDemo.StartBtnClick = () => { + mainLoopTimeoutDemo.StopBtnClick (); - // Center three buttons with 5 spaces between them - // TODO: Use Pos.Width instead of (Right-Left) when implemented (#502) - startButton.X = Pos.Left (pulseButton) - (Pos.Right (startButton) - Pos.Left (startButton)) - 5; - stopbutton.X = Pos.Right (pulseButton) + 5; + mainLoopTimeoutDemo.ActivityProgressBar.Fraction = 0F; + mainLoopTimeoutDemo.PulseProgressBar.Fraction = 0F; - Win.Add (startButton); - Win.Add (pulseButton); - Win.Add (stopbutton); - - _activityProgressBar = new ProgressBar () { - X = Pos.Center (), - // BUGBUG: If you remove the +1 below the control is drawn at top?!?! - Y = Pos.Center ()+1, - Width = 30, - Fraction = 0.25F, + Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (_mainLooopTimeoutTick), (loop) => { + mainLoopTimeoutDemo?.PulseBtnClick (); + return true; + }); }; - Win.Add (_activityProgressBar); - - _pulseProgressBar = new ProgressBar () { - X = Pos.Center (), - // BUGBUG: If you remove the +1 below the control is drawn at top?!?! - Y = Pos.Center () + 3, - Width = 30, + mainLoopTimeoutDemo.PulseBtnClick = () => { + if (mainLoopTimeoutDemo.ActivityProgressBar.Fraction + 0.01F >= 1) { + mainLoopTimeoutDemo.ActivityProgressBar.Fraction = 0F; + } else { + mainLoopTimeoutDemo.ActivityProgressBar.Fraction += 0.01F; + } + mainLoopTimeoutDemo.PulseProgressBar.Pulse (); }; - Win.Add (_pulseProgressBar); + mainLoopTimeoutDemo.StopBtnClick = () => { + if (_mainLoopTimeout != null) { + Application.MainLoop.RemoveTimeout (_mainLoopTimeout); + _mainLoopTimeout = null; + } + + mainLoopTimeoutDemo.ActivityProgressBar.Fraction = 1F; + mainLoopTimeoutDemo.PulseProgressBar.Fraction = 1F; + }; + Win.Add (mainLoopTimeoutDemo); + } protected override void Dispose (bool disposing) { - _timer?.Dispose (); - _timer = null; - if (_timeoutToken != null) { - Application.MainLoop.RemoveTimeout (_timeoutToken); + Win.GetEnumerator ().Reset (); + while (Win.GetEnumerator ().MoveNext ()) { + var cur = (ProgressDemo)Win.GetEnumerator ().Current; + cur?.StopBtnClick (); + cur.Dispose (); } base.Dispose (disposing); } - - private void Pulse () - { - if (_activityProgressBar.Fraction + 0.01F >= 1) { - _activityProgressBar.Fraction = 0F; - } else { - _activityProgressBar.Fraction += 0.01F; - } - _pulseProgressBar.Pulse (); - } - - private void Start () - { - _timer?.Dispose (); - _timer = null; - - _activityProgressBar.Fraction = 0F; - _pulseProgressBar.Fraction = 0F; - - _timer = new Timer ((o) => { - Application.MainLoop.Invoke (() => Pulse ()); - }, null, 0, 20); - } - - private void Stop () - { - _timer?.Dispose (); - _timer = null; - if (_timeoutToken != null) { - Application.MainLoop.RemoveTimeout (_timeoutToken); - } - - _activityProgressBar.Fraction = 1F; - _pulseProgressBar.Fraction = 1F; - } } } \ No newline at end of file From 14958b4fa30954742328cbac518b2c8c505916a1 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Sun, 24 May 2020 19:31:45 -0600 Subject: [PATCH 2/5] fixed minor bug --- UICatalog/Scenarios/Progress.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UICatalog/Scenarios/Progress.cs b/UICatalog/Scenarios/Progress.cs index a62456ce0..946077fca 100644 --- a/UICatalog/Scenarios/Progress.cs +++ b/UICatalog/Scenarios/Progress.cs @@ -154,7 +154,7 @@ namespace UICatalog { mainLoopTimeoutDemo.ActivityProgressBar.Fraction = 0F; mainLoopTimeoutDemo.PulseProgressBar.Fraction = 0F; - Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (_mainLooopTimeoutTick), (loop) => { + _mainLoopTimeout = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (_mainLooopTimeoutTick), (loop) => { mainLoopTimeoutDemo?.PulseBtnClick (); return true; }); From ba0e74207fe836d7db1e827baac52d6b3759a276 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Sun, 24 May 2020 19:38:38 -0600 Subject: [PATCH 3/5] removed un-needed IDispose --- UICatalog/Scenarios/Progress.cs | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/UICatalog/Scenarios/Progress.cs b/UICatalog/Scenarios/Progress.cs index 946077fca..4a106f333 100644 --- a/UICatalog/Scenarios/Progress.cs +++ b/UICatalog/Scenarios/Progress.cs @@ -13,12 +13,11 @@ namespace UICatalog { [ScenarioCategory ("Threading")] class Progress : Scenario { - class ProgressDemo : FrameView, IDisposable { - internal ProgressBar ActivityProgressBar { get; private set; } - internal ProgressBar PulseProgressBar { get; private set; } - bool _disposedValue; + class ProgressDemo : FrameView { const int _verticalSpace = 1; + internal ProgressBar ActivityProgressBar { get; private set; } + internal ProgressBar PulseProgressBar { get; private set; } internal Action StartBtnClick; internal Action StopBtnClick; internal Action PulseBtnClick; @@ -78,23 +77,6 @@ namespace UICatalog { Height = 2 + _verticalSpace + Dim.Height (startButton) + _verticalSpace + Dim.Height (ActivityProgressBar) + _verticalSpace + Dim.Height (PulseProgressBar) + _verticalSpace; } - - protected virtual void Dispose (bool disposing) - { - if (!_disposedValue) { - if (disposing) { - - } - _disposedValue = true; - } - } - - public void Dispose () - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose (disposing: true); - GC.SuppressFinalize (this); - } } private Timer _systemTimer = null; @@ -186,7 +168,6 @@ namespace UICatalog { while (Win.GetEnumerator ().MoveNext ()) { var cur = (ProgressDemo)Win.GetEnumerator ().Current; cur?.StopBtnClick (); - cur.Dispose (); } base.Dispose (disposing); } From 9a3812da43945a5ce11aa235ef94bb9cb1fcf256 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Sun, 24 May 2020 19:54:38 -0600 Subject: [PATCH 4/5] stopped using GetEnumerator() --- UICatalog/Scenarios/Progress.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/UICatalog/Scenarios/Progress.cs b/UICatalog/Scenarios/Progress.cs index 4a106f333..d7b10595e 100644 --- a/UICatalog/Scenarios/Progress.cs +++ b/UICatalog/Scenarios/Progress.cs @@ -164,10 +164,8 @@ namespace UICatalog { protected override void Dispose (bool disposing) { - Win.GetEnumerator ().Reset (); - while (Win.GetEnumerator ().MoveNext ()) { - var cur = (ProgressDemo)Win.GetEnumerator ().Current; - cur?.StopBtnClick (); + foreach (var v in Win.Subviews.OfType()) { + v?.StopBtnClick (); } base.Dispose (disposing); } From fc1ed282a38d753303ea6f33fe13902600d6adb4 Mon Sep 17 00:00:00 2001 From: Charlie Kindel Date: Mon, 25 May 2020 12:40:46 -0600 Subject: [PATCH 5/5] updated to really show stuff off --- Terminal.Gui/Views/TextField.cs | 6 +- UICatalog/Scenarios/Progress.cs | 131 ++++++++++++++++++++++++-------- 2 files changed, 105 insertions(+), 32 deletions(-) diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 33adf50fc..79e3b2400 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -36,8 +36,10 @@ namespace Terminal.Gui { /// Changed event, raised when the text has clicked. /// /// - /// Client code can hook up to this event, it is - /// raised when the text in the entry changes. + /// This event is raised when the changes. + /// + /// + /// The passed is a containing the old value. /// public event EventHandler Changed; diff --git a/UICatalog/Scenarios/Progress.cs b/UICatalog/Scenarios/Progress.cs index d7b10595e..629c30202 100644 --- a/UICatalog/Scenarios/Progress.cs +++ b/UICatalog/Scenarios/Progress.cs @@ -10,34 +10,55 @@ namespace UICatalog { // [ScenarioMetadata (Name: "Progress", Description: "Shows off ProgressBar and Threading")] [ScenarioCategory ("Controls")] + [ScenarioCategory ("MainLoop")] [ScenarioCategory ("Threading")] class Progress : Scenario { class ProgressDemo : FrameView { const int _verticalSpace = 1; + internal FrameView LeftFrame { get; private set; } + internal TextField Speed { get; private set; } internal ProgressBar ActivityProgressBar { get; private set; } internal ProgressBar PulseProgressBar { get; private set; } internal Action StartBtnClick; internal Action StopBtnClick; internal Action PulseBtnClick; + private Label _startedLabel; + internal bool Started { + get { + return _startedLabel.Text == "Started"; + } + private set { + _startedLabel.Text = value ? "Started" : "Stopped"; + } + } internal ProgressDemo (ustring title) : base (title) { ColorScheme = Colors.Dialog; - var leftFrame = new FrameView ("Settings") { + LeftFrame = new FrameView ("Settings") { X = 0, Y = 0, - Height = Dim.Percent (100), - Width = Dim.Percent (30) + Height = Dim.Percent (100) + 1, // BUGBUG: This +1 should not be needed + Width = Dim.Percent (25) }; - Add (leftFrame); + var lbl = new Label (1, 1, "Tick every (ms):"); + LeftFrame.Add (lbl); + Speed = new TextField ("") { + X = Pos.Right (lbl) + 1, + Y = Pos.Y (lbl), + Width = 7, + }; + LeftFrame.Add (Speed); + + Add (LeftFrame); var startButton = new Button ("Start Timer") { - X = Pos.Right (leftFrame) + 1, + X = Pos.Right (LeftFrame) + 1, Y = 0, - Clicked = () => StartBtnClick?.Invoke () + Clicked = () => Start() }; var pulseButton = new Button ("Pulse") { X = Pos.Right (startButton) + 2, @@ -47,7 +68,7 @@ namespace UICatalog { var stopbutton = new Button ("Stop Timer") { X = Pos.Right (pulseButton) + 2, Y = Pos.Top (pulseButton), - Clicked = () => StopBtnClick.Invoke () + Clicked = () => Stop() }; Add (startButton); @@ -55,7 +76,7 @@ namespace UICatalog { Add (stopbutton); ActivityProgressBar = new ProgressBar () { - X = Pos.Right (leftFrame) + 1, + X = Pos.Right (LeftFrame) + 1, Y = Pos.Bottom (startButton) + 1, Width = Dim.Fill (), Height = 1, @@ -65,7 +86,7 @@ namespace UICatalog { Add (ActivityProgressBar); PulseProgressBar = new ProgressBar () { - X = Pos.Right (leftFrame) + 1, + X = Pos.Right (LeftFrame) + 1, Y = Pos.Bottom (ActivityProgressBar) + 1, Width = Dim.Fill (), Height = 1, @@ -73,16 +94,49 @@ namespace UICatalog { }; Add (PulseProgressBar); + _startedLabel = new Label ("Stopped") { + X = Pos.Right (LeftFrame) + 1, + Y = Pos.Bottom (PulseProgressBar), + }; + Add (_startedLabel); + + // Set height to height of controls + spacing + frame Height = 2 + _verticalSpace + Dim.Height (startButton) + _verticalSpace + Dim.Height (ActivityProgressBar) + _verticalSpace + Dim.Height (PulseProgressBar) + _verticalSpace; + } + internal void Start () + { + Started = true; + StartBtnClick?.Invoke (); + } + + internal void Stop () + { + Started = false; + StopBtnClick?.Invoke (); + } + + internal void Pulse () + { + if (PulseBtnClick != null) { + PulseBtnClick?.Invoke (); + + } else { + if (ActivityProgressBar.Fraction + 0.01F >= 1) { + ActivityProgressBar.Fraction = 0F; + } else { + ActivityProgressBar.Fraction += 0.01F; + } + PulseProgressBar.Pulse (); + } } } private Timer _systemTimer = null; - private int _systemTimerTick = 100; // ms + private uint _systemTimerTick = 1000; // ms private object _mainLoopTimeout = null; - private int _mainLooopTimeoutTick = 1000; // ms + private uint _mainLooopTimeoutTick = 1000; // ms public override void Setup () { // Demo #1 - Use System.Timer (and threading) @@ -101,18 +155,10 @@ namespace UICatalog { _systemTimer = new Timer ((o) => { // Note the check for Mainloop being valid. System.Timers can run after they are Disposed. // This code must be defensive for that. - Application.MainLoop?.Invoke (() => systemTimerDemo.PulseBtnClick ()); + Application.MainLoop?.Invoke (() => systemTimerDemo.Pulse ()); }, null, 0, _systemTimerTick); }; - systemTimerDemo.PulseBtnClick = () => { - if (systemTimerDemo.ActivityProgressBar.Fraction + 0.01F >= 1) { - systemTimerDemo.ActivityProgressBar.Fraction = 0F; - } else { - systemTimerDemo.ActivityProgressBar.Fraction += 0.01F; - } - systemTimerDemo.PulseProgressBar.Pulse (); - }; systemTimerDemo.StopBtnClick = () => { _systemTimer?.Dispose (); _systemTimer = null; @@ -120,8 +166,20 @@ namespace UICatalog { systemTimerDemo.ActivityProgressBar.Fraction = 1F; systemTimerDemo.PulseProgressBar.Fraction = 1F; }; + systemTimerDemo.Speed.Text = $"{_systemTimerTick}"; + systemTimerDemo.Speed.Changed += (sender, a) => { + uint result; + if (uint.TryParse (systemTimerDemo.Speed.Text.ToString(), out result)) { + _systemTimerTick = result; + System.Diagnostics.Debug.WriteLine ($"{_systemTimerTick}"); + if (systemTimerDemo.Started) { + systemTimerDemo.Start (); + } - + } else { + System.Diagnostics.Debug.WriteLine ("bad entry"); + } + }; Win.Add (systemTimerDemo); // Demo #2 - Use Application.MainLoop.AddTimeout (no threads) @@ -137,18 +195,10 @@ namespace UICatalog { mainLoopTimeoutDemo.PulseProgressBar.Fraction = 0F; _mainLoopTimeout = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (_mainLooopTimeoutTick), (loop) => { - mainLoopTimeoutDemo?.PulseBtnClick (); + mainLoopTimeoutDemo.Pulse (); return true; }); }; - mainLoopTimeoutDemo.PulseBtnClick = () => { - if (mainLoopTimeoutDemo.ActivityProgressBar.Fraction + 0.01F >= 1) { - mainLoopTimeoutDemo.ActivityProgressBar.Fraction = 0F; - } else { - mainLoopTimeoutDemo.ActivityProgressBar.Fraction += 0.01F; - } - mainLoopTimeoutDemo.PulseProgressBar.Pulse (); - }; mainLoopTimeoutDemo.StopBtnClick = () => { if (_mainLoopTimeout != null) { Application.MainLoop.RemoveTimeout (_mainLoopTimeout); @@ -158,8 +208,29 @@ namespace UICatalog { mainLoopTimeoutDemo.ActivityProgressBar.Fraction = 1F; mainLoopTimeoutDemo.PulseProgressBar.Fraction = 1F; }; + + mainLoopTimeoutDemo.Speed.Text = $"{_mainLooopTimeoutTick}"; + mainLoopTimeoutDemo.Speed.Changed += (sender, a) => { + uint result; + if (uint.TryParse (mainLoopTimeoutDemo.Speed.Text.ToString (), out result)) { + _mainLooopTimeoutTick = result; + if (mainLoopTimeoutDemo.Started) { + mainLoopTimeoutDemo.Start (); + } + } + }; Win.Add (mainLoopTimeoutDemo); + var startBoth = new Button ("Start Both") { + X = Pos.Center (), + Y = Pos.AnchorEnd () - 1, + }; + startBoth.Clicked = () => { + systemTimerDemo.Start (); + mainLoopTimeoutDemo.Start (); + }; + Win.Add (startBoth); + } protected override void Dispose (bool disposing)