diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
index 9e77a775f..66eeed296 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
@@ -1451,16 +1451,20 @@ namespace Terminal.Gui {
{
TerminalResized = terminalResized;
- var winSize = WinConsole.GetConsoleOutputWindow (out Point pos);
- cols = winSize.Width;
- rows = winSize.Height;
+ try {
+ var winSize = WinConsole.GetConsoleOutputWindow (out Point pos);
+ cols = winSize.Width;
+ rows = winSize.Height;
- WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
+ WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
- ResizeScreen ();
- UpdateOffScreen ();
+ ResizeScreen ();
+ UpdateOffScreen ();
- CreateColors ();
+ CreateColors ();
+ } catch (Win32Exception e) {
+ throw new InvalidOperationException ("The Windows Console output window is not available.", e);
+ }
}
public override void ResizeScreen ()
diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs
index f9954d0bf..1b6cb2489 100644
--- a/Terminal.Gui/Core/Application.cs
+++ b/Terminal.Gui/Core/Application.cs
@@ -329,29 +329,28 @@ namespace Terminal.Gui {
/// into a single call. An applciation cam use
/// without explicitly calling .
///
- /// The to use. If not specified the default driver for the
+ ///
+ /// The to use. If not specified the default driver for the
/// platform will be used (see , , and ).
- /// Specifies the to use.
- public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver, resetState: true);
+ ///
+ /// Specifies the to use.
+ /// Must not be if is not .
+ ///
+ public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => InternalInit (() => Toplevel.Create (), driver, mainLoopDriver);
internal static bool _initialized = false;
internal static int _mainThreadId = -1;
- ///
- /// Internal function for initializing a Terminal.Gui application with a factory object,
- /// a , and .
- ///
- /// This is a low-level function; most applications will use as it is simpler.
- ///
- /// Specifies the factory funtion./>
- /// The to use. If not specified the default driver for the
- /// platform will be used (see , , and ).
- /// Specifies the to use.
- /// If (default) all state will be reset.
- /// Set to to not reset the state (for when this function is called via
- /// when
- /// has not already been called. f
- internal static void Init (Func topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null, bool resetState = true)
+ // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop.
+ //
+ // Called from:
+ //
+ // Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset.
+ // Run() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run() to be called without calling Init first.
+ // Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset.
+ //
+ // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset.
+ internal static void InternalInit (Func topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null, bool calledViaRunT = false)
{
if (_initialized && driver == null) return;
@@ -359,6 +358,7 @@ namespace Terminal.Gui {
throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown.");
}
+ // Note in this case, we don't verify the type of the Toplevel created by new T().
// Used only for start debugging on Unix.
//#if DEBUG
// while (!System.Diagnostics.Debugger.IsAttached) {
@@ -367,15 +367,18 @@ namespace Terminal.Gui {
// System.Diagnostics.Debugger.Break ();
//#endif
- // Reset all class variables (Application is a singleton).
- if (resetState) {
+ if (!calledViaRunT) {
+ // Reset all class variables (Application is a singleton).
ResetState ();
}
- // This supports Unit Tests and the passing of a mock driver/loopdriver
+ // FakeDriver (for UnitTests)
if (driver != null) {
if (mainLoopDriver == null) {
- throw new ArgumentNullException ("mainLoopDriver cannot be null if driver is provided.");
+ throw new ArgumentNullException ("InternalInit mainLoopDriver cannot be null if driver is provided.");
+ }
+ if (!(driver is FakeDriver)) {
+ throw new InvalidOperationException ("InternalInit can only be called with FakeDriver.");
}
Driver = driver;
}
@@ -395,7 +398,16 @@ namespace Terminal.Gui {
}
MainLoop = new MainLoop (mainLoopDriver);
- Driver.Init (TerminalResized);
+ try {
+ Driver.Init (TerminalResized);
+ } catch (InvalidOperationException ex) {
+ // This is a case where the driver is unable to initialize the console.
+ // This can happen if the console is already in use by another process or
+ // if running in unit tests.
+ // In this case, we want to throw a more specific exception.
+ throw new InvalidOperationException ("Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.", ex);
+ }
+
SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
Top = topLevelFactory ();
@@ -1250,8 +1262,7 @@ namespace Terminal.Gui {
/// Runs the application by calling
/// with a new instance of the specified -derived class.
///
- /// If has not arleady been called, this function will
- /// call .
+ /// Calling first is not needed as this function will initialze the
///
///
/// must be called when the application is closing (typically after Run> has
@@ -1263,27 +1274,36 @@ namespace Terminal.Gui {
///
///
/// The to use. If not specified the default driver for the
- /// platform will be used (see , , and ).
+ /// platform will be used (, , or ).
+ /// This parameteter must be if has already been called.
+ ///
/// Specifies the to use.
public static void Run (Func errorHandler = null, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) where T : Toplevel, new()
{
- if (_initialized && Driver != null) {
- var top = new T ();
- var type = top.GetType ().BaseType;
- while (type != typeof (Toplevel) && type != typeof (object)) {
- type = type.BaseType;
+ if (_initialized) {
+ if (Driver != null) {
+ // Init() has been called and we have a driver, so just run the app.
+ var top = new T ();
+ var type = top.GetType ().BaseType;
+ while (type != typeof (Toplevel) && type != typeof (object)) {
+ type = type.BaseType;
+ }
+ if (type != typeof (Toplevel)) {
+ throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel");
+ }
+ Run (top, errorHandler);
+ } else {
+ // This codepath should be impossible because Init(null, null) will select the platform default driver
+ throw new InvalidOperationException ("Init() completed without a driver being set (this should be impossible); Run() cannot be called.");
}
- if (type != typeof (Toplevel)) {
- throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel");
- }
- // Run() will eventually cause Application.Top to be set, via Begin() and SetCurrentAsTop()
- Run (top, errorHandler);
} else {
- if (!_initialized && driver == null) {
- throw new ArgumentException ("Init has not been called; a valid driver and mainloop must be provided");
+ // Init() has NOT been called.
+ if (driver != null) {
+ // Caller has provided a driver so call Init with it (but set calledViaRunT to true so we don't reset Application state).
+ InternalInit (() => new T (), driver, mainLoopDriver, calledViaRunT: true);
+ } else {
+ throw new ArgumentException ("A Driver must be specified when calling Run() when Init() has not been called.");
}
- // Note in this case, we don't verify the type of the Toplevel created by new T().
- Init (() => new T (), Driver == null ? driver : Driver, Driver == null ? mainLoopDriver : null, resetState: false);
Run (Top, errorHandler);
}
}
diff --git a/UICatalog/Scenarios/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs
index 13ebcc1a4..2e566ebe5 100644
--- a/UICatalog/Scenarios/BackgroundWorkerCollection.cs
+++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs
@@ -12,10 +12,6 @@ namespace UICatalog.Scenarios {
[ScenarioCategory ("Dialogs")]
[ScenarioCategory ("Controls")]
public class BackgroundWorkerCollection : Scenario {
- public override void Init (ColorScheme colorScheme)
- {
- // Do not call Init as Application.Run will do it
- }
public override void Run ()
{
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index d4a7594d5..979b9eed7 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -117,6 +117,7 @@ namespace UICatalog {
// Run UI Catalog UI. When it exits, if _selectedScenario is != null then
// a Scenario was selected. Otherwise, the user wants to exit UI Catalog.
+ Application.Init ();
Application.Run ();
Application.Shutdown ();
diff --git a/UnitTests/ApplicationTests.cs b/UnitTests/ApplicationTests.cs
index ef1fd01e6..911154a66 100644
--- a/UnitTests/ApplicationTests.cs
+++ b/UnitTests/ApplicationTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
@@ -100,7 +101,7 @@ namespace Terminal.Gui.Core {
Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
Toplevel topLevel = null;
- Assert.Throws (() => Application.Init (() => topLevel = new TestToplevel (), new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))));
+ Assert.Throws (() => Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))));
Shutdown ();
Assert.Null (Application.Top);
@@ -109,7 +110,7 @@ namespace Terminal.Gui.Core {
// Now try the other way
topLevel = null;
- Application.Init (() => topLevel = new TestToplevel (), new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+ Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
Assert.Throws (() => Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))));
Shutdown ();
@@ -118,7 +119,7 @@ namespace Terminal.Gui.Core {
Assert.Null (Application.MainLoop);
Assert.Null (Application.Driver);
}
-
+
class TestToplevel : Toplevel {
public TestToplevel ()
@@ -131,7 +132,7 @@ namespace Terminal.Gui.Core {
public void Init_Begin_End_Cleans_Up ()
{
Init ();
-
+
// Begin will cause Run() to be called, which will call Begin(). Thus will block the tests
// if we don't stop
Application.Iteration = () => {
@@ -145,7 +146,7 @@ namespace Terminal.Gui.Core {
};
Application.NotifyNewRunState += NewRunStateFn;
- Toplevel topLevel = new Toplevel();
+ Toplevel topLevel = new Toplevel ();
var rs = Application.Begin (topLevel);
Assert.NotNull (rs);
Assert.NotNull (runstate);
@@ -181,7 +182,7 @@ namespace Terminal.Gui.Core {
// NOTE: Run, when called after Init has been called behaves differently than
// when called if Init has not been called.
Toplevel topLevel = null;
- Application.Init (() => topLevel = new TestToplevel (), new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+ Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
Application.RunState runstate = null;
Action NewRunStateFn = (rs) => {
@@ -272,9 +273,56 @@ namespace Terminal.Gui.Core {
Application.Iteration = () => {
Application.RequestStop ();
};
-
+
// Init has been called and we're passing no driver to Run. This is ok.
- Application.Run (errorHandler: null);
+ Application.Run ();
+
+ Shutdown ();
+
+ Assert.Null (Application.Top);
+ Assert.Null (Application.MainLoop);
+ Assert.Null (Application.Driver);
+ }
+
+ [Fact]
+ public void Run_T_After_InitNullDriver_with_TestTopLevel_Throws ()
+ {
+ var p = Environment.OSVersion.Platform;
+ if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
+ Assert.Throws (() => Application.Init (null, null));
+ } else {
+ Application.Init (null, null);
+ Assert.Equal (typeof (CursesDriver), Application.Driver.GetType ());
+ Application.Shutdown ();
+ }
+
+ Application.Iteration = () => {
+ Application.RequestStop ();
+ };
+
+ // Init has been called without selecting a driver and we're passing no driver to Run. Bad
+ Assert.Throws (() => Application.Run ());
+
+ Shutdown ();
+
+ Assert.Null (Application.Top);
+ Assert.Null (Application.MainLoop);
+ Assert.Null (Application.Driver);
+ }
+
+ [Fact]
+ public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws ()
+ {
+ Init ();
+
+ Application.Driver = null;
+
+ Application.Iteration = () => {
+ Application.RequestStop ();
+ };
+
+ // Init has been called, but Driver has been set to null. Bad.
+ Assert.Throws (() => Application.Run ());
Shutdown ();
@@ -379,6 +427,9 @@ namespace Terminal.Gui.Core {
Application.Shutdown ();
Assert.Equal (3, count);
}
+
+ // TODO: Add tests for Run that test errorHandler
+
#endregion
#region ShutdownTests
@@ -407,7 +458,7 @@ namespace Terminal.Gui.Core {
Assert.Null (SynchronizationContext.Current);
}
#endregion
-
+
[Fact]
[AutoInitShutdown]
public void SetCurrentAsTop_Run_A_Not_Modal_Toplevel_Make_It_The_Current_Application_Top ()