Browse Source

Merge branch 'develop' into fix-nonbmp

Tig 2 years ago
parent
commit
6af2fd12d3
69 changed files with 3431 additions and 2107 deletions
  1. 65 50
      Example/Example.cs
  2. 65 50
      README.md
  3. 11 7
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  4. 240 100
      Terminal.Gui/Core/Application.cs
  5. 1 1
      Terminal.Gui/Core/ConsoleDriver.cs
  6. 2 2
      Terminal.Gui/Core/MainLoop.cs
  7. 4 4
      Terminal.Gui/Core/Toplevel.cs
  8. 42 40
      Terminal.Gui/Core/Trees/Branch.cs
  9. 15 10
      Terminal.Gui/Core/Trees/TreeStyle.cs
  10. 1 1
      Terminal.Gui/Core/View.cs
  11. 7 1
      Terminal.Gui/Views/GraphView.cs
  12. 3 24
      Terminal.Gui/Views/TabView.cs
  13. 236 192
      Terminal.Gui/Views/TextView.cs
  14. 8 4
      Terminal.Gui/Views/TreeView.cs
  15. 1 1
      Terminal.Gui/Windows/FileDialog.cs
  16. 1 1
      Terminal.Gui/Windows/Wizard.cs
  17. 8 0
      UICatalog/Properties/launchSettings.json
  18. 8 14
      UICatalog/Scenario.cs
  19. 10 24
      UICatalog/Scenarios/AllViewsTester.cs
  20. 1 8
      UICatalog/Scenarios/BackgroundWorkerCollection.cs
  21. 4 6
      UICatalog/Scenarios/BordersComparisons.cs
  22. 2 2
      UICatalog/Scenarios/Buttons.cs
  23. 353 43
      UICatalog/Scenarios/CharacterMap.cs
  24. 20 7
      UICatalog/Scenarios/ClassExplorer.cs
  25. 4 7
      UICatalog/Scenarios/Clipping.cs
  26. 11 13
      UICatalog/Scenarios/CollectionNavigatorTester.cs
  27. 2 2
      UICatalog/Scenarios/ComputedLayout.cs
  28. 1 1
      UICatalog/Scenarios/ContextMenus.cs
  29. 3 3
      UICatalog/Scenarios/CsvEditor.cs
  30. 2 2
      UICatalog/Scenarios/Dialogs.cs
  31. 2 3
      UICatalog/Scenarios/DynamicMenuBar.cs
  32. 2 3
      UICatalog/Scenarios/DynamicStatusBar.cs
  33. 7 7
      UICatalog/Scenarios/Editor.cs
  34. 3 3
      UICatalog/Scenarios/GraphViewExample.cs
  35. 2 2
      UICatalog/Scenarios/HexEditor.cs
  36. 3 3
      UICatalog/Scenarios/InteractiveTree.cs
  37. 6 7
      UICatalog/Scenarios/Keys.cs
  38. 3 3
      UICatalog/Scenarios/LabelsAsButtons.cs
  39. 3 3
      UICatalog/Scenarios/LineViewExample.cs
  40. 2 2
      UICatalog/Scenarios/MessageBoxes.cs
  41. 3 3
      UICatalog/Scenarios/MultiColouredTable.cs
  42. 5 6
      UICatalog/Scenarios/Notepad.cs
  43. 2 2
      UICatalog/Scenarios/ProgressBarStyles.cs
  44. 76 0
      UICatalog/Scenarios/RunTExample.cs
  45. 1 1
      UICatalog/Scenarios/RuneWidthGreaterThanOne.cs
  46. 4 4
      UICatalog/Scenarios/Scrolling.cs
  47. 2 2
      UICatalog/Scenarios/SingleBackgroundWorker.cs
  48. 3 3
      UICatalog/Scenarios/SyntaxHighlighting.cs
  49. 3 7
      UICatalog/Scenarios/TabViewExample.cs
  50. 4 4
      UICatalog/Scenarios/TableEditor.cs
  51. 84 33
      UICatalog/Scenarios/Text.cs
  52. 1 1
      UICatalog/Scenarios/TextFormatterDemo.cs
  53. 2 2
      UICatalog/Scenarios/TextViewAutocompletePopup.cs
  54. 2 2
      UICatalog/Scenarios/Threading.cs
  55. 3 3
      UICatalog/Scenarios/TreeUseCases.cs
  56. 107 78
      UICatalog/Scenarios/TreeViewFileSystem.cs
  57. 2 2
      UICatalog/Scenarios/Unicode.cs
  58. 38 38
      UICatalog/Scenarios/WindowsAndFrameViews.cs
  59. 4 5
      UICatalog/Scenarios/WizardAsView.cs
  60. 2 2
      UICatalog/Scenarios/Wizards.cs
  61. 402 390
      UICatalog/UICatalog.cs
  62. 256 856
      UnitTests/ApplicationTests.cs
  63. 3 3
      UnitTests/MainLoopTests.cs
  64. 695 0
      UnitTests/MdiTests.cs
  65. 105 0
      UnitTests/RunStateTests.cs
  66. 9 3
      UnitTests/ScenarioTests.cs
  67. 83 0
      UnitTests/SynchronizatonContextTests.cs
  68. 364 1
      UnitTests/TextViewTests.cs
  69. 2 0
      UnitTests/ToplevelTests.cs

+ 65 - 50
Example/Example.cs

@@ -5,53 +5,68 @@
 
 using Terminal.Gui;
 
-// Initialize the console
-Application.Init();
-
-// Creates the top-level window with border and title
-var win = new Window("Example App (Ctrl+Q to quit)");
-
-// Create input components and labels
-
-var usernameLabel = new Label("Username:");
-var usernameText = new TextField("")
-{
-    // Position text field adjacent to label
-    X = Pos.Right(usernameLabel) + 1,
-
-    // Fill remaining horizontal space with a margin of 1
-    Width = Dim.Fill(1),
-};
-
-var passwordLabel = new Label(0,2,"Password:");
-var passwordText = new TextField("")
-{
-    Secret = true,
-    // align with the text box above
-    X = Pos.Left(usernameText),
-    Y = 2,
-    Width = Dim.Fill(1),
-};
-
-// Create login button
-var btnLogin = new Button("Login")
-{
-    Y = 4,
-    // center the login button horizontally
-    X = Pos.Center(),
-    IsDefault = true,
-};
-
-// When login button is clicked display a message popup
-btnLogin.Clicked += () => MessageBox.Query("Logging In", "Login Successful", "Ok");
-
-// Add all the views to the window
-win.Add(
-    usernameLabel, usernameText, passwordLabel, passwordText,btnLogin
-);
-
-// Show the application
-Application.Run(win);
-
-// After the application exits, release and reset console for clean shutdown
-Application.Shutdown();
+Application.Run<ExampleWindow> ();
+
+System.Console.WriteLine ($"Username: {((ExampleWindow)Application.Top).usernameText.Text}");
+
+// Before the application exits, reset Terminal.Gui for clean shutdown
+Application.Shutdown ();
+
+// Defines a top-level window with border and title
+public class ExampleWindow : Window {
+	public TextField usernameText;
+	
+	public ExampleWindow ()
+	{
+		Title = "Example App (Ctrl+Q to quit)";
+
+		// Create input components and labels
+		var usernameLabel = new Label () { 
+			Text = "Username:" 
+		};
+
+		usernameText = new TextField ("") {
+			// Position text field adjacent to the label
+			X = Pos.Right (usernameLabel) + 1,
+
+			// Fill remaining horizontal space
+			Width = Dim.Fill (),
+		};
+
+		var passwordLabel = new Label () {
+			Text = "Password:",
+			X = Pos.Left (usernameLabel),
+			Y = Pos.Bottom (usernameLabel) + 1
+		};
+
+		var passwordText = new TextField ("") {
+			Secret = true,
+			// align with the text box above
+			X = Pos.Left (usernameText),
+			Y = Pos.Top (passwordLabel),
+			Width = Dim.Fill (),
+		};
+
+		// Create login button
+		var btnLogin = new Button () {
+			Text = "Login",
+			Y = Pos.Bottom(passwordLabel) + 1,
+			// center the login button horizontally
+			X = Pos.Center (),
+			IsDefault = true,
+		};
+
+		// When login button is clicked display a message popup
+		btnLogin.Clicked += () => {
+			if (usernameText.Text == "admin" && passwordText.Text == "password") {
+				MessageBox.Query ("Logging In", "Login Successful", "Ok");
+				Application.RequestStop ();
+			} else {
+				MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok");
+			}
+		};
+
+		// Add the views to the Window
+		Add (usernameLabel, usernameText, passwordLabel, passwordText, btnLogin);
+	}
+}

+ 65 - 50
README.md

@@ -68,56 +68,71 @@ The following example shows a basic Terminal.Gui application written in C#:
 
 using Terminal.Gui;
 
-// Initialize the console
-Application.Init();
-
-// Creates the top-level window with border and title
-var win = new Window("Example App (Ctrl+Q to quit)");
-
-// Create input components and labels
-
-var usernameLabel = new Label("Username:");
-var usernameText = new TextField("")
-{
-    // Position text field adjacent to label
-    X = Pos.Right(usernameLabel) + 1,
-
-    // Fill remaining horizontal space with a margin of 1
-    Width = Dim.Fill(1),
-};
-
-var passwordLabel = new Label(0,2,"Password:");
-var passwordText = new TextField("")
-{
-    Secret = true,
-    // align with the text box above
-    X = Pos.Left(usernameText),
-    Y = 2,
-    Width = Dim.Fill(1),
-};
-
-// Create login button
-var btnLogin = new Button("Login")
-{
-    Y = 4,
-    // center the login button horizontally
-    X = Pos.Center(),
-    IsDefault = true,
-};
-
-// When login button is clicked display a message popup
-btnLogin.Clicked += () => MessageBox.Query("Logging In", "Login Successful", "Ok");
-
-// Add all the views to the window
-win.Add(
-    usernameLabel, usernameText, passwordLabel, passwordText,btnLogin
-);
-
-// Show the application
-Application.Run(win);
-
-// After the application exits, release and reset console for clean shutdown
-Application.Shutdown();
+Application.Run<ExampleWindow> ();
+
+System.Console.WriteLine ($"Username: {((ExampleWindow)Application.Top).usernameText.Text}");
+
+// Before the application exits, reset Terminal.Gui for clean shutdown
+Application.Shutdown ();
+
+// Defines a top-level window with border and title
+public class ExampleWindow : Window {
+	public TextField usernameText;
+	
+	public ExampleWindow ()
+	{
+		Title = "Example App (Ctrl+Q to quit)";
+
+		// Create input components and labels
+		var usernameLabel = new Label () { 
+			Text = "Username:" 
+		};
+
+		usernameText = new TextField ("") {
+			// Position text field adjacent to the label
+			X = Pos.Right (usernameLabel) + 1,
+
+			// Fill remaining horizontal space
+			Width = Dim.Fill (),
+		};
+
+		var passwordLabel = new Label () {
+			Text = "Password:",
+			X = Pos.Left (usernameLabel),
+			Y = Pos.Bottom (usernameLabel) + 1
+		};
+
+		var passwordText = new TextField ("") {
+			Secret = true,
+			// align with the text box above
+			X = Pos.Left (usernameText),
+			Y = Pos.Top (passwordLabel),
+			Width = Dim.Fill (),
+		};
+
+		// Create login button
+		var btnLogin = new Button () {
+			Text = "Login",
+			Y = Pos.Bottom(passwordLabel) + 1,
+			// center the login button horizontally
+			X = Pos.Center (),
+			IsDefault = true,
+		};
+
+		// When login button is clicked display a message popup
+		btnLogin.Clicked += () => {
+			if (usernameText.Text == "admin" && passwordText.Text == "password") {
+				MessageBox.Query ("Logging In", "Login Successful", "Ok");
+				Application.RequestStop ();
+			} else {
+				MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok");
+			}
+		};
+
+		// Add the views to the Window
+		Add (usernameLabel, usernameText, passwordLabel, passwordText, btnLogin);
+	}
+}
 ```
 
 When run the application looks as follows:

+ 11 - 7
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 ()

+ 240 - 100
Terminal.Gui/Core/Application.cs

@@ -39,6 +39,7 @@ namespace Terminal.Gui {
 	/// };
 	/// Application.Top.Add(win);
 	/// Application.Run();
+	/// Application.Shutdown();
 	/// </code>
 	/// </example>
 	/// <remarks>
@@ -222,19 +223,28 @@ namespace Terminal.Gui {
 		public static bool ExitRunLoopAfterFirstIteration { get; set; } = false;
 
 		/// <summary>
-		/// Notify that a new <see cref="RunState"/> token was created,
-		/// used if <see cref="ExitRunLoopAfterFirstIteration"/> is true.
+		/// Notify that a new <see cref="RunState"/> was created (<see cref="Begin(Toplevel)"/> was called). The token is created in 
+		/// <see cref="Begin(Toplevel)"/> and this event will be fired before that function exits.
 		/// </summary>
+		/// <remarks>
+		///	If <see cref="ExitRunLoopAfterFirstIteration"/> is <see langword="true"/> callers to
+		///	<see cref="Begin(Toplevel)"/> must also subscribe to <see cref="NotifyStopRunState"/>
+		///	and manually dispose of the <see cref="RunState"/> token when the application is done.
+		/// </remarks>
 		public static event Action<RunState> NotifyNewRunState;
 
 		/// <summary>
-		/// Notify that a existent <see cref="RunState"/> token is stopping,
-		/// used if <see cref="ExitRunLoopAfterFirstIteration"/> is true.
+		/// Notify that a existent <see cref="RunState"/> is stopping (<see cref="End(RunState)"/> was called).
 		/// </summary>
+		/// <remarks>
+		///	If <see cref="ExitRunLoopAfterFirstIteration"/> is <see langword="true"/> callers to
+		///	<see cref="Begin(Toplevel)"/> must also subscribe to <see cref="NotifyStopRunState"/>
+		///	and manually dispose of the <see cref="RunState"/> token when the application is done.
+		/// </remarks>
 		public static event Action<Toplevel> NotifyStopRunState;
 
 		/// <summary>
-		///   This event is raised on each iteration of the <see cref="MainLoop"/> 
+		///   This event is raised on each iteration of the <see cref="MainLoop"/>. 
 		/// </summary>
 		/// <remarks>
 		///   See also <see cref="Timeout"/>
@@ -299,36 +309,59 @@ namespace Terminal.Gui {
 		/// </summary>
 		public static bool UseSystemConsole;
 
+		// For Unit testing - ignores UseSystemConsole
+		internal static bool ForceFakeConsole;
+
 		/// <summary>
 		/// Initializes a new instance of <see cref="Terminal.Gui"/> Application. 
 		/// </summary>
-		/// <remarks>
 		/// <para>
 		/// Call this method once per instance (or after <see cref="Shutdown"/> has been called).
 		/// </para>
 		/// <para>
-		/// Loads the right <see cref="ConsoleDriver"/> for the platform.
+		/// This function loads the right <see cref="ConsoleDriver"/> for the platform, 
+		/// Creates a <see cref="Toplevel"/>. and assigns it to <see cref="Top"/>
 		/// </para>
 		/// <para>
-		/// Creates a <see cref="Toplevel"/> and assigns it to <see cref="Top"/>
+		/// <see cref="Shutdown"/> must be called when the application is closing (typically after <see cref="Run(Func{Exception, bool})"/> has 
+		/// returned) to ensure resources are cleaned up and terminal settings restored.
 		/// </para>
-		/// </remarks>
-		public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver);
+		/// <para>
+		/// The <see cref="Run{T}(Func{Exception, bool}, ConsoleDriver, IMainLoopDriver)"/> function 
+		/// combines <see cref="Init(ConsoleDriver, IMainLoopDriver)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
+		/// into a single call. An applciation cam use <see cref="Run{T}(Func{Exception, bool}, ConsoleDriver, IMainLoopDriver)"/> 
+		/// without explicitly calling <see cref="Init(ConsoleDriver, IMainLoopDriver)"/>.
+		/// </para>
+		/// <param name="driver">
+		/// The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the
+		/// platform will be used (see <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, and <see cref="NetDriver"/>).</param>
+		/// <param name="mainLoopDriver">
+		/// Specifies the <see cref="MainLoop"/> to use. 
+		/// Must not be <see langword="null"/> if <paramref name="driver"/> is not <see langword="null"/>.
+		/// </param>
+		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;
 
-		/// <summary>
-		/// Initializes the Terminal.Gui application
-		/// </summary>
-		static void Init (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null)
+		// 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<T>() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run<T>() 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<Toplevel> topLevelFactory, ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null, bool calledViaRunT = false)
 		{
 			if (_initialized && driver == null) return;
 
 			if (_initialized) {
-				throw new InvalidOperationException ("Init must be bracketed by Shutdown");
+				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) {
@@ -337,23 +370,29 @@ namespace Terminal.Gui {
 			//			System.Diagnostics.Debugger.Break ();
 			//#endif
 
-			// Reset all class variables (Application is a singleton).
-			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;
-				Driver.Init (TerminalResized);
-				MainLoop = new MainLoop (mainLoopDriver);
-				SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
 			}
 
 			if (Driver == null) {
 				var p = Environment.OSVersion.Platform;
-				if (UseSystemConsole) {
+				if (ForceFakeConsole) {
+					// For Unit Testing only
+					Driver = new FakeDriver ();
+					mainLoopDriver = new FakeMainLoop (() => FakeConsole.ReadKey (true));
+				} else if (UseSystemConsole) {
 					Driver = new NetDriver ();
 					mainLoopDriver = new NetMainLoop (Driver);
 				} else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
@@ -363,10 +402,21 @@ namespace Terminal.Gui {
 					mainLoopDriver = new UnixMainLoop ();
 					Driver = new CursesDriver ();
 				}
+			}
+			MainLoop = new MainLoop (mainLoopDriver);
+
+			try {
 				Driver.Init (TerminalResized);
-				MainLoop = new MainLoop (mainLoopDriver);
-				SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
+			} 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 ();
 			Current = Top;
 			supportedCultures = GetSupportedCultures ();
@@ -375,7 +425,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Captures the execution state for the provided <see cref="Toplevel"/>  view.
+		/// Captures the execution state for the provided <see cref="Toplevel"/> view.
 		/// </summary>
 		public class RunState : IDisposable {
 			/// <summary>
@@ -391,31 +441,61 @@ namespace Terminal.Gui {
 			/// </summary>
 			public Toplevel Toplevel { get; internal set; }
 
+#if DEBUG_IDISPOSABLE
+			/// <summary>
+			/// For debug purposes to verify objects are being disposed properly
+			/// </summary>
+			public bool WasDisposed = false;
+			/// <summary>
+			/// For debug purposes to verify objects are being disposed properly
+			/// </summary>
+			public int DisposedCount = 0;
 			/// <summary>
-			/// Releases alTop = l resource used by the <see cref="Application.RunState"/> object.
+			/// For debug purposes
 			/// </summary>
-			/// <remarks>Call <see cref="Dispose()"/> when you are finished using the <see cref="Application.RunState"/>. The
+			public static List<RunState> Instances = new List<RunState> ();
+			/// <summary>
+			/// For debug purposes
+			/// </summary>
+			public RunState ()
+			{
+				Instances.Add (this);
+			}
+#endif
+
+			/// <summary>
+			/// Releases all resource used by the <see cref="Application.RunState"/> object.
+			/// </summary>
+			/// <remarks>
+			/// Call <see cref="Dispose()"/> when you are finished using the <see cref="Application.RunState"/>. 
+			/// </remarks>
+			/// <remarks>
 			/// <see cref="Dispose()"/> method leaves the <see cref="Application.RunState"/> in an unusable state. After
 			/// calling <see cref="Dispose()"/>, you must release all references to the
 			/// <see cref="Application.RunState"/> so the garbage collector can reclaim the memory that the
-			/// <see cref="Application.RunState"/> was occupying.</remarks>
+			/// <see cref="Application.RunState"/> was occupying.
+			/// </remarks>
 			public void Dispose ()
 			{
 				Dispose (true);
 				GC.SuppressFinalize (this);
+#if DEBUG_IDISPOSABLE
+				WasDisposed = true;
+#endif
 			}
 
 			/// <summary>
-			/// Dispose the specified disposing.
+			/// Releases all resource used by the <see cref="Application.RunState"/> object.
 			/// </summary>
-			/// <returns>The dispose.</returns>
-			/// <param name="disposing">If set to <c>true</c> disposing.</param>
+			/// <param name="disposing">If set to <see langword="true"/> we are disposing and should dispose held objects.</param>
 			protected virtual void Dispose (bool disposing)
 			{
 				if (Toplevel != null && disposing) {
-					End (Toplevel);
-					Toplevel.Dispose ();
-					Toplevel = null;
+					throw new InvalidOperationException ("You must clean up (Dispose) the Toplevel before calling Application.RunState.Dispose");
+					// BUGBUG: It's insidious that we call EndFirstTopLevel here so I moved it to End.
+					//EndFirstTopLevel (Toplevel);
+					//Toplevel.Dispose ();
+					//Toplevel = null;
 				}
 			}
 		}
@@ -802,8 +882,8 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Building block API: Prepares the provided <see cref="Toplevel"/>  for execution.
 		/// </summary>
-		/// <returns>The runstate handle that needs to be passed to the <see cref="End(RunState)"/> method upon completion.</returns>
-		/// <param name="toplevel">Toplevel to prepare execution for.</param>
+		/// <returns>The <see cref="RunState"/> handle that needs to be passed to the <see cref="End(RunState)"/> method upon completion.</returns>
+		/// <param name="toplevel">The <see cref="Toplevel"/> to prepare execution for.</param>
 		/// <remarks>
 		///  This method prepares the provided toplevel for running with the focus,
 		///  it adds this to the list of toplevels, sets up the mainloop to process the
@@ -816,13 +896,12 @@ namespace Terminal.Gui {
 		{
 			if (toplevel == null) {
 				throw new ArgumentNullException (nameof (toplevel));
-			} else if (toplevel.IsMdiContainer && MdiTop != null) {
+			} else if (toplevel.IsMdiContainer && MdiTop != toplevel && MdiTop != null) {
 				throw new InvalidOperationException ("Only one Mdi Container is allowed.");
 			}
 
 			var rs = new RunState (toplevel);
-
-			Init ();
+			
 			if (toplevel is ISupportInitializeNotification initializableNotification &&
 			    !initializableNotification.IsInitialized) {
 				initializableNotification.BeginInit ();
@@ -833,6 +912,13 @@ namespace Terminal.Gui {
 			}
 
 			lock (toplevels) {
+				// If Top was already initialized with Init, and Begin has never been called
+				// Top was not added to the toplevels Stack. It will thus never get disposed.
+				// Clean it up here:
+				if (Top != null && toplevel != Top && !toplevels.Contains(Top)) {
+					Top.Dispose ();
+					Top = null;
+				}
 				if (string.IsNullOrEmpty (toplevel.Id.ToString ())) {
 					var count = 1;
 					var id = (toplevels.Count + count).ToString ();
@@ -854,7 +940,8 @@ namespace Terminal.Gui {
 					throw new ArgumentException ("There are duplicates toplevels Id's");
 				}
 			}
-			if (toplevel.IsMdiContainer) {
+			// Fix $520 - Set Top = toplevel if Top == null
+			if (Top == null || toplevel.IsMdiContainer) {
 				Top = toplevel;
 			}
 
@@ -893,13 +980,14 @@ namespace Terminal.Gui {
 				Driver.Refresh ();
 			}
 
+			NotifyNewRunState?.Invoke (rs);
 			return rs;
 		}
 
 		/// <summary>
-		/// Building block API: completes the execution of a <see cref="Toplevel"/>  that was started with <see cref="Begin(Toplevel)"/> .
+		/// Building block API: completes the execution of a <see cref="Toplevel"/> that was started with <see cref="Begin(Toplevel)"/> .
 		/// </summary>
-		/// <param name="runState">The runstate returned by the <see cref="Begin(Toplevel)"/> method.</param>
+		/// <param name="runState">The <see cref="RunState"/> returned by the <see cref="Begin(Toplevel)"/> method.</param>
 		public static void End (RunState runState)
 		{
 			if (runState == null)
@@ -910,12 +998,52 @@ namespace Terminal.Gui {
 			} else {
 				runState.Toplevel.OnUnloaded ();
 			}
+
+			// End the RunState.Toplevel 
+			// First, take it off the toplevel Stack
+			if (toplevels.Count > 0) {
+				if (toplevels.Peek () != runState.Toplevel) {
+					// If there the top of the stack is not the RunState.Toplevel then
+					// this call to End is not balanced with the call to Begin that started the RunState
+					throw new ArgumentException ("End must be balanced with calls to Begin");
+				}
+				toplevels.Pop ();
+			}
+
+			// Notify that it is closing
+			runState.Toplevel?.OnClosed (runState.Toplevel);
+
+			// If there is a MdiTop that is not the RunState.Toplevel then runstate.TopLevel 
+			// is a child of MidTop and we should notify the MdiTop that it is closing
+			if (MdiTop != null && !(runState.Toplevel).Modal && runState.Toplevel != MdiTop) {
+				MdiTop.OnChildClosed (runState.Toplevel);
+			}
+
+			// Set Current and Top to the next TopLevel on the stack
+			if (toplevels.Count == 0) {
+				Current = null;
+			} else {
+				Current = toplevels.Peek ();
+				if (toplevels.Count == 1 && Current == MdiTop) {
+					MdiTop.OnAllChildClosed ();
+				} else {
+					SetCurrentAsTop ();
+				}
+				Refresh ();
+			}
+
+			runState.Toplevel?.Dispose ();
+			runState.Toplevel = null;
 			runState.Dispose ();
 		}
 
 		/// <summary>
-		/// Shutdown an application initialized with <see cref="Init(ConsoleDriver, IMainLoopDriver)"/>
+		/// Shutdown an application initialized with <see cref="Init(ConsoleDriver, IMainLoopDriver)"/>.
 		/// </summary>
+		/// <remarks>
+		/// Shutdown must be called for every call to <see cref="Init(ConsoleDriver, IMainLoopDriver)"/> or <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>
+		/// to ensure all resources are cleaned up (Disposed) and terminal settings are restored.
+		/// </remarks>
 		public static void Shutdown ()
 		{
 			ResetState ();
@@ -930,15 +1058,17 @@ namespace Terminal.Gui {
 			// 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;
 				t.Dispose ();
 			}
 			toplevels.Clear ();
 			Current = null;
+			Top?.Dispose ();
 			Top = null;
 
+			// BUGBUG: MdiTop is not cleared here, but it should be?
+
 			MainLoop = null;
 			Driver?.End ();
 			Driver = null;
@@ -990,40 +1120,17 @@ namespace Terminal.Gui {
 			Driver.Refresh ();
 		}
 
-		internal static void End (View view)
-		{
-			if (toplevels.Peek () != view)
-				throw new ArgumentException ("The view that you end with must be balanced");
-			toplevels.Pop ();
-
-			(view as Toplevel)?.OnClosed ((Toplevel)view);
-
-			if (MdiTop != null && !((Toplevel)view).Modal && view != MdiTop) {
-				MdiTop.OnChildClosed (view as Toplevel);
-			}
 
-			if (toplevels.Count == 0) {
-				Current = null;
-			} else {
-				Current = toplevels.Peek ();
-				if (toplevels.Count == 1 && Current == MdiTop) {
-					MdiTop.OnAllChildClosed ();
-				} else {
-					SetCurrentAsTop ();
-				}
-				Refresh ();
-			}
-		}
 
 		/// <summary>
-		///   Building block API: Runs the main loop for the created dialog
+		///   Building block API: Runs the <see cref="MainLoop"/> for the created <see cref="Toplevel"/>.
 		/// </summary>
 		/// <remarks>
-		///   Use the wait parameter to control whether this is a
-		///   blocking or non-blocking call.
+		///   Use the <paramref name="wait"/> parameter to control whether this is a blocking or non-blocking call.
 		/// </remarks>
-		/// <param name="state">The state returned by the Begin method.</param>
-		/// <param name="wait">By default this is true which will execute the runloop waiting for events, if you pass false, you can use this method to run a single iteration of the events.</param>
+		/// <param name="state">The state returned by the <see cref="Begin(Toplevel)"/> method.</param>
+		/// <param name="wait">By default this is <see langword="true"/> which will execute the runloop waiting for events, 
+		/// if set to <see langword="false"/>, a single iteration will execute.</param>
 		public static void RunLoop (RunState state, bool wait = true)
 		{
 			if (state == null)
@@ -1033,18 +1140,21 @@ namespace Terminal.Gui {
 
 			bool firstIteration = true;
 			for (state.Toplevel.Running = true; state.Toplevel.Running;) {
-				if (ExitRunLoopAfterFirstIteration && !firstIteration)
+				if (ExitRunLoopAfterFirstIteration && !firstIteration) {
 					return;
+				}
 				RunMainLoopIteration (ref state, wait, ref firstIteration);
 			}
 		}
 
 		/// <summary>
-		/// Run one iteration of the MainLoop.
+		/// Run one iteration of the <see cref="MainLoop"/>.
 		/// </summary>
-		/// <param name="state">The state returned by the Begin method.</param>
-		/// <param name="wait">If will execute the runloop waiting for events.</param>
-		/// <param name="firstIteration">If it's the first run loop iteration.</param>
+		/// <param name="state">The state returned by <see cref="Begin(Toplevel)"/>.</param>
+		/// <param name="wait">If <see langword="true"/> will execute the runloop waiting for events. If <see langword="true"/>
+		/// will return after a single iteration.</param>
+		/// <param name="firstIteration">Set to <see langword="true"/> if this is the first run loop iteration. Upon return,
+		/// it will be set to <see langword="false"/> if at least one iteration happened.</param>
 		public static void RunMainLoopIteration (ref RunState state, bool wait, ref bool firstIteration)
 		{
 			if (MainLoop.EventsPending (wait)) {
@@ -1145,30 +1255,57 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> with the value of <see cref="Top"/>
+		/// Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> with the value of <see cref="Top"/>.
 		/// </summary>
+		/// <remarks>
+		/// See <see cref="Run(Toplevel, Func{Exception, bool})"/> for more details.
+		/// </remarks>
 		public static void Run (Func<Exception, bool> errorHandler = null)
 		{
 			Run (Top, errorHandler);
 		}
 
 		/// <summary>
-		/// Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> with a new instance of the specified <see cref="Toplevel"/>-derived class
+		/// Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> 
+		/// with a new instance of the specified <see cref="Toplevel"/>-derived class.
+		/// <para>
+		/// Calling <see cref="Init(ConsoleDriver, IMainLoopDriver)"/> first is not needed as this function will initialze the application.
+		/// </para>
+		/// <para>
+		/// <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has 
+		/// returned) to ensure resources are cleaned up and terminal settings restored.
+		/// </para>
 		/// </summary>
-		public static void Run<T> (Func<Exception, bool> errorHandler = null) where T : Toplevel, new()
+		/// <remarks>
+		/// See <see cref="Run(Toplevel, Func{Exception, bool})"/> for more details.
+		/// </remarks>
+		/// <param name="errorHandler"></param>
+		/// <param name="driver">The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the
+		/// platform will be used (<see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>).
+		/// This parameteter must be <see langword="null"/> if <see cref="Init(ConsoleDriver, IMainLoopDriver)"/> has already been called. 
+		/// </param>
+		/// <param name="mainLoopDriver">Specifies the <see cref="MainLoop"/> to use.</param>
+		public static void Run<T> (Func<Exception, bool> 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 (type != typeof (Toplevel)) {
-					throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel");
+			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<T>() cannot be called.");
 				}
-				Run (top, errorHandler);
 			} else {
-				Init (() => new T ());
+				// Init() has NOT been called.
+				InternalInit (() => new T (), driver, mainLoopDriver, calledViaRunT: true);
 				Run (Top, errorHandler);
 			}
 		}
@@ -1192,16 +1329,19 @@ namespace Terminal.Gui {
 		///   <para>
 		///     Alternatively, to have a program control the main loop and 
 		///     process events manually, call <see cref="Begin(Toplevel)"/> to set things up manually and then
-		///     repeatedly call <see cref="RunLoop(RunState, bool)"/> with the wait parameter set to false.   By doing this
+		///     repeatedly call <see cref="RunLoop(RunState, bool)"/> with the wait parameter set to false. By doing this
 		///     the <see cref="RunLoop(RunState, bool)"/> method will only process any pending events, timers, idle handlers and
 		///     then return control immediately.
 		///   </para>
 		///   <para>
-		///     When <paramref name="errorHandler"/> is null the exception is rethrown, when it returns true the application is resumed and when false method exits gracefully.
+		///     RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exeptions will be rethrown.  
+		///     Otheriwse, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/> 
+		///     returns <see langword="true"/> the <see cref="RunLoop(RunState, bool)"/> will resume; otherwise 
+		///     this method will exit.
 		///   </para>
 		/// </remarks>
 		/// <param name="view">The <see cref="Toplevel"/> to run modally.</param>
-		/// <param name="errorHandler">Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
+		/// <param name="errorHandler">RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
 		public static void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
 		{
 			var resume = true;
@@ -1211,13 +1351,12 @@ namespace Terminal.Gui {
 #endif
 				resume = false;
 				var runToken = Begin (view);
+				// If ExitRunLoopAfterFirstIteration is true then the user must dispose of the runToken
+				// by using NotifyStopRunState event.
 				RunLoop (runToken);
-				if (!ExitRunLoopAfterFirstIteration)
+				if (!ExitRunLoopAfterFirstIteration) {
 					End (runToken);
-				else
-					// If ExitRunLoopAfterFirstIteration is true then the user must deal his disposing when it ends
-					// by using NotifyStopRunState event.
-					NotifyNewRunState?.Invoke (runToken);
+				}
 #if !DEBUG
 				}
 				catch (Exception error)
@@ -1310,8 +1449,9 @@ namespace Terminal.Gui {
 
 		static void OnNotifyStopRunState (Toplevel top)
 		{
-			if (ExitRunLoopAfterFirstIteration)
+			if (ExitRunLoopAfterFirstIteration) {
 				NotifyStopRunState?.Invoke (top);
+			}
 		}
 
 		/// <summary>

+ 1 - 1
Terminal.Gui/Core/ConsoleDriver.cs

@@ -1386,7 +1386,7 @@ namespace Terminal.Gui {
 			Colors.Error.Normal = MakeColor (Color.Red, Color.White);
 			Colors.Error.Focus = MakeColor (Color.Black, Color.BrightRed);
 			Colors.Error.HotNormal = MakeColor (Color.Black, Color.White);
-			Colors.Error.HotFocus = MakeColor (Color.BrightRed, Color.Gray);
+			Colors.Error.HotFocus = MakeColor (Color.White, Color.BrightRed);
 			Colors.Error.Disabled = MakeColor (Color.DarkGray, Color.White);
 		}
 	}

+ 2 - 2
Terminal.Gui/Core/MainLoop.cs

@@ -94,8 +94,8 @@ namespace Terminal.Gui {
 		public IMainLoopDriver Driver { get; }
 
 		/// <summary>
-		/// Invoked when a new timeout is added to be used on the case
-		/// if <see cref="Application.ExitRunLoopAfterFirstIteration"/> is true,
+		/// Invoked when a new timeout is added. To be used in the case
+		/// when <see cref="Application.ExitRunLoopAfterFirstIteration"/> is <see langword="true"/>.
 		/// </summary>
 		public event Action<long> TimeoutAdded;
 

+ 4 - 4
Terminal.Gui/Core/Toplevel.cs

@@ -44,7 +44,7 @@ namespace Terminal.Gui {
 		public bool Running { get; set; }
 
 		/// <summary>
-		/// Invoked when the Toplevel <see cref="Application.RunState"/> has begin loaded.
+		/// Invoked when the Toplevel <see cref="Application.RunState"/> has begun to be loaded.
 		/// A Loaded event handler is a good place to finalize initialization before calling 
 		/// <see cref="Application.RunLoop(Application.RunState, bool)"/>.
 		/// </summary>
@@ -77,13 +77,13 @@ namespace Terminal.Gui {
 
 		/// <summary>
 		/// Invoked when a child of the Toplevel <see cref="Application.RunState"/> is closed by  
-		/// <see cref="Application.End(View)"/>.
+		/// <see cref="Application.End(Application.RunState)"/>.
 		/// </summary>
 		public event Action<Toplevel> ChildClosed;
 
 		/// <summary>
 		/// Invoked when the last child of the Toplevel <see cref="Application.RunState"/> is closed from 
-		/// by <see cref="Application.End(View)"/>.
+		/// by <see cref="Application.End(Application.RunState)"/>.
 		/// </summary>
 		public event Action AllChildClosed;
 
@@ -94,7 +94,7 @@ namespace Terminal.Gui {
 		public event Action<ToplevelClosingEventArgs> Closing;
 
 		/// <summary>
-		/// Invoked when the Toplevel's <see cref="Application.RunState"/> is closed by <see cref="Application.End(View)"/>.
+		/// Invoked when the Toplevel's <see cref="Application.RunState"/> is closed by <see cref="Application.End(Application.RunState)"/>.
 		/// </summary>
 		public event Action<Toplevel> Closed;
 

+ 42 - 40
Terminal.Gui/Core/Trees/Branch.cs

@@ -5,23 +5,23 @@ using System.Linq;
 namespace Terminal.Gui.Trees {
 	class Branch<T> where T : class {
 		/// <summary>
-		/// True if the branch is expanded to reveal child branches
+		/// True if the branch is expanded to reveal child branches.
 		/// </summary>
 		public bool IsExpanded { get; set; }
 
 		/// <summary>
-		/// The users object that is being displayed by this branch of the tree
+		/// The users object that is being displayed by this branch of the tree.
 		/// </summary>
 		public T Model { get; private set; }
 
 		/// <summary>
-		/// The depth of the current branch.  Depth of 0 indicates root level branches
+		/// The depth of the current branch.  Depth of 0 indicates root level branches.
 		/// </summary>
 		public int Depth { get; private set; } = 0;
 
 		/// <summary>
 		/// The children of the current branch.  This is null until the first call to 
-		/// <see cref="FetchChildren"/> to avoid enumerating the entire underlying hierarchy
+		/// <see cref="FetchChildren"/> to avoid enumerating the entire underlying hierarchy.
 		/// </summary>
 		public Dictionary<T, Branch<T>> ChildBranches { get; set; }
 
@@ -34,12 +34,12 @@ namespace Terminal.Gui.Trees {
 
 		/// <summary>
 		/// Declares a new branch of <paramref name="tree"/> in which the users object 
-		/// <paramref name="model"/> is presented
+		/// <paramref name="model"/> is presented.
 		/// </summary>
-		/// <param name="tree">The UI control in which the branch resides</param>
+		/// <param name="tree">The UI control in which the branch resides.</param>
 		/// <param name="parentBranchIfAny">Pass null for root level branches, otherwise
-		/// pass the parent</param>
-		/// <param name="model">The user's object that should be displayed</param>
+		/// pass the parent.</param>
+		/// <param name="model">The user's object that should be displayed.</param>
 		public Branch (TreeView<T> tree, Branch<T> parentBranchIfAny, T model)
 		{
 			this.tree = tree;
@@ -53,7 +53,7 @@ namespace Terminal.Gui.Trees {
 
 
 		/// <summary>
-		/// Fetch the children of this branch. This method populates <see cref="ChildBranches"/>
+		/// Fetch the children of this branch. This method populates <see cref="ChildBranches"/>.
 		/// </summary>
 		public virtual void FetchChildren ()
 		{
@@ -80,7 +80,7 @@ namespace Terminal.Gui.Trees {
 		}
 
 		/// <summary>
-		/// Renders the current <see cref="Model"/> on the specified line <paramref name="y"/>
+		/// Renders the current <see cref="Model"/> on the specified line <paramref name="y"/>.
 		/// </summary>
 		/// <param name="driver"></param>
 		/// <param name="colorScheme"></param>
@@ -89,10 +89,10 @@ namespace Terminal.Gui.Trees {
 		public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, int availableWidth)
 		{
 			// true if the current line of the tree is the selected one and control has focus
-			bool isSelected = tree.IsSelected (Model);// && tree.HasFocus;
-			Attribute lineColor = isSelected ? (tree.HasFocus ? colorScheme.HotFocus : colorScheme.HotNormal) : colorScheme.Normal ;
+			bool isSelected = tree.IsSelected (Model);
 
-			driver.SetAttribute (lineColor);
+			Attribute textColor = isSelected ? (tree.HasFocus ? colorScheme.Focus : colorScheme.HotNormal) : colorScheme.Normal;
+			Attribute symbolColor = tree.Style.HighlightModelTextOnly ? colorScheme.Normal : textColor;
 
 			// Everything on line before the expansion run and branch text
 			Rune [] prefix = GetLinePrefix (driver).ToArray ();
@@ -104,7 +104,8 @@ namespace Terminal.Gui.Trees {
 			// if we have scrolled to the right then bits of the prefix will have dispeared off the screen
 			int toSkip = tree.ScrollOffsetHorizontal;
 
-			// Draw the line prefix (all paralell lanes or whitespace and an expand/collapse/leaf symbol)
+			driver.SetAttribute (symbolColor);
+			// Draw the line prefix (all parallel lanes or whitespace and an expand/collapse/leaf symbol)
 			foreach (Rune r in prefix) {
 
 				if (toSkip > 0) {
@@ -117,12 +118,16 @@ namespace Terminal.Gui.Trees {
 
 			// pick color for expanded symbol
 			if (tree.Style.ColorExpandSymbol || tree.Style.InvertExpandSymbolColors) {
-				Attribute color;
+				Attribute color = symbolColor;
 
 				if (tree.Style.ColorExpandSymbol) {
-					color = isSelected ? tree.ColorScheme.HotFocus : tree.ColorScheme.HotNormal;
+					if (isSelected) {
+						color = tree.Style.HighlightModelTextOnly ? colorScheme.HotNormal : (tree.HasFocus ? tree.ColorScheme.HotFocus : tree.ColorScheme.HotNormal);
+					} else {
+						color = tree.ColorScheme.HotNormal;
+					}
 				} else {
-					color = lineColor;
+					color = symbolColor;
 				}
 
 				if (tree.Style.InvertExpandSymbolColors) {
@@ -162,16 +167,14 @@ namespace Terminal.Gui.Trees {
 
 			// default behaviour is for model to use the color scheme
 			// of the tree view
-			var modelColor = lineColor;
+			var modelColor = textColor;
 
 			// if custom color delegate invoke it
-			if(tree.ColorGetter != null)
-			{
-				var modelScheme = tree.ColorGetter(Model);
+			if (tree.ColorGetter != null) {
+				var modelScheme = tree.ColorGetter (Model);
 
 				// if custom color scheme is defined for this Model
-				if(modelScheme != null)
-				{
+				if (modelScheme != null) {
 					// use it
 					modelColor = isSelected ? modelScheme.Focus : modelScheme.Normal;
 				}
@@ -179,24 +182,23 @@ namespace Terminal.Gui.Trees {
 
 			driver.SetAttribute (modelColor);
 			driver.AddStr (lineBody);
-			driver.SetAttribute (lineColor);
 
 			if (availableWidth > 0) {
+				driver.SetAttribute (symbolColor);
 				driver.AddStr (new string (' ', availableWidth));
 			}
-
 			driver.SetAttribute (colorScheme.Normal);
 		}
 
 		/// <summary>
 		/// Gets all characters to render prior to the current branches line.  This includes indentation
-		/// whitespace and any tree branches (if enabled)
+		/// whitespace and any tree branches (if enabled).
 		/// </summary>
 		/// <param name="driver"></param>
 		/// <returns></returns>
 		private IEnumerable<Rune> GetLinePrefix (ConsoleDriver driver)
 		{
-			// If not showing line branches or this is a root object
+			// If not showing line branches or this is a root object.
 			if (!tree.Style.ShowBranchLines) {
 				for (int i = 0; i < Depth; i++) {
 					yield return new Rune (' ');
@@ -224,7 +226,7 @@ namespace Terminal.Gui.Trees {
 		}
 
 		/// <summary>
-		/// Returns all parents starting with the immediate parent and ending at the root
+		/// Returns all parents starting with the immediate parent and ending at the root.
 		/// </summary>
 		/// <returns></returns>
 		private IEnumerable<Branch<T>> GetParentBranches ()
@@ -240,7 +242,7 @@ namespace Terminal.Gui.Trees {
 		/// <summary>
 		/// Returns an appropriate symbol for displaying next to the string representation of 
 		/// the <see cref="Model"/> object to indicate whether it <see cref="IsExpanded"/> or
-		/// not (or it is a leaf)
+		/// not (or it is a leaf).
 		/// </summary>
 		/// <param name="driver"></param>
 		/// <returns></returns>
@@ -261,7 +263,7 @@ namespace Terminal.Gui.Trees {
 
 		/// <summary>
 		/// Returns true if the current branch can be expanded according to 
-		/// the <see cref="TreeBuilder{T}"/> or cached children already fetched
+		/// the <see cref="TreeBuilder{T}"/> or cached children already fetched.
 		/// </summary>
 		/// <returns></returns>
 		public bool CanExpand ()
@@ -283,7 +285,7 @@ namespace Terminal.Gui.Trees {
 		}
 
 		/// <summary>
-		/// Expands the current branch if possible
+		/// Expands the current branch if possible.
 		/// </summary>
 		public void Expand ()
 		{
@@ -297,7 +299,7 @@ namespace Terminal.Gui.Trees {
 		}
 
 		/// <summary>
-		/// Marks the branch as collapsed (<see cref="IsExpanded"/> false)
+		/// Marks the branch as collapsed (<see cref="IsExpanded"/> false).
 		/// </summary>
 		public void Collapse ()
 		{
@@ -305,10 +307,10 @@ namespace Terminal.Gui.Trees {
 		}
 
 		/// <summary>
-		/// Refreshes cached knowledge in this branch e.g. what children an object has
+		/// Refreshes cached knowledge in this branch e.g. what children an object has.
 		/// </summary>
 		/// <param name="startAtTop">True to also refresh all <see cref="Parent"/> 
-		/// branches (starting with the root)</param>
+		/// branches (starting with the root).</param>
 		public void Refresh (bool startAtTop)
 		{
 			// if we must go up and refresh from the top down
@@ -351,7 +353,7 @@ namespace Terminal.Gui.Trees {
 		}
 
 		/// <summary>
-		/// Calls <see cref="Refresh(bool)"/> on the current branch and all expanded children
+		/// Calls <see cref="Refresh(bool)"/> on the current branch and all expanded children.
 		/// </summary>
 		internal void Rebuild ()
 		{
@@ -375,7 +377,7 @@ namespace Terminal.Gui.Trees {
 
 		/// <summary>
 		/// Returns true if this branch has parents and it is the last node of it's parents 
-		/// branches (or last root of the tree)
+		/// branches (or last root of the tree).
 		/// </summary>
 		/// <returns></returns>
 		private bool IsLast ()
@@ -389,7 +391,7 @@ namespace Terminal.Gui.Trees {
 
 		/// <summary>
 		/// Returns true if the given x offset on the branch line is the +/- symbol.  Returns 
-		/// false if not showing expansion symbols or leaf node etc
+		/// false if not showing expansion symbols or leaf node etc.
 		/// </summary>
 		/// <param name="driver"></param>
 		/// <param name="x"></param>
@@ -415,10 +417,10 @@ namespace Terminal.Gui.Trees {
 		}
 
 		/// <summary>
-		/// Expands the current branch and all children branches
+		/// Expands the current branch and all children branches.
 		/// </summary>
 		internal void ExpandAll ()
-			{
+		{
 			Expand ();
 
 			if (ChildBranches != null) {
@@ -430,7 +432,7 @@ namespace Terminal.Gui.Trees {
 
 		/// <summary>
 		/// Collapses the current branch and all children branches (even though those branches are 
-		/// no longer visible they retain collapse/expansion state)
+		/// no longer visible they retain collapse/expansion state).
 		/// </summary>
 		internal void CollapseAll ()
 		{

+ 15 - 10
Terminal.Gui/Core/Trees/TreeStyle.cs

@@ -2,46 +2,51 @@
 
 namespace Terminal.Gui.Trees {
 	/// <summary>
-	/// Defines rendering options that affect how the tree is displayed
+	/// Defines rendering options that affect how the tree is displayed.
 	/// </summary>
 	public class TreeStyle {
 
 		/// <summary>
-		/// True to render vertical lines under expanded nodes to show which node belongs to which 
-		/// parent.  False to use only whitespace
+		/// <see langword="true"/> to render vertical lines under expanded nodes to show which node belongs to which 
+		/// parent. <see langword="false"/> to use only whitespace.
 		/// </summary>
 		/// <value></value>
 		public bool ShowBranchLines { get; set; } = true;
 
 		/// <summary>
-		/// Symbol to use for branch nodes that can be expanded to indicate this to the user.  
-		/// Defaults to '+'. Set to null to hide
+		/// Symbol to use for branch nodes that can be expanded to indicate this to the user. 
+		/// Defaults to '+'. Set to null to hide.
 		/// </summary>
 		public Rune? ExpandableSymbol { get; set; } = '+';
 
 		/// <summary>
 		/// Symbol to use for branch nodes that can be collapsed (are currently expanded).
-		/// Defaults to '-'.  Set to null to hide
+		/// Defaults to '-'. Set to null to hide.
 		/// </summary>
 		public Rune? CollapseableSymbol { get; set; } = '-';
 
 		/// <summary>
-		/// Set to true to highlight expand/collapse symbols in hot key color
+		/// Set to <see langword="true"/> to highlight expand/collapse symbols in hot key color.
 		/// </summary>
 		public bool ColorExpandSymbol { get; set; }
 
 		/// <summary>
-		/// Invert console colours used to render the expand symbol
+		/// Invert console colours used to render the expand symbol.
 		/// </summary>
 		public bool InvertExpandSymbolColors { get; set; }
 
 		/// <summary>
-		/// True to leave the last row of the control free for overwritting (e.g. by a scrollbar)
-		/// When True scrolling will be triggered on the second last row of the control rather than
+		/// <see langword="true"/> to leave the last row of the control free for overwritting (e.g. by a scrollbar)
+		/// When <see langword="true"/> scrolling will be triggered on the second last row of the control rather than.
 		/// the last.
 		/// </summary>
 		/// <value></value>
 		public bool LeaveLastRow { get; set; }
 
+		/// <summary>
+		/// Set to <see langword="true"/> to cause the selected item to be rendered with only the <see cref="Branch{T}.Model"/> text
+		/// to be highlighted. If <see langword="false"/> (the default), the entire row will be highlighted.
+		/// </summary>
+		public bool HighlightModelTextOnly { get; set; } = false;
 	}
 }

+ 1 - 1
Terminal.Gui/Core/View.cs

@@ -1496,7 +1496,7 @@ namespace Terminal.Gui {
 			if (Border != null) {
 				Border.DrawContent (this);
 			} else if (ustring.IsNullOrEmpty (TextFormatter.Text) &&
-				(GetType ().IsPublic || GetType ().IsNestedPublic) && !IsOverridden (this, "Redraw") &&
+				(GetType ().IsNestedPublic) && !IsOverridden (this, "Redraw") &&
 				(!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded)) {
 
 				Clear ();

+ 7 - 1
Terminal.Gui/Views/GraphView.cs

@@ -240,7 +240,13 @@ namespace Terminal.Gui {
 				);
 		}
 
-
+		/// <inheritdoc/>
+		/// <remarks>Also ensures that cursor is invisible after entering the <see cref="GraphView"/>.</remarks>
+		public override bool OnEnter (View view)
+		{
+			Driver.SetCursorVisibility (CursorVisibility.Invisible);
+			return base.OnEnter (view);
+		}
 
 		/// <inheritdoc/>
 		public override bool ProcessKey (KeyEvent keyEvent)

+ 3 - 24
Terminal.Gui/Views/TabView.cs

@@ -466,31 +466,10 @@ namespace Terminal.Gui {
 				Width = Dim.Fill ();
 			}
 
-			/// <summary>
-			/// Positions the cursor at the start of the currently selected tab
-			/// </summary>
-			public override void PositionCursor ()
+			public override bool OnEnter (View view)
 			{
-				base.PositionCursor ();
-
-				var selected = host.CalculateViewport (Bounds).FirstOrDefault (t => Equals (host.SelectedTab, t.Tab));
-
-				if (selected == null) {
-					return;
-				}
-
-				int y;
-
-				if (host.Style.TabsOnBottom) {
-					y = 1;
-				} else {
-					y = host.Style.ShowTopLine ? 1 : 0;
-				}
-
-				Move (selected.X, y);
-
-
-
+				Driver.SetCursorVisibility (CursorVisibility.Invisible);
+				return base.OnEnter (view);
 			}
 
 			public override void Redraw (Rect bounds)

+ 236 - 192
Terminal.Gui/Views/TextView.cs

@@ -1,28 +1,4 @@
-//
 // TextView.cs: multi-line text editing
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-// 
-// TODO:
-// In ReadOnly mode backspace/space behave like pageup/pagedown
-// Attributed text on spans
-// Replace insertion with Insert method
-// String accumulation (Control-k, control-k is not preserving the last new line, see StringToRunes
-// Alt-D, Alt-Backspace
-// API to set the cursor position
-// API to scroll to a particular place
-// keybindings to go to top/bottom
-// public API to insert, remove ranges
-// Add word forward/word backwards commands
-// Save buffer API
-// Mouse
-//
-// Desirable:
-//   Move all the text manipulation into the TextModel
-
-
 using System;
 using System.Collections.Generic;
 using System.Globalization;
@@ -33,6 +9,7 @@ using System.Text;
 using System.Threading;
 using NStack;
 using Terminal.Gui.Resources;
+using static Terminal.Gui.Graphs.PathAnnotation;
 using Rune = System.Rune;
 
 namespace Terminal.Gui {
@@ -742,6 +719,7 @@ namespace Terminal.Gui {
 			historyTextItems.Clear ();
 			idxHistoryText = -1;
 			originalText = text;
+			OnChangeText (null);
 		}
 
 		public bool IsDirty (ustring text)
@@ -1037,120 +1015,119 @@ namespace Terminal.Gui {
 	}
 
 	/// <summary>
-	///   Multi-line text editing <see cref="View"/>
+	///  Multi-line text editing <see cref="View"/>.
 	/// </summary>
 	/// <remarks>
-	///   <para>
-	///     <see cref="TextView"/> provides a multi-line text editor. Users interact
-	///     with it with the standard Emacs commands for movement or the arrow
-	///     keys. 
-	///   </para> 
-	///   <list type="table"> 
-	///     <listheader>
-	///       <term>Shortcut</term>
-	///       <description>Action performed</description>
-	///     </listheader>
-	///     <item>
-	///        <term>Left cursor, Control-b</term>
-	///        <description>
-	///          Moves the editing point left.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Right cursor, Control-f</term>
-	///        <description>
-	///          Moves the editing point right.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Alt-b</term>
-	///        <description>
-	///          Moves one word back.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Alt-f</term>
-	///        <description>
-	///          Moves one word forward.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Up cursor, Control-p</term>
-	///        <description>
-	///          Moves the editing point one line up.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Down cursor, Control-n</term>
-	///        <description>
-	///          Moves the editing point one line down
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Home key, Control-a</term>
-	///        <description>
-	///          Moves the cursor to the beginning of the line.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>End key, Control-e</term>
-	///        <description>
-	///          Moves the cursor to the end of the line.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Control-Home</term>
-	///        <description>
-	///          Scrolls to the first line and moves the cursor there.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Control-End</term>
-	///        <description>
-	///          Scrolls to the last line and moves the cursor there.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Delete, Control-d</term>
-	///        <description>
-	///          Deletes the character in front of the cursor.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Backspace</term>
-	///        <description>
-	///          Deletes the character behind the cursor.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Control-k</term>
-	///        <description>
-	///          Deletes the text until the end of the line and replaces the kill buffer
-	///          with the deleted text.   You can paste this text in a different place by
-	///          using Control-y.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Control-y</term>
-	///        <description>
-	///           Pastes the content of the kill ring into the current position.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Alt-d</term>
-	///        <description>
-	///           Deletes the word above the cursor and adds it to the kill ring.  You 
-	///           can paste the contents of the kill ring with Control-y.
-	///        </description>
-	///     </item>
-	///     <item>
-	///        <term>Control-q</term>
-	///        <description>
-	///          Quotes the next input character, to prevent the normal processing of
-	///          key handling to take place.
-	///        </description>
-	///     </item>
-	///   </list>
+	///  <para>
+	///   <see cref="TextView"/> provides a multi-line text editor. Users interact
+	///   with it with the standard Windows, Mac, and Linux (Emacs) commands. 
+	///  </para> 
+	///  <list type="table"> 
+	///   <listheader>
+	///    <term>Shortcut</term>
+	///    <description>Action performed</description>
+	///   </listheader>
+	///   <item>
+	///    <term>Left cursor, Control-b</term>
+	///    <description>
+	///     Moves the editing point left.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Right cursor, Control-f</term>
+	///    <description>
+	///     Moves the editing point right.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Alt-b</term>
+	///    <description>
+	///     Moves one word back.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Alt-f</term>
+	///    <description>
+	///     Moves one word forward.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Up cursor, Control-p</term>
+	///    <description>
+	///     Moves the editing point one line up.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Down cursor, Control-n</term>
+	///    <description>
+	///     Moves the editing point one line down
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Home key, Control-a</term>
+	///    <description>
+	///     Moves the cursor to the beginning of the line.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>End key, Control-e</term>
+	///    <description>
+	///     Moves the cursor to the end of the line.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Control-Home</term>
+	///    <description>
+	///     Scrolls to the first line and moves the cursor there.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Control-End</term>
+	///    <description>
+	///     Scrolls to the last line and moves the cursor there.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Delete, Control-d</term>
+	///    <description>
+	///     Deletes the character in front of the cursor.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Backspace</term>
+	///    <description>
+	///     Deletes the character behind the cursor.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Control-k</term>
+	///    <description>
+	///     Deletes the text until the end of the line and replaces the kill buffer
+	///     with the deleted text. You can paste this text in a different place by
+	///     using Control-y.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Control-y</term>
+	///    <description>
+	///      Pastes the content of the kill ring into the current position.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Alt-d</term>
+	///    <description>
+	///      Deletes the word above the cursor and adds it to the kill ring. You 
+	///      can paste the contents of the kill ring with Control-y.
+	///    </description>
+	///   </item>
+	///   <item>
+	///    <term>Control-q</term>
+	///    <description>
+	///     Quotes the next input character, to prevent the normal processing of
+	///     key handling to take place.
+	///    </description>
+	///   </item>
+	///  </list>
 	/// </remarks>
 	public class TextView : View {
 		TextModel model = new TextModel ();
@@ -1172,10 +1149,24 @@ namespace Terminal.Gui {
 		CultureInfo currentCulture;
 
 		/// <summary>
-		/// Raised when the <see cref="Text"/> of the <see cref="TextView"/> changes.
+		/// Raised when the <see cref="Text"/> property of the <see cref="TextView"/> changes.
 		/// </summary>
+		/// <remarks>
+		/// The <see cref="Text"/> property of <see cref="TextView"/> only changes when it is explicitly
+		/// set, not as the user types. To be notified as the user changes the contents of the TextView
+		/// see <see cref="IsDirty"/>.
+		/// </remarks>
 		public event Action TextChanged;
 
+		/// <summary>
+		///  Raised when the contents of the <see cref="TextView"/> are changed. 
+		/// </summary>
+		/// <remarks>
+		/// Unlike the <see cref="TextChanged"/> event, this event is raised whenever the user types or
+		/// otherwise changes the contents of the <see cref="TextView"/>.
+		/// </remarks>
+		public Action<ContentsChangedEventArgs> ContentsChanged;
+
 		/// <summary>
 		/// Invoked with the unwrapped <see cref="CursorPosition"/>.
 		/// </summary>
@@ -1183,22 +1174,12 @@ namespace Terminal.Gui {
 
 		/// <summary>
 		/// Provides autocomplete context menu based on suggestions at the current cursor
-		/// position.  Populate <see cref="Autocomplete.AllSuggestions"/> to enable this feature
+		/// position. Populate <see cref="Autocomplete.AllSuggestions"/> to enable this feature
 		/// </summary>
 		public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete ();
 
-#if false
-		/// <summary>
-		///   Changed event, raised when the text has clicked.
-		/// </summary>
-		/// <remarks>
-		///   Client code can hook up to this event, it is
-		///   raised when the text in the entry changes.
-		/// </remarks>
-		public Action Changed;
-#endif
 		/// <summary>
-		///   Initializes a <see cref="TextView"/> on the specified area, with absolute position and size.
+		///  Initializes a <see cref="TextView"/> on the specified area, with absolute position and size.
 		/// </summary>
 		/// <remarks>
 		/// </remarks>
@@ -1208,8 +1189,8 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		///   Initializes a <see cref="TextView"/> on the specified area, 
-		///   with dimensions controlled with the X, Y, Width and Height properties.
+		///  Initializes a <see cref="TextView"/> on the specified area, 
+		///  with dimensions controlled with the X, Y, Width and Height properties.
 		/// </summary>
 		public TextView () : base ()
 		{
@@ -1404,48 +1385,56 @@ namespace Terminal.Gui {
 
 		private void Model_LinesLoaded ()
 		{
-			historyText.Clear (Text);
+			// This call is not needed. Model_LinesLoaded gets invoked when
+			// model.LoadString (value) is called. LoadString is called from one place
+			// (Text.set) and historyText.Clear() is called immediately after.
+			// If this call happens, HistoryText_ChangeText will get called multiple times
+			// when Text is set, which is wrong.
+			//historyText.Clear (Text);
 		}
 
 		private void HistoryText_ChangeText (HistoryText.HistoryTextItem obj)
 		{
 			SetWrapModel ();
 
-			var startLine = obj.CursorPosition.Y;
+			if (obj != null) {
+				var startLine = obj.CursorPosition.Y;
 
-			if (obj.RemovedOnAdded != null) {
-				int offset;
-				if (obj.IsUndoing) {
-					offset = Math.Max (obj.RemovedOnAdded.Lines.Count - obj.Lines.Count, 1);
-				} else {
-					offset = obj.RemovedOnAdded.Lines.Count - 1;
-				}
-				for (int i = 0; i < offset; i++) {
-					if (Lines > obj.RemovedOnAdded.CursorPosition.Y) {
-						model.RemoveLine (obj.RemovedOnAdded.CursorPosition.Y);
+				if (obj.RemovedOnAdded != null) {
+					int offset;
+					if (obj.IsUndoing) {
+						offset = Math.Max (obj.RemovedOnAdded.Lines.Count - obj.Lines.Count, 1);
 					} else {
-						break;
+						offset = obj.RemovedOnAdded.Lines.Count - 1;
+					}
+					for (int i = 0; i < offset; i++) {
+						if (Lines > obj.RemovedOnAdded.CursorPosition.Y) {
+							model.RemoveLine (obj.RemovedOnAdded.CursorPosition.Y);
+						} else {
+							break;
+						}
 					}
 				}
-			}
 
-			for (int i = 0; i < obj.Lines.Count; i++) {
-				if (i == 0) {
-					model.ReplaceLine (startLine, obj.Lines [i]);
-				} else if ((obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Removed)
-						|| !obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Added) {
-					model.AddLine (startLine, obj.Lines [i]);
-				} else if (Lines > obj.CursorPosition.Y + 1) {
-					model.RemoveLine (obj.CursorPosition.Y + 1);
+				for (int i = 0; i < obj.Lines.Count; i++) {
+					if (i == 0) {
+						model.ReplaceLine (startLine, obj.Lines [i]);
+					} else if ((obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Removed)
+							|| !obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Added) {
+						model.AddLine (startLine, obj.Lines [i]);
+					} else if (Lines > obj.CursorPosition.Y + 1) {
+						model.RemoveLine (obj.CursorPosition.Y + 1);
+					}
+					startLine++;
 				}
-				startLine++;
-			}
 
-			CursorPosition = obj.FinalCursorPosition;
+				CursorPosition = obj.FinalCursorPosition;
+			}
 
 			UpdateWrapModel ();
-
+			
 			Adjust ();
+			OnContentsChanged ();
 		}
 
 		void TextView_Initialized (object sender, EventArgs e)
@@ -1454,6 +1443,7 @@ namespace Terminal.Gui {
 
 			Application.Top.AlternateForwardKeyChanged += Top_AlternateForwardKeyChanged;
 			Application.Top.AlternateBackwardKeyChanged += Top_AlternateBackwardKeyChanged;
+			OnContentsChanged ();
 		}
 
 		void Top_AlternateBackwardKeyChanged (Key obj)
@@ -1480,9 +1470,11 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		///   Sets or gets the text in the <see cref="TextView"/>.
+		///  Sets or gets the text in the <see cref="TextView"/>.
 		/// </summary>
 		/// <remarks>
+		/// The <see cref="TextChanged"/> event is fired whenever this property is set. Note, however,
+		/// that Text is not set by <see cref="TextView"/> as the user types.
 		/// </remarks>
 		public override ustring Text {
 			get {
@@ -1559,12 +1551,12 @@ namespace Terminal.Gui {
 		public int Maxlength => model.GetMaxVisibleLine (topRow, topRow + Frame.Height, TabWidth);
 
 		/// <summary>
-		/// Gets the  number of lines.
+		/// Gets the number of lines.
 		/// </summary>
 		public int Lines => model.Count;
 
 		/// <summary>
-		///    Sets or gets the current cursor position.
+		///  Sets or gets the current cursor position.
 		/// </summary>
 		public Point CursorPosition {
 			get => new Point (currentColumn, currentRow);
@@ -1828,7 +1820,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Loads the contents of the file into the  <see cref="TextView"/>.
+		/// Loads the contents of the file into the <see cref="TextView"/>.
 		/// </summary>
 		/// <returns><c>true</c>, if file was loaded, <c>false</c> otherwise.</returns>
 		/// <param name="path">Path to the file to load.</param>
@@ -1845,12 +1837,13 @@ namespace Terminal.Gui {
 				UpdateWrapModel ();
 				SetNeedsDisplay ();
 				Adjust ();
+				OnContentsChanged ();
 			}
 			return res;
 		}
 
 		/// <summary>
-		/// Loads the contents of the stream into the  <see cref="TextView"/>.
+		/// Loads the contents of the stream into the <see cref="TextView"/>.
 		/// </summary>
 		/// <returns><c>true</c>, if stream was loaded, <c>false</c> otherwise.</returns>
 		/// <param name="stream">Stream to load the contents from.</param>
@@ -1859,10 +1852,11 @@ namespace Terminal.Gui {
 			model.LoadStream (stream);
 			ResetPosition ();
 			SetNeedsDisplay ();
+			OnContentsChanged ();
 		}
 
 		/// <summary>
-		/// Closes the contents of the stream into the  <see cref="TextView"/>.
+		/// Closes the contents of the stream into the <see cref="TextView"/>.
 		/// </summary>
 		/// <returns><c>true</c>, if stream was closed, <c>false</c> otherwise.</returns>
 		public bool CloseFile ()
@@ -1874,7 +1868,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		///    Gets the current cursor row.
+		///  Gets the current cursor row.
 		/// </summary>
 		public int CurrentRow => currentRow;
 
@@ -1885,7 +1879,7 @@ namespace Terminal.Gui {
 		public int CurrentColumn => currentColumn;
 
 		/// <summary>
-		///   Positions the cursor on the current row and column
+		///  Positions the cursor on the current row and column
 		/// </summary>
 		public override void PositionCursor ()
 		{
@@ -1936,7 +1930,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Sets the driver to the default color for the control where no text is being rendered.  Defaults to <see cref="ColorScheme.Normal"/>.
+		/// Sets the driver to the default color for the control where no text is being rendered. Defaults to <see cref="ColorScheme.Normal"/>.
 		/// </summary>
 		protected virtual void SetNormalColor ()
 		{
@@ -1945,7 +1939,7 @@ namespace Terminal.Gui {
 
 		/// <summary>
 		/// Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idx"/> of the
-		/// current <paramref name="line"/>.  Override to provide custom coloring by calling <see cref="ConsoleDriver.SetAttribute(Attribute)"/>
+		/// current <paramref name="line"/>. Override to provide custom coloring by calling <see cref="ConsoleDriver.SetAttribute(Attribute)"/>
 		/// Defaults to <see cref="ColorScheme.Normal"/>.
 		/// </summary>
 		/// <param name="line"></param>
@@ -1957,7 +1951,7 @@ namespace Terminal.Gui {
 
 		/// <summary>
 		/// Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idx"/> of the
-		/// current <paramref name="line"/>.  Override to provide custom coloring by calling <see cref="ConsoleDriver.SetAttribute(Attribute)"/>
+		/// current <paramref name="line"/>. Override to provide custom coloring by calling <see cref="ConsoleDriver.SetAttribute(Attribute)"/>
 		/// Defaults to <see cref="ColorScheme.Focus"/>.
 		/// </summary>
 		/// <param name="line"></param>
@@ -1969,7 +1963,7 @@ namespace Terminal.Gui {
 
 		/// <summary>
 		/// Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idx"/> of the
-		/// current <paramref name="line"/>.  Override to provide custom coloring by calling <see cref="ConsoleDriver.SetAttribute(Attribute)"/>
+		/// current <paramref name="line"/>. Override to provide custom coloring by calling <see cref="ConsoleDriver.SetAttribute(Attribute)"/>
 		/// Defaults to <see cref="ColorScheme.Focus"/>.
 		/// </summary>
 		/// <param name="line"></param>
@@ -1987,7 +1981,7 @@ namespace Terminal.Gui {
 
 		/// <summary>
 		/// Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idx"/> of the
-		/// current <paramref name="line"/>.  Override to provide custom coloring by calling <see cref="ConsoleDriver.SetAttribute(Attribute)"/>
+		/// current <paramref name="line"/>. Override to provide custom coloring by calling <see cref="ConsoleDriver.SetAttribute(Attribute)"/>
 		/// Defaults to <see cref="ColorScheme.HotFocus"/>.
 		/// </summary>
 		/// <param name="line"></param>
@@ -2000,7 +1994,7 @@ namespace Terminal.Gui {
 		bool isReadOnly = false;
 
 		/// <summary>
-		/// Gets or sets whether the  <see cref="TextView"/> is in read-only mode or not
+		/// Gets or sets whether the <see cref="TextView"/> is in read-only mode or not
 		/// </summary>
 		/// <value>Boolean value(Default false)</value>
 		public bool ReadOnly {
@@ -2504,6 +2498,12 @@ namespace Terminal.Gui {
 
 				InsertText (new KeyEvent () { Key = key });
 			}
+
+			if (NeedDisplay.IsEmpty) {
+				PositionCursor ();
+			} else {
+				Adjust ();
+			}
 		}
 
 		void Insert (Rune rune)
@@ -2521,6 +2521,7 @@ namespace Terminal.Gui {
 			if (!wrapNeeded) {
 				SetNeedsDisplay (new Rect (0, prow, Math.Max (Frame.Width, 0), Math.Max (prow + 1, 0)));
 			}
+
 		}
 
 		ustring StringFromRunes (List<Rune> runes)
@@ -2584,6 +2585,8 @@ namespace Terminal.Gui {
 
 				UpdateWrapModel ();
 
+				OnContentsChanged ();
+
 				return;
 			}
 
@@ -2690,6 +2693,42 @@ namespace Terminal.Gui {
 			OnUnwrappedCursorPosition ();
 		}
 
+		/// <summary>
+		/// Event arguments for events for when the contents of the TextView change. E.g. the <see cref="ContentsChanged"/> event.
+		/// </summary>
+		public class ContentsChangedEventArgs : EventArgs {
+			/// <summary>
+			/// Creates a new <see cref="ContentsChanged"/> instance.
+			/// </summary>
+			/// <param name="currentRow">Contains the row where the change occurred.</param>
+			/// <param name="currentColumn">Contains the column where the change occured.</param>
+			public ContentsChangedEventArgs (int currentRow, int currentColumn)
+			{
+				Row = currentRow;
+				Col = currentColumn;
+			}
+
+			/// <summary>
+			/// 
+			/// Contains the row where the change occurred.
+			/// </summary>
+			public int Row { get; private set; }
+
+			/// <summary>
+			/// Contains the column where the change occurred.
+			/// </summary>
+			public int Col { get; private set; }
+		}
+
+		/// <summary>
+		/// Called when the contents of the TextView change. E.g. when the user types text or deletes text. Raises
+		/// the <see cref="ContentsChanged"/> event.
+		/// </summary>
+		public virtual void OnContentsChanged ()
+		{
+			ContentsChanged?.Invoke (new ContentsChangedEventArgs (CurrentRow, CurrentColumn));
+		}
+
 		(int width, int height) OffSetBackground ()
 		{
 			int w = 0;
@@ -2708,7 +2747,7 @@ namespace Terminal.Gui {
 		/// will scroll the <see cref="TextView"/> to display the specified column at the left if <paramref name="isRow"/> is false.
 		/// </summary>
 		/// <param name="idx">Row that should be displayed at the top or Column that should be displayed at the left,
-		///  if the value is negative it will be reset to zero</param>
+		/// if the value is negative it will be reset to zero</param>
 		/// <param name="isRow">If true (default) the <paramref name="idx"/> is a row, column otherwise.</param>
 		public void ScrollTo (int idx, bool isRow = true)
 		{
@@ -3178,6 +3217,7 @@ namespace Terminal.Gui {
 			UpdateWrapModel ();
 
 			DoNeededAction ();
+			OnContentsChanged ();
 			return true;
 		}
 
@@ -3674,6 +3714,7 @@ namespace Terminal.Gui {
 				HistoryText.LineStatus.Replaced);
 
 			UpdateWrapModel ();
+			OnContentsChanged ();
 
 			return true;
 		}
@@ -3883,6 +3924,7 @@ namespace Terminal.Gui {
 			UpdateWrapModel ();
 			selecting = false;
 			DoNeededAction ();
+			OnContentsChanged ();
 		}
 
 		/// <summary>
@@ -3913,6 +3955,7 @@ namespace Terminal.Gui {
 
 				historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
 					HistoryText.LineStatus.Replaced);
+				OnContentsChanged ();
 			} else {
 				if (selecting) {
 					ClearRegion ();
@@ -4423,6 +4466,7 @@ namespace Terminal.Gui {
 		}
 	}
 
+
 	/// <summary>
 	/// Renders an overlay on another view at a given point that allows selecting
 	/// from a range of 'autocomplete' options.

+ 8 - 4
Terminal.Gui/Views/TreeView.cs

@@ -689,8 +689,10 @@ namespace Terminal.Gui {
 		/// </summary>
 		public void ScrollDown ()
 		{
-			ScrollOffsetVertical++;
-			SetNeedsDisplay ();
+			if (ScrollOffsetVertical <= ContentHeight - 2) {
+				ScrollOffsetVertical++;
+				SetNeedsDisplay ();
+			}
 		}
 
 		/// <summary>
@@ -698,8 +700,10 @@ namespace Terminal.Gui {
 		/// </summary>
 		public void ScrollUp ()
 		{
-			ScrollOffsetVertical--;
-			SetNeedsDisplay ();
+			if (scrollOffsetVertical > 0) {
+				ScrollOffsetVertical--;
+				SetNeedsDisplay ();
+			}
 		}
 
 		/// <summary>

+ 1 - 1
Terminal.Gui/Windows/FileDialog.cs

@@ -41,7 +41,7 @@ namespace Terminal.Gui {
 			if (allowedFileTypes == null)
 				return true;
 			foreach (var ft in allowedFileTypes)
-				if (fsi.Name.EndsWith (ft) || ft == ".*")
+				if (fsi.Name.EndsWith (ft, StringComparison.InvariantCultureIgnoreCase) || ft == ".*")
 					return true;
 			return false;
 		}

+ 1 - 1
Terminal.Gui/Windows/Wizard.cs

@@ -159,7 +159,7 @@ namespace Terminal.Gui {
 			public event Action<TitleEventArgs> TitleChanged;
 
 			// The contentView works like the ContentView in FrameView.
-			private View contentView = new View ();
+			private View contentView = new View () { Data = "WizardContentView" };
 
 			/// <summary>
 			/// Sets or gets help text for the <see cref="WizardStep"/>.If <see cref="WizardStep.HelpText"/> is empty

+ 8 - 0
UICatalog/Properties/launchSettings.json

@@ -48,6 +48,14 @@
     "WSL": {
       "commandName": "WSL2",
       "distributionName": ""
+    },
+    "All Views Tester": {
+      "commandName": "Project",
+      "commandLineArgs": "\"All Views Tester\""
+    },
+    "Windows & FrameViews": {
+      "commandName": "Project",
+      "commandLineArgs": "\"Windows & FrameViews\""
     }
   }
 }

+ 8 - 14
UICatalog/Scenario.cs

@@ -48,12 +48,7 @@ namespace UICatalog {
 		private bool _disposedValue;
 
 		/// <summary>
-		/// The Top level for the <see cref="Scenario"/>. This should be set to <see cref="Terminal.Gui.Application.Top"/> in most cases.
-		/// </summary>
-		public Toplevel Top { get; set; }
-
-		/// <summary>
-		/// The Window for the <see cref="Scenario"/>. This should be set within the <see cref="Terminal.Gui.Application.Top"/> in most cases.
+		/// The Window for the <see cref="Scenario"/>. This should be set to <see cref="Terminal.Gui.Application.Top"/> in most cases.
 		/// </summary>
 		public Window Win { get; set; }
 
@@ -63,22 +58,21 @@ namespace UICatalog {
 		/// the Scenario picker UI.
 		/// Override <see cref="Init"/> to provide any <see cref="Terminal.Gui.Toplevel"/> behavior needed.
 		/// </summary>
-		/// <param name="top">The Toplevel created by the UI Catalog host.</param>
 		/// <param name="colorScheme">The colorscheme to use.</param>
 		/// <remarks>
 		/// <para>
-		/// The base implementation calls <see cref="Application.Init"/>, sets <see cref="Top"/> to the passed in <see cref="Toplevel"/>, creates a <see cref="Window"/> for <see cref="Win"/> and adds it to <see cref="Top"/>.
+		/// The base implementation calls <see cref="Application.Init"/> and creates a <see cref="Window"/> for <see cref="Win"/> 
+		/// and adds it to <see cref="Application.Top"/>.
 		/// </para>
 		/// <para>
-		/// Overrides that do not call the base.<see cref="Run"/>, must call <see cref="Application.Init"/> before creating any views or calling other Terminal.Gui APIs.
+		/// Overrides that do not call the base.<see cref="Run"/>, must call <see cref="Application.Init"/> 
+		/// before creating any views or calling other Terminal.Gui APIs.
 		/// </para>
 		/// </remarks>
-		public virtual void Init (Toplevel top, ColorScheme colorScheme)
+		public virtual void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
 
-			Top = top != null ? top : Application.Top;
-
 			Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") {
 				X = 0,
 				Y = 0,
@@ -86,7 +80,7 @@ namespace UICatalog {
 				Height = Dim.Fill (),
 				ColorScheme = colorScheme,
 			};
-			Top.Add (Win);
+			Application.Top.Add (Win);
 		}
 
 		/// <summary>
@@ -201,7 +195,7 @@ namespace UICatalog {
 		public virtual void Run ()
 		{
 			// Must explicit call Application.Shutdown method to shutdown.
-			Application.Run (Top);
+			Application.Run (Application.Top);
 		}
 
 		/// <summary>

+ 10 - 24
UICatalog/Scenarios/AllViewsTester.cs

@@ -14,7 +14,7 @@ namespace UICatalog.Scenarios {
 	[ScenarioCategory ("Tests")]
 	[ScenarioCategory ("Top Level Windows")]
 	public class AllViewsTester : Scenario {
-		Window _leftPane;
+		FrameView _leftPane;
 		ListView _classListView;
 		FrameView _hostPane;
 
@@ -40,42 +40,33 @@ namespace UICatalog.Scenarios {
 		TextField _hText;
 		int _hVal = 0;
 
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-
-			Top = top != null ? top : Application.Top;
-
-			//Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") {
-			//	X = 0,
-			//	Y = 0,
-			//	Width = Dim.Fill (),
-			//	Height = Dim.Fill ()
-			//};
-			//Top.Add (Win);
+			// Don't create a sub-win; just use Applicatiion.Top
 		}
-
+		
 		public override void Setup ()
 		{
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 				new StatusItem(Key.F2, "~F2~ Toggle Frame Ruler", () => {
 					ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler;
-					Top.SetNeedsDisplay ();
+					Application.Top.SetNeedsDisplay ();
 				}),
 				new StatusItem(Key.F3, "~F3~ Toggle Frame Padding", () => {
 					ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FramePadding;
-					Top.SetNeedsDisplay ();
+					Application.Top.SetNeedsDisplay ();
 				}),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			_viewClasses = GetAllViewClassesCollection ()
 				.OrderBy (t => t.Name)
 				.Select (t => new KeyValuePair<string, Type> (t.Name, t))
 				.ToDictionary (t => t.Key, t => t.Value);
 
-			_leftPane = new Window ("Classes") {
+			_leftPane = new FrameView ("Classes") {
 				X = 0,
 				Y = 0,
 				Width = 15,
@@ -241,9 +232,9 @@ namespace UICatalog.Scenarios {
 				ColorScheme = Colors.Dialog,
 			};
 
-			Top.Add (_leftPane, _settingsPane, _hostPane);
+			Application.Top.Add (_leftPane, _settingsPane, _hostPane);
 
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			_curView = CreateClass (_viewClasses.First ().Value);
 		}
@@ -438,11 +429,6 @@ namespace UICatalog.Scenarios {
 			UpdateTitle (_curView);
 		}
 
-		public override void Run ()
-		{
-			base.Run ();
-		}
-
 		private void Quit ()
 		{
 			Application.RequestStop ();

+ 1 - 8
UICatalog/Scenarios/BackgroundWorkerCollection.cs

@@ -12,17 +12,10 @@ namespace UICatalog.Scenarios {
 	[ScenarioCategory ("Dialogs")]
 	[ScenarioCategory ("Controls")]
 	public class BackgroundWorkerCollection : Scenario {
-		public override void Init (Toplevel top, ColorScheme colorScheme)
-		{
-			Application.Top.Dispose ();
-
-			Application.Run<MdiMain> ();
-
-			Application.Top.Dispose ();
-		}
 
 		public override void Run ()
 		{
+			Application.Run<MdiMain> ();
 		}
 
 		class MdiMain : Toplevel {

+ 4 - 6
UICatalog/Scenarios/BordersComparisons.cs

@@ -5,12 +5,10 @@ namespace UICatalog.Scenarios {
 	[ScenarioCategory ("Layout")]
 	[ScenarioCategory ("Borders")]
 	public class BordersComparisons : Scenario {
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
 
-			top = Application.Top;
-
 			var borderStyle = BorderStyle.Double;
 			var drawMarginFrame = false;
 			var borderThickness = new Thickness (1, 2, 3, 4);
@@ -53,7 +51,7 @@ namespace UICatalog.Scenarios {
 				Width = 10
 			};
 			win.Add (tf1, button, label, tv, tf2);
-			top.Add (win);
+			Application.Top.Add (win);
 
 			var top2 = new Border.ToplevelContainer (new Rect (50, 5, 40, 20),
 				new Border () {
@@ -92,7 +90,7 @@ namespace UICatalog.Scenarios {
 				Width = 10
 			};
 			top2.Add (tf3, button2, label2, tv2, tf4);
-			top.Add (top2);
+			Application.Top.Add (top2);
 
 			var frm = new FrameView (new Rect (95, 5, 40, 20), "Test3", null,
 				new Border () {
@@ -128,7 +126,7 @@ namespace UICatalog.Scenarios {
 				Width = 10
 			};
 			frm.Add (tf5, button3, label3, tv3, tf6);
-			top.Add (frm);
+			Application.Top.Add (frm);
 
 			Application.Run ();
 		}

+ 2 - 2
UICatalog/Scenarios/Buttons.cs

@@ -56,7 +56,7 @@ namespace UICatalog.Scenarios {
 
 			//View prev = colorButtonsLabel;
 
-			//With this method there is no need to call Top.Ready += () => Top.Redraw (Top.Bounds);
+			//With this method there is no need to call Application.TopReady += () => Application.TopRedraw (Top.Bounds);
 			var x = Pos.Right (colorButtonsLabel) + 2;
 			foreach (var colorScheme in Colors.ColorSchemes) {
 				var colorButton = new Button ($"{colorScheme.Key}") {
@@ -272,7 +272,7 @@ namespace UICatalog.Scenarios {
 				}
 			};
 
-			Top.Ready += () => radioGroup.Refresh ();
+			Application.Top.Ready += () => radioGroup.Refresh ();
 		}
 	}
 }

+ 353 - 43
UICatalog/Scenarios/CharacterMap.cs

@@ -1,12 +1,14 @@
 #define DRAW_CONTENT
 //#define BASE_DRAW_CONTENT
 
+using Microsoft.VisualBasic;
 using NStack;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using Terminal.Gui;
+using Terminal.Gui.Resources;
 using Rune = System.Rune;
 
 namespace UICatalog.Scenarios {
@@ -31,51 +33,67 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (),
 			};
 
-			var radioItems = new (ustring radioLabel, int start, int end) [] {
-				CreateRadio("ASCII Control Characters", 0x00, 0x1F),
-				CreateRadio("C0 Control Characters", 0x80, 0x9f),
-				CreateRadio("Hangul Jamo", 0x1100, 0x11ff),	// This is where wide chars tend to start
-				CreateRadio("Currency Symbols", 0x20A0, 0x20CF),
-				CreateRadio("Letter-like Symbols", 0x2100, 0x214F),
-				CreateRadio("Arrows", 0x2190, 0x21ff),
-				CreateRadio("Mathematical symbols", 0x2200, 0x22ff),
-				CreateRadio("Miscellaneous Technical", 0x2300, 0x23ff),
-				CreateRadio("Box Drawing & Geometric Shapes", 0x2500, 0x25ff),
-				CreateRadio("Miscellaneous Symbols", 0x2600, 0x26ff),
-				CreateRadio("Dingbats", 0x2700, 0x27ff),
-				CreateRadio("Braille", 0x2800, 0x28ff),
-				CreateRadio("Miscellaneous Symbols & Arrows", 0x2b00, 0x2bff),
-				CreateRadio("Alphabetic Pres. Forms", 0xFB00, 0xFb4f),
-				CreateRadio("Cuneiform Num. and Punct.", 0x12400, 0x1240f),
-				CreateRadio("Chess Symbols", 0x1FA00, 0x1FA0f),
-				CreateRadio("End", CharMap.MaxCodePointVal - 16, CharMap.MaxCodePointVal),
+			var jumpLabel = new Label ("Jump To Glyph:") { X = Pos.Right (_charMap) + 1, Y = Pos.Y (_charMap) };
+			Win.Add (jumpLabel);
+			var jumpEdit = new TextField () { X = Pos.Right (jumpLabel) + 1, Y = Pos.Y (_charMap), Width = 10, };
+			Win.Add (jumpEdit);
+			var unicodeLabel = new Label ("") { X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap) };
+			Win.Add (unicodeLabel);
+			jumpEdit.TextChanged += (s) => {
+				uint result = 0;
+				if (jumpEdit.Text.Length == 0) return;
+				try {
+					result = Convert.ToUInt32 (jumpEdit.Text.ToString (), 10);
+				} catch (OverflowException) {
+					unicodeLabel.Text = $"Invalid (overflow)";
+					return;
+				} catch (FormatException) {
+					try {
+						result = Convert.ToUInt32 (jumpEdit.Text.ToString (), 16);
+					} catch (OverflowException) {
+						unicodeLabel.Text = $"Invalid (overflow)";
+						return;
+					} catch (FormatException) {
+						unicodeLabel.Text = $"Invalid (can't parse)";
+						return;
+					}
+				}
+				unicodeLabel.Text = $"U+{result:x4}";
+				_charMap.SelectedGlyph = result;
 			};
-			(ustring radioLabel, int start, int end) CreateRadio (ustring title, int start, int end)
+
+			var radioItems = new (ustring radioLabel, uint start, uint end) [UnicodeRange.Ranges.Count];
+
+			for (var i = 0; i < UnicodeRange.Ranges.Count; i++) {
+				var range = UnicodeRange.Ranges [i];
+				radioItems [i] = CreateRadio (range.Category, range.Start, range.End);
+			}
+			(ustring radioLabel, uint start, uint end) CreateRadio (ustring title, uint start, uint end)
 			{
 				return ($"{title} (U+{start:x5}-{end:x5})", start, end);
 			}
 
 			Win.Add (_charMap);
-			var label = new Label ("Jump To Unicode Block:") { X = Pos.Right (_charMap) + 1, Y = Pos.Y (_charMap) };
+			var label = new Label ("Jump To Unicode Block:") { X = Pos.Right (_charMap) + 1, Y = Pos.Bottom (jumpLabel) + 1 };
 			Win.Add (label);
 
-			var jumpList = new RadioGroup (radioItems.Select (t => t.radioLabel).ToArray ()) {
-				X = Pos.X (label),
+			var jumpList = new ListView (radioItems.Select (t => t.radioLabel).ToArray ()) {
+				X = Pos.X (label) + 1,
 				Y = Pos.Bottom (label),
-				Width = radioItems.Max (r => r.radioLabel.Length) + 3,
-				SelectedItem = 8
+				Width = radioItems.Max (r => r.radioLabel.Length) + 2,
+				Height = Dim.Fill(1),
+				SelectedItem = 0
 			};
 			jumpList.SelectedItemChanged += (args) => {
-				_charMap.Start = radioItems [args.SelectedItem].start;
+				_charMap.StartGlyph = radioItems [jumpList.SelectedItem].start;
 			};
 
 			Win.Add (jumpList);
 
-			jumpList.Refresh ();
-			jumpList.SetFocus ();
+			//jumpList.Refresh ();
+			_charMap.SetFocus ();
 
 			_charMap.Width = Dim.Fill () - jumpList.Width;
-
 		}
 	}
 
@@ -85,23 +103,50 @@ namespace UICatalog.Scenarios {
 		/// Specifies the starting offset for the character map. The default is 0x2500 
 		/// which is the Box Drawing characters.
 		/// </summary>
-		public int Start {
+		public uint StartGlyph {
 			get => _start;
 			set {
 				_start = value;
-				ContentOffset = new Point (0, _start / 16);
+				_selected = value;
+				ContentOffset = new Point (0, (int)(_start / 16));
 				SetNeedsDisplay ();
 			}
 		}
 
-		int _start = 0x2500;
+		/// <summary>
+		/// Specifies the starting offset for the character map. The default is 0x2500 
+		/// which is the Box Drawing characters.
+		/// </summary>
+		public uint SelectedGlyph {
+			get => _selected;
+			set {
+				_selected = value;
+				int row = (int)_selected / 16;
+				int height = (Bounds.Height / ROW_HEIGHT) - 1;
+				if (row + ContentOffset.Y < 0) {
+					// Moving up.
+					ContentOffset = new Point (0, row);
+				} else if (row + ContentOffset.Y >= height) {
+					// Moving down.
+					ContentOffset = new Point (0, Math.Min (row, (row - height) + 1));
+
+				} else {
+					//ContentOffset = new Point (0, Math.Min (row, (row - height) - 1));
+				}
+
+				SetNeedsDisplay ();
+			}
+		}
+
+		uint _start = 0;
+		uint _selected = 0;
 
 		public const int COLUMN_WIDTH = 3;
 		public const int ROW_HEIGHT = 1;
 
-		public static int MaxCodePointVal => 0x10FFFF;
+		public static uint MaxCodePointVal => 0x10FFFF;
 
-		public static int RowLabelWidth => $"U+{MaxCodePointVal:x5}".Length;
+		public static int RowLabelWidth => $"U+{MaxCodePointVal:x5}".Length + 1; 
 		public static int RowWidth => RowLabelWidth + (COLUMN_WIDTH * 16);
 
 		public CharMap ()
@@ -109,7 +154,7 @@ namespace UICatalog.Scenarios {
 			ColorScheme = Colors.Dialog;
 			CanFocus = true;
 
-			ContentSize = new Size (CharMap.RowWidth, MaxCodePointVal / 16);
+			ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePointVal / 16 + 1));
 			ShowVerticalScrollIndicator = true;
 			ShowHorizontalScrollIndicator = false;
 			LayoutComplete += (args) => {
@@ -124,10 +169,61 @@ namespace UICatalog.Scenarios {
 			};
 			DrawContent += CharMap_DrawContent;
 
-			AddCommand (Command.ScrollUp, () => { ScrollUp (1); return true; });
-			AddCommand (Command.ScrollDown, () => { ScrollDown (1); return true; });
-			AddCommand (Command.ScrollLeft, () => { ScrollLeft (1); return true; });
-			AddCommand (Command.ScrollRight, () => { ScrollRight (1); return true; });
+			AddCommand (Command.ScrollUp, () => {
+				if (SelectedGlyph >= 16) {
+					SelectedGlyph = SelectedGlyph - 16;
+				}
+				return true;
+			});
+			AddCommand (Command.ScrollDown, () => {
+				if (SelectedGlyph < MaxCodePointVal - 16) {
+					SelectedGlyph = SelectedGlyph + 16;
+				}
+				return true;
+			});
+			AddCommand (Command.ScrollLeft, () => {
+				if (SelectedGlyph > 0) {
+					SelectedGlyph--;
+				}
+				return true;
+			});
+			AddCommand (Command.ScrollRight, () => {
+				if (SelectedGlyph < MaxCodePointVal - 1) {
+					SelectedGlyph++;
+				}
+				return true;
+			});
+			AddCommand (Command.PageUp, () => {
+				var page = (uint)(Bounds.Height / ROW_HEIGHT - 1) * 16;
+				SelectedGlyph -= Math.Min(page, SelectedGlyph);
+				return true;
+			});
+			AddCommand (Command.PageDown, () => {
+				var page = (uint)(Bounds.Height / ROW_HEIGHT - 1) * 16;
+				SelectedGlyph += Math.Min(page, MaxCodePointVal -SelectedGlyph);
+				return true;
+			});
+			AddCommand (Command.TopHome, () => {
+				SelectedGlyph = 0;
+				return true;
+			});
+			AddCommand (Command.BottomEnd, () => {
+				SelectedGlyph = MaxCodePointVal;
+				return true;
+			});
+
+			MouseClick += Handle_MouseClick;
+			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+		}
+
+		private void CopyValue ()
+		{
+			Clipboard.Contents = $"U+{SelectedGlyph:x5}";
+		}
+
+		private void CopyGlyph ()
+		{
+			Clipboard.Contents = $"{new Rune (SelectedGlyph)}";
 		}
 
 		private void CharMap_DrawContent (Rect viewport)
@@ -148,8 +244,6 @@ namespace UICatalog.Scenarios {
 					Driver.AddStr ($" {hexDigit:x} ");
 				}
 			}
-			//Move (RowWidth, 0);
-			//Driver.AddRune (' ');
 
 			var firstColumnX = viewport.X + RowLabelWidth;
 			for (int row = -ContentOffset.Y, y = 0; row <= (-ContentOffset.Y) + (Bounds.Height / ROW_HEIGHT); row++, y += ROW_HEIGHT) {
@@ -157,10 +251,11 @@ namespace UICatalog.Scenarios {
 				Driver.SetAttribute (GetNormalColor ());
 				Move (firstColumnX, y + 1);
 				Driver.AddStr (new string (' ', 16 * COLUMN_WIDTH));
-				if (val < MaxCodePointVal) {
+				if (val <= MaxCodePointVal) {
 					Driver.SetAttribute (GetNormalColor ());
 					for (int col = 0; col < 16; col++) {
-						var rune = new Rune ((uint)((uint)val + col));
+						uint glyph = (uint)((uint)val + col);
+						var rune = new Rune (glyph);
 						//if (rune >= 0x00D800 && rune <= 0x00DFFF) {
 						//	if (col == 0) {
 						//		Driver.AddStr ("Reserved for surrogate pairs.");
@@ -168,21 +263,236 @@ namespace UICatalog.Scenarios {
 						//	continue;
 						//}						
 						Move (firstColumnX + (col * COLUMN_WIDTH) + 1, y + 1);
+						if (glyph == SelectedGlyph) {
+							Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal);
+						} else {
+							Driver.SetAttribute (GetNormalColor ());
+						}
 						Driver.AddRune (rune);
 					}
 					Move (0, y + 1);
 					Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : ColorScheme.Focus);
-					var rowLabel = $"U+{val / 16:x4}x ";
+					var rowLabel = $"U+{val / 16:x5}_ ";
 					Driver.AddStr (rowLabel);
 				}
 			}
 			Driver.Clip = oldClip;
 		}
 
+		ContextMenu _contextMenu = new ContextMenu ();
+		void Handle_MouseClick (MouseEventArgs args)
+		{
+			var me = args.MouseEvent;
+			if (me.Flags == MouseFlags.ReportMousePosition || (me.Flags != MouseFlags.Button1Clicked &&
+				me.Flags != MouseFlags.Button1DoubleClicked &&
+				me.Flags != _contextMenu.MouseFlags)) {
+				return;
+			}
+
+			if (me.X < RowLabelWidth) {
+				return;
+			}
+
+			if (me.Y < 1) {
+				return;
+			}
+
+			var row = (me.Y - 1);
+			var col = (me.X - RowLabelWidth - ContentOffset.X) / COLUMN_WIDTH;
+			uint val = (uint)((((uint)row - (uint)ContentOffset.Y) * 16) + col);
+			if (val > MaxCodePointVal) {
+				return;
+			}
+
+			if (me.Flags == MouseFlags.Button1Clicked) {
+				SelectedGlyph = (uint)val;
+				return;
+			}
+
+			if (me.Flags == MouseFlags.Button1DoubleClicked) {
+				SelectedGlyph = (uint)val;
+				MessageBox.Query ("Glyph", $"{new Rune (val)} U+{SelectedGlyph:x4}", "Ok");
+				return;
+			}
+
+			if (me.Flags == _contextMenu.MouseFlags) {
+				SelectedGlyph = (uint)val;
+				_contextMenu = new ContextMenu (me.X + 1, me.Y + 1,
+					new MenuBarItem (new MenuItem [] {
+					new MenuItem ("_Copy Glyph", "", () => CopyGlyph (), null, null, Key.C | Key.CtrlMask),
+					new MenuItem ("Copy _Value", "", () => CopyValue (), null, null, Key.C | Key.ShiftMask | Key.CtrlMask),
+					}) {
+
+					}
+				);
+				_contextMenu.Show ();
+			}
+		}
+
 		protected override void Dispose (bool disposing)
 		{
 			DrawContent -= CharMap_DrawContent;
 			base.Dispose (disposing);
 		}
 	}
+
+	class UnicodeRange {
+		public uint Start;
+		public uint End;
+		public string Category;
+		public UnicodeRange (uint start, uint end, string category)
+		{
+			this.Start = start;
+			this.End = end;
+			this.Category = category;
+		}
+			
+		public static List<UnicodeRange> Ranges = new List<UnicodeRange> {
+			new UnicodeRange (0x0000, 0x001F, "ASCII Control Characters"),
+			new UnicodeRange (0x0080, 0x009F, "C0 Control Characters"),
+			new UnicodeRange(0x1100, 0x11ff,"Hangul Jamo"),	// This is where wide chars tend to start
+			new UnicodeRange(0x20A0, 0x20CF,"Currency Symbols"),
+			new UnicodeRange(0x2100, 0x214F,"Letterlike Symbols"),
+			new UnicodeRange(0x2160, 0x218F, "Roman Numerals"),
+			new UnicodeRange(0x2190, 0x21ff,"Arrows" ),
+			new UnicodeRange(0x2200, 0x22ff,"Mathematical symbols"),
+			new UnicodeRange(0x2300, 0x23ff,"Miscellaneous Technical"),
+			new UnicodeRange(0x24B6, 0x24e9,"Circled Latin Capital Letters"), 
+			new UnicodeRange(0x1F130, 0x1F149,"Squared Latin Capital Letters"), 
+			new UnicodeRange(0x2500, 0x25ff,"Box Drawing & Geometric Shapes"),
+			new UnicodeRange(0x2600, 0x26ff,"Miscellaneous Symbols"),
+			new UnicodeRange(0x2700, 0x27ff,"Dingbats"),
+			new UnicodeRange(0x2800, 0x28ff,"Braille"),
+			new UnicodeRange(0x2b00, 0x2bff,"Miscellaneous Symbols and Arrows"),
+			new UnicodeRange(0xFB00, 0xFb4f,"Alphabetic Presentation Forms"),
+			new UnicodeRange(0x12400, 0x1240f,"Cuneiform Numbers and Punctuation"),
+			new UnicodeRange(0x1FA00, 0x1FA0f,"Chess Symbols"),
+
+			new UnicodeRange (0x0020 ,0x007F        ,"Basic Latin"),
+			new UnicodeRange (0x00A0 ,0x00FF        ,"Latin-1 Supplement"),
+			new UnicodeRange (0x0100 ,0x017F        ,"Latin Extended-A"),
+			new UnicodeRange (0x0180 ,0x024F        ,"Latin Extended-B"),
+			new UnicodeRange (0x0250 ,0x02AF        ,"IPA Extensions"),
+			new UnicodeRange (0x02B0 ,0x02FF        ,"Spacing Modifier Letters"),
+			new UnicodeRange (0x0300 ,0x036F        ,"Combining Diacritical Marks"),
+			new UnicodeRange (0x0370 ,0x03FF        ,"Greek and Coptic"),
+			new UnicodeRange (0x0400 ,0x04FF        ,"Cyrillic"),
+			new UnicodeRange (0x0500 ,0x052F        ,"Cyrillic Supplementary"),
+			new UnicodeRange (0x0530 ,0x058F        ,"Armenian"),
+			new UnicodeRange (0x0590 ,0x05FF        ,"Hebrew"),
+			new UnicodeRange (0x0600 ,0x06FF        ,"Arabic"),
+			new UnicodeRange (0x0700 ,0x074F        ,"Syriac"),
+			new UnicodeRange (0x0780 ,0x07BF        ,"Thaana"),
+			new UnicodeRange (0x0900 ,0x097F        ,"Devanagari"),
+			new UnicodeRange (0x0980 ,0x09FF        ,"Bengali"),
+			new UnicodeRange (0x0A00 ,0x0A7F        ,"Gurmukhi"),
+			new UnicodeRange (0x0A80 ,0x0AFF        ,"Gujarati"),
+			new UnicodeRange (0x0B00 ,0x0B7F        ,"Oriya"),
+			new UnicodeRange (0x0B80 ,0x0BFF        ,"Tamil"),
+			new UnicodeRange (0x0C00 ,0x0C7F        ,"Telugu"),
+			new UnicodeRange (0x0C80 ,0x0CFF        ,"Kannada"),
+			new UnicodeRange (0x0D00 ,0x0D7F        ,"Malayalam"),
+			new UnicodeRange (0x0D80 ,0x0DFF        ,"Sinhala"),
+			new UnicodeRange (0x0E00 ,0x0E7F        ,"Thai"),
+			new UnicodeRange (0x0E80 ,0x0EFF        ,"Lao"),
+			new UnicodeRange (0x0F00 ,0x0FFF        ,"Tibetan"),
+			new UnicodeRange (0x1000 ,0x109F        ,"Myanmar"),
+			new UnicodeRange (0x10A0 ,0x10FF        ,"Georgian"),
+			new UnicodeRange (0x1100 ,0x11FF        ,"Hangul Jamo"),
+			new UnicodeRange (0x1200 ,0x137F        ,"Ethiopic"),
+			new UnicodeRange (0x13A0 ,0x13FF        ,"Cherokee"),
+			new UnicodeRange (0x1400 ,0x167F        ,"Unified Canadian Aboriginal Syllabics"),
+			new UnicodeRange (0x1680 ,0x169F        ,"Ogham"),
+			new UnicodeRange (0x16A0 ,0x16FF        ,"Runic"),
+			new UnicodeRange (0x1700 ,0x171F        ,"Tagalog"),
+			new UnicodeRange (0x1720 ,0x173F        ,"Hanunoo"),
+			new UnicodeRange (0x1740 ,0x175F        ,"Buhid"),
+			new UnicodeRange (0x1760 ,0x177F        ,"Tagbanwa"),
+			new UnicodeRange (0x1780 ,0x17FF        ,"Khmer"),
+			new UnicodeRange (0x1800 ,0x18AF        ,"Mongolian"),
+			new UnicodeRange (0x1900 ,0x194F        ,"Limbu"),
+			new UnicodeRange (0x1950 ,0x197F        ,"Tai Le"),
+			new UnicodeRange (0x19E0 ,0x19FF        ,"Khmer Symbols"),
+			new UnicodeRange (0x1D00 ,0x1D7F        ,"Phonetic Extensions"),
+			new UnicodeRange (0x1E00 ,0x1EFF        ,"Latin Extended Additional"),
+			new UnicodeRange (0x1F00 ,0x1FFF        ,"Greek Extended"),
+			new UnicodeRange (0x2000 ,0x206F        ,"General Punctuation"),
+			new UnicodeRange (0x2070 ,0x209F        ,"Superscripts and Subscripts"),
+			new UnicodeRange (0x20A0 ,0x20CF        ,"Currency Symbols"),
+			new UnicodeRange (0x20D0 ,0x20FF        ,"Combining Diacritical Marks for Symbols"),
+			new UnicodeRange (0x2100 ,0x214F        ,"Letterlike Symbols"),
+			new UnicodeRange (0x2150 ,0x218F        ,"Number Forms"),
+			new UnicodeRange (0x2190 ,0x21FF        ,"Arrows"),
+			new UnicodeRange (0x2200 ,0x22FF        ,"Mathematical Operators"),
+			new UnicodeRange (0x2300 ,0x23FF        ,"Miscellaneous Technical"),
+			new UnicodeRange (0x2400 ,0x243F        ,"Control Pictures"),
+			new UnicodeRange (0x2440 ,0x245F        ,"Optical Character Recognition"),
+			new UnicodeRange (0x2460 ,0x24FF        ,"Enclosed Alphanumerics"),
+			new UnicodeRange (0x2500 ,0x257F        ,"Box Drawing"),
+			new UnicodeRange (0x2580 ,0x259F        ,"Block Elements"),
+			new UnicodeRange (0x25A0 ,0x25FF        ,"Geometric Shapes"),
+			new UnicodeRange (0x2600 ,0x26FF        ,"Miscellaneous Symbols"),
+			new UnicodeRange (0x2700 ,0x27BF        ,"Dingbats"),
+			new UnicodeRange (0x27C0 ,0x27EF        ,"Miscellaneous Mathematical Symbols-A"),
+			new UnicodeRange (0x27F0 ,0x27FF        ,"Supplemental Arrows-A"),
+			new UnicodeRange (0x2800 ,0x28FF        ,"Braille Patterns"),
+			new UnicodeRange (0x2900 ,0x297F        ,"Supplemental Arrows-B"),
+			new UnicodeRange (0x2980 ,0x29FF        ,"Miscellaneous Mathematical Symbols-B"),
+			new UnicodeRange (0x2A00 ,0x2AFF        ,"Supplemental Mathematical Operators"),
+			new UnicodeRange (0x2B00 ,0x2BFF        ,"Miscellaneous Symbols and Arrows"),
+			new UnicodeRange (0x2E80 ,0x2EFF        ,"CJK Radicals Supplement"),
+			new UnicodeRange (0x2F00 ,0x2FDF        ,"Kangxi Radicals"),
+			new UnicodeRange (0x2FF0 ,0x2FFF        ,"Ideographic Description Characters"),
+			new UnicodeRange (0x3000 ,0x303F        ,"CJK Symbols and Punctuation"),
+			new UnicodeRange (0x3040 ,0x309F        ,"Hiragana"),
+			new UnicodeRange (0x30A0 ,0x30FF        ,"Katakana"),
+			new UnicodeRange (0x3100 ,0x312F        ,"Bopomofo"),
+			new UnicodeRange (0x3130 ,0x318F        ,"Hangul Compatibility Jamo"),
+			new UnicodeRange (0x3190 ,0x319F        ,"Kanbun"),
+			new UnicodeRange (0x31A0 ,0x31BF        ,"Bopomofo Extended"),
+			new UnicodeRange (0x31F0 ,0x31FF        ,"Katakana Phonetic Extensions"),
+			new UnicodeRange (0x3200 ,0x32FF        ,"Enclosed CJK Letters and Months"),
+			new UnicodeRange (0x3300 ,0x33FF        ,"CJK Compatibility"),
+			new UnicodeRange (0x3400 ,0x4DBF        ,"CJK Unified Ideographs Extension A"),
+			new UnicodeRange (0x4DC0 ,0x4DFF        ,"Yijing Hexagram Symbols"),
+			new UnicodeRange (0x4E00 ,0x9FFF        ,"CJK Unified Ideographs"),
+			new UnicodeRange (0xA000 ,0xA48F        ,"Yi Syllables"),
+			new UnicodeRange (0xA490 ,0xA4CF        ,"Yi Radicals"),
+			new UnicodeRange (0xAC00 ,0xD7AF        ,"Hangul Syllables"),
+			new UnicodeRange (0xD800 ,0xDB7F        ,"High Surrogates"),
+			new UnicodeRange (0xDB80 ,0xDBFF        ,"High Private Use Surrogates"),
+			new UnicodeRange (0xDC00 ,0xDFFF        ,"Low Surrogates"),
+			new UnicodeRange (0xE000 ,0xF8FF        ,"Private Use Area"),
+			new UnicodeRange (0xF900 ,0xFAFF        ,"CJK Compatibility Ideographs"),
+			new UnicodeRange (0xFB00 ,0xFB4F        ,"Alphabetic Presentation Forms"),
+			new UnicodeRange (0xFB50 ,0xFDFF        ,"Arabic Presentation Forms-A"),
+			new UnicodeRange (0xFE00 ,0xFE0F        ,"Variation Selectors"),
+			new UnicodeRange (0xFE20 ,0xFE2F        ,"Combining Half Marks"),
+			new UnicodeRange (0xFE30 ,0xFE4F        ,"CJK Compatibility Forms"),
+			new UnicodeRange (0xFE50 ,0xFE6F        ,"Small Form Variants"),
+			new UnicodeRange (0xFE70 ,0xFEFF        ,"Arabic Presentation Forms-B"),
+			new UnicodeRange (0xFF00 ,0xFFEF        ,"Halfwidth and Fullwidth Forms"),
+			new UnicodeRange (0xFFF0 ,0xFFFF        ,"Specials"),
+			new UnicodeRange (0x10000, 0x1007F   ,"Linear B Syllabary"),
+			new UnicodeRange (0x10080, 0x100FF   ,"Linear B Ideograms"),
+			new UnicodeRange (0x10100, 0x1013F   ,"Aegean Numbers"),
+			new UnicodeRange (0x10300, 0x1032F   ,"Old Italic"),
+			new UnicodeRange (0x10330, 0x1034F   ,"Gothic"),
+			new UnicodeRange (0x10380, 0x1039F   ,"Ugaritic"),
+			new UnicodeRange (0x10400, 0x1044F   ,"Deseret"),
+			new UnicodeRange (0x10450, 0x1047F   ,"Shavian"),
+			new UnicodeRange (0x10480, 0x104AF   ,"Osmanya"),
+			new UnicodeRange (0x10800, 0x1083F   ,"Cypriot Syllabary"),
+			new UnicodeRange (0x1D000, 0x1D0FF   ,"Byzantine Musical Symbols"),
+			new UnicodeRange (0x1D100, 0x1D1FF   ,"Musical Symbols"),
+			new UnicodeRange (0x1D300, 0x1D35F   ,"Tai Xuan Jing Symbols"),
+			new UnicodeRange (0x1D400, 0x1D7FF   ,"Mathematical Alphanumeric Symbols"),
+			new UnicodeRange (0x1F600, 0x1F532   ,"Emojis Symbols"),
+			new UnicodeRange (0x20000, 0x2A6DF   ,"CJK Unified Ideographs Extension B"),
+			new UnicodeRange (0x2F800, 0x2FA1F   ,"CJK Compatibility Ideographs Supplement"),
+			new UnicodeRange (0xE0000, 0xE007F   ,"Tags"),
+			new UnicodeRange((uint)(CharMap.MaxCodePointVal - 16), (uint)CharMap.MaxCodePointVal,"End"),
+		};
+	}
+	
 }

+ 20 - 7
UICatalog/Scenarios/ClassExplorer.cs

@@ -53,27 +53,34 @@ namespace UICatalog.Scenarios {
 			}
 		}
 
+		MenuItem highlightModelTextOnly;
+
 		public override void Setup ()
 		{
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
 					new MenuItem ("_Quit", "", () => Quit()),
-				})
-				,
+				}),
 				new MenuBarItem ("_View", new MenuItem [] {
 					miShowPrivate = new MenuItem ("_Include Private", "", () => ShowPrivate()){
 						Checked = false,
 						CheckType = MenuItemCheckStyle.Checked
 					},
-				new MenuItem ("_Expand All", "", () => treeView.ExpandAll()),
-				new MenuItem ("_Collapse All", "", () => treeView.CollapseAll()) }),
+					new MenuItem ("_Expand All", "", () => treeView.ExpandAll()),
+					new MenuItem ("_Collapse All", "", () => treeView.CollapseAll())
+				}),
+				new MenuBarItem ("_Style", new MenuItem [] {
+					highlightModelTextOnly = new MenuItem ("_Highlight Model Text Only", "", () => OnCheckHighlightModelTextOnly()) {
+						CheckType = MenuItemCheckStyle.Checked
+					},
+				}) 
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			treeView = new TreeView<object> () {
 				X = 0,
@@ -82,7 +89,6 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (),
 			};
 
-
 			treeView.AddObjects (AppDomain.CurrentDomain.GetAssemblies ());
 			treeView.AspectGetter = GetRepresentation;
 			treeView.TreeBuilder = new DelegateTreeBuilder<object> (ChildGetter, CanExpand);
@@ -100,6 +106,13 @@ namespace UICatalog.Scenarios {
 			Win.Add (textView);
 		}
 
+		private void OnCheckHighlightModelTextOnly ()
+		{
+			treeView.Style.HighlightModelTextOnly = !treeView.Style.HighlightModelTextOnly;
+			highlightModelTextOnly.Checked = treeView.Style.HighlightModelTextOnly;
+			treeView.SetNeedsDisplay ();
+		}
+
 		private void ShowPrivate ()
 		{
 			miShowPrivate.Checked = !miShowPrivate.Checked;

+ 4 - 7
UICatalog/Scenarios/Clipping.cs

@@ -7,13 +7,10 @@ namespace UICatalog.Scenarios {
 
 	public class Clipping : Scenario {
 
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-
-			Top = top != null ? top : Application.Top;
-
-			Top.ColorScheme = Colors.Base;
+			Application.Top.ColorScheme = Colors.Base;
 		}
 
 		public override void Setup ()
@@ -26,7 +23,7 @@ namespace UICatalog.Scenarios {
 				X = 0, Y = 0,
 				//ColorScheme = Colors.Dialog
 			};
-			Top.Add (label);
+			Application.Top.Add (label);
 
 			var scrollView = new ScrollView (new Rect (3, 3, 50, 20));
 			scrollView.ColorScheme = Colors.Menu;
@@ -69,7 +66,7 @@ namespace UICatalog.Scenarios {
 
 			scrollView.Add (embedded1);
 
-			Top.Add (scrollView);
+			Application.Top.Add (scrollView);
 		}
 	}
 }

+ 11 - 13
UICatalog/Scenarios/CollectionNavigatorTester.cs

@@ -15,11 +15,10 @@ namespace UICatalog.Scenarios {
 	public class CollectionNavigatorTester : Scenario {
 
 		// Don't create a Window, just return the top-level view
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = top != null ? top : Application.Top;
-			Top.ColorScheme = Colors.Base;
+			Application.Top.ColorScheme = Colors.Base;
 		}
 
 		System.Collections.Generic.List<string> _items = new string [] {
@@ -103,7 +102,7 @@ namespace UICatalog.Scenarios {
 				new MenuBarItem("_Quit", "CTRL-Q", () => Quit()),
 			});
 
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			_items.Sort (StringComparer.OrdinalIgnoreCase);
 
@@ -113,7 +112,7 @@ namespace UICatalog.Scenarios {
 				Y = 1,
 				Height = Dim.Fill ()
 			};
-			Top.Add (vsep);
+			Application.Top.Add (vsep);
 			CreateTreeView ();
 		}
 
@@ -129,7 +128,7 @@ namespace UICatalog.Scenarios {
 				Width = Dim.Percent (50),
 				Height = 1,
 			};
-			Top.Add (label);
+			Application.Top.Add (label);
 
 			_listView = new ListView () {
 				X = 0,
@@ -138,9 +137,8 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (),
 				AllowsMarking = false,
 				AllowsMultipleSelection = false,
-				ColorScheme = Colors.TopLevel
 			};
-			Top.Add (_listView);
+			Application.Top.Add (_listView);
 
 			_listView.SetSource (_items);
 
@@ -158,19 +156,19 @@ namespace UICatalog.Scenarios {
 				TextAlignment = TextAlignment.Centered,
 				X = Pos.Right (_listView) + 2,
 				Y = 1, // for menu
-				Width = Dim.Percent (50),
+				Width = Dim.Percent	 (50),
 				Height = 1,
 			};
-			Top.Add (label);
+			Application.Top.Add (label);
 
 			_treeView = new TreeView () {
 				X = Pos.Right (_listView) + 1,
 				Y = Pos.Bottom (label),
 				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-				ColorScheme = Colors.TopLevel
+				Height = Dim.Fill ()
 			};
-			Top.Add (_treeView);
+			_treeView.Style.HighlightModelTextOnly = true;
+			Application.Top.Add (_treeView);
 
 			var root = new TreeNode ("IsLetterOrDigit examples");
 			root.Children = _items.Where (i => char.IsLetterOrDigit (i [0])).Select (i => new TreeNode (i)).Cast<ITreeNode> ().ToList ();

+ 2 - 2
UICatalog/Scenarios/ComputedLayout.cs

@@ -25,12 +25,12 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Quit", "", () => Quit()),
 				}),
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			//Top.LayoutStyle = LayoutStyle.Computed;
 			// Demonstrate using Dim to create a horizontal ruler that always measures the parent window's width

+ 1 - 1
UICatalog/Scenarios/ContextMenus.cs

@@ -81,7 +81,7 @@ namespace UICatalog.Scenarios {
 
 			Win.WantMousePositionReports = true;
 
-			Top.Closed += (_) => {
+			Application.Top.Closed += (_) => {
 				Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US");
 				Application.RootMouseEvent -= Application_RootMouseEvent;
 			};

+ 3 - 3
UICatalog/Scenarios/CsvEditor.cs

@@ -34,7 +34,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			this.tableView = new TableView () {
 				X = 0,
@@ -70,14 +70,14 @@ namespace UICatalog.Scenarios {
 					miCentered = new MenuItem ("_Set Format Pattern", "", () => SetFormat()),
 				})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.O, "~^O~ Open", () => Open()),
 				new StatusItem(Key.CtrlMask | Key.S, "~^S~ Save", () => Save()),
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			Win.Add (tableView);
 

+ 2 - 2
UICatalog/Scenarios/Dialogs.cs

@@ -116,9 +116,9 @@ namespace UICatalog.Scenarios {
 			{
 				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit)
 					+ Dim.Height (numButtonsEdit) + Dim.Height (styleRadioGroup) + Dim.Height(glyphsNotWords) + 2;
-				Top.Loaded -= Top_Loaded;
+				Application.Top.Loaded -= Top_Loaded;
 			}
-			Top.Loaded += Top_Loaded;
+			Application.Top.Loaded += Top_Loaded;
 
 			label = new Label ("Button Pressed:") {
 				X = Pos.Center (),

+ 2 - 3
UICatalog/Scenarios/DynamicMenuBar.cs

@@ -13,11 +13,10 @@ namespace UICatalog.Scenarios {
 	[ScenarioCategory ("Top Level Windows")]
 	[ScenarioCategory ("Menus")]
 	public class DynamicMenuBar : Scenario {
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = Application.Top;
-			Top.Add (new DynamicMenuBarSample ($"CTRL-Q to Close - Scenario: {GetName ()}"));
+			Application.Top.Add (new DynamicMenuBarSample ($"CTRL-Q to Close - Scenario: {GetName ()}"));
 		}
 
 		public class DynamicMenuItemList {

+ 2 - 3
UICatalog/Scenarios/DynamicStatusBar.cs

@@ -12,11 +12,10 @@ namespace UICatalog.Scenarios {
 	[ScenarioMetadata (Name: "Dynamic StatusBar", Description: "Demonstrates how to add and remove a StatusBar and change items dynamically.")]
 	[ScenarioCategory ("Top Level Windows")]
 	public class DynamicStatusBar : Scenario {
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = Application.Top;
-			Top.Add (new DynamicStatusBarSample ($"CTRL-Q to Close - Scenario: {GetName ()}"));
+			Application.Top.Add (new DynamicStatusBarSample ($"CTRL-Q to Close - Scenario: {GetName ()}"));
 		}
 
 		public class DynamicStatusItemList {

+ 7 - 7
UICatalog/Scenarios/Editor.cs

@@ -30,12 +30,12 @@ namespace UICatalog.Scenarios {
 		private TabView _tabView;
 		private MenuItem _miForceMinimumPosToZero;
 		private bool _forceMinimumPosToZero = true;
-		private readonly List<CultureInfo> _cultureInfos = Application.SupportedCultures;
+		private List<CultureInfo> _cultureInfos;
 
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = top != null ? top : Application.Top;
+			_cultureInfos = Application.SupportedCultures;
 
 			Win = new Window (_fileName ?? "Untitled") {
 				X = 0,
@@ -44,7 +44,7 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (),
 				ColorScheme = colorScheme,
 			};
-			Top.Add (Win);
+			Application.Top.Add (Win);
 
 			_textView = new TextView () {
 				X = 0,
@@ -114,7 +114,7 @@ namespace UICatalog.Scenarios {
 				})
 			});
 
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				siCursorPosition,
@@ -124,7 +124,7 @@ namespace UICatalog.Scenarios {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 				new StatusItem(Key.Null, $"OS Clipboard IsSupported : {Clipboard.IsSupported}", null)
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			_scrollBar = new ScrollBarView (_textView, true);
 
@@ -196,7 +196,7 @@ namespace UICatalog.Scenarios {
 				}
 			};
 
-			Top.Closed += (_) => Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US");
+			Application.Top.Closed += (_) => Thread.CurrentThread.CurrentUICulture = new CultureInfo ("en-US");
 		}
 
 		private void DisposeWinDialog ()

+ 3 - 3
UICatalog/Scenarios/GraphViewExample.cs

@@ -23,7 +23,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			graphs = new Action [] {
 				 ()=>SetupPeriodicTableScatterPlot(),    //0
@@ -59,7 +59,7 @@ namespace UICatalog.Scenarios {
 				}),
 
 				});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			graphView = new GraphView () {
 				X = 1,
@@ -92,7 +92,7 @@ namespace UICatalog.Scenarios {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 				new StatusItem(Key.CtrlMask | Key.G, "~^G~ Next", ()=>graphs[currentGraph++%graphs.Length]()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 		}
 
 		private void MultiBarGraph ()

+ 2 - 2
UICatalog/Scenarios/HexEditor.cs

@@ -52,7 +52,7 @@ namespace UICatalog.Scenarios {
 					miAllowEdits = new MenuItem ("_AllowEdits", "", () => ToggleAllowEdits ()){Checked = _hexView.AllowEdits, CheckType = MenuItemCheckStyle.Checked}
 				})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.F2, "~F2~ Open", () => Open()),
@@ -61,7 +61,7 @@ namespace UICatalog.Scenarios {
 				siPositionChanged = new StatusItem(Key.Null,
 					$"Position: {_hexView.Position} Line: {_hexView.CursorPosition.Y} Col: {_hexView.CursorPosition.X} Line length: {_hexView.BytesPerLine}", () => {})
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 		}
 
 		private void _hexView_PositionChanged (HexView.HexViewEventArgs obj)

+ 3 - 3
UICatalog/Scenarios/InteractiveTree.cs

@@ -20,14 +20,14 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
 					new MenuItem ("_Quit", "", () => Quit()),
 				})
 				});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			treeView = new TreeView () {
 				X = 0,
@@ -45,7 +45,7 @@ namespace UICatalog.Scenarios {
 				new StatusItem(Key.CtrlMask | Key.T, "~^T~ Add Root", () => AddRootNode()),
 				new StatusItem(Key.CtrlMask | Key.R, "~^R~ Rename Node", () => RenameNode()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 		}
 

+ 6 - 7
UICatalog/Scenarios/Keys.cs

@@ -48,10 +48,9 @@ namespace UICatalog.Scenarios {
 			}
 		}
 
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = top != null ? top : Application.Top;
 			
 			Win = new TestWindow ($"CTRL-Q to Close - Scenario: {GetName ()}") {
 				X = 0,
@@ -60,7 +59,7 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (),
 				ColorScheme = colorScheme,
 			};
-			Top.Add (Win);
+			Application.Top.Add (Win);
 		}
 
 		public override void Setup ()
@@ -107,7 +106,7 @@ namespace UICatalog.Scenarios {
 				Shift = true
 			});
 			var maxLogEntry = $"Key{"",-5}: {fakeKeyPress}".Length;
-			var yOffset = (Top == Application.Top ? 1 : 6);
+			var yOffset = (Application.Top == Application.Top ? 1 : 6);
 			var keyStrokelist = new List<string> ();
 			var keyStrokeListView = new ListView (keyStrokelist) {
 				X = 0,
@@ -126,7 +125,7 @@ namespace UICatalog.Scenarios {
 			Win.Add (processKeyLogLabel);
 
 			maxLogEntry = $"{fakeKeyPress}".Length;
-			yOffset = (Top == Application.Top ? 1 : 6);
+			yOffset = (Application.Top == Application.Top ? 1 : 6);
 			var processKeyListView = new ListView (((TestWindow)Win)._processKeyList) {
 				X = Pos.Left (processKeyLogLabel),
 				Y = Pos.Top (processKeyLogLabel) + yOffset,
@@ -144,7 +143,7 @@ namespace UICatalog.Scenarios {
 			};
 			Win.Add (processHotKeyLogLabel);
 
-			yOffset = (Top == Application.Top ? 1 : 6);
+			yOffset = (Application.Top == Application.Top ? 1 : 6);
 			var processHotKeyListView = new ListView (((TestWindow)Win)._processHotKeyList) {
 				X = Pos.Left (processHotKeyLogLabel),
 				Y = Pos.Top (processHotKeyLogLabel) + yOffset,
@@ -162,7 +161,7 @@ namespace UICatalog.Scenarios {
 			};
 			Win.Add (processColdKeyLogLabel);
 
-			yOffset = (Top == Application.Top ? 1 : 6);
+			yOffset = (Application.Top == Application.Top ? 1 : 6);
 			var processColdKeyListView = new ListView (((TestWindow)Win)._processColdKeyList) {
 				X = Pos.Left (processColdKeyLogLabel),
 				Y = Pos.Top (processColdKeyLogLabel) + yOffset,

+ 3 - 3
UICatalog/Scenarios/LabelsAsButtons.cs

@@ -59,7 +59,7 @@ namespace UICatalog.Scenarios {
 			};
 			Win.Add (colorLabelsLabel);
 
-			//With this method there is no need to call Top.Ready += () => Top.Redraw (Top.Bounds);
+			//With this method there is no need to call Application.TopReady += () => Application.TopRedraw (Top.Bounds);
 			var x = Pos.Right (colorLabelsLabel) + 2;
 			foreach (var colorScheme in Colors.ColorSchemes) {
 				var colorLabel = new Label ($"{colorScheme.Key}") {
@@ -73,7 +73,7 @@ namespace UICatalog.Scenarios {
 				Win.Add (colorLabel);
 				x += colorLabel.Text.Length + 2;
 			}
-			Top.Ready += () => Top.Redraw (Top.Bounds);
+			Application.Top.Ready += () => Application.Top.Redraw (Application.Top.Bounds);
 
 			Label Label;
 			Win.Add (Label = new Label ("A super long _Label that will probably expose a bug in clipping or wrapping of text. Will it?") {
@@ -306,7 +306,7 @@ namespace UICatalog.Scenarios {
 				}
 			};
 
-			Top.Ready += () => radioGroup.Refresh ();
+			Application.Top.Ready += () => radioGroup.Refresh ();
 		}
 	}
 }

+ 3 - 3
UICatalog/Scenarios/LineViewExample.cs

@@ -17,14 +17,14 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
 				new MenuItem ("_Quit", "", () => Quit()),
 			})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 
 			Win.Add (new Label ("Regular Line") { Y = 0 });
@@ -94,7 +94,7 @@ namespace UICatalog.Scenarios {
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit())
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 		}
 

+ 2 - 2
UICatalog/Scenarios/MessageBoxes.cs

@@ -156,9 +156,9 @@ namespace UICatalog.Scenarios {
 			{
 				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + Dim.Height (messageEdit)
 				+ Dim.Height (numButtonsEdit) + Dim.Height (defaultButtonEdit) + Dim.Height (styleRadioGroup) + 2 + Dim.Height (ckbEffect3D);
-				Top.Loaded -= Top_Loaded;
+				Application.Top.Loaded -= Top_Loaded;
 			}
-			Top.Loaded += Top_Loaded;
+			Application.Top.Loaded += Top_Loaded;
 
 			label = new Label ("Button Pressed:") {
 				X = Pos.Center (),

+ 3 - 3
UICatalog/Scenarios/MultiColouredTable.cs

@@ -16,7 +16,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			this.tableView = new TableViewColors () {
 				X = 0,
@@ -30,12 +30,12 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Quit", "", () => Quit()),
 				}),
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			Win.Add (tableView);
 

+ 5 - 6
UICatalog/Scenarios/Notepad.cs

@@ -11,11 +11,10 @@ namespace UICatalog.Scenarios {
 		private int numbeOfNewTabs = 1;
 
 		// Don't create a Window, just return the top-level view
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = top != null ? top : Application.Top;
-			Top.ColorScheme = Colors.Base;
+			Application.Top.ColorScheme = Colors.Base;
 		}
 
 		public override void Setup ()
@@ -30,7 +29,7 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Quit", "", () => Quit()),
 				})
 				});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			tabView = new TabView () {
 				X = 0,
@@ -42,7 +41,7 @@ namespace UICatalog.Scenarios {
 			tabView.Style.ShowBorder = true;
 			tabView.ApplyStyleChanges ();
 
-			Top.Add (tabView);
+			Application.Top.Add (tabView);
 
 			var lenStatusItem = new StatusItem (Key.CharMask, "Len: ", null);
 			var statusBar = new StatusBar (new StatusItem [] {
@@ -59,7 +58,7 @@ namespace UICatalog.Scenarios {
 
 			tabView.SelectedTabChanged += (s, e) => lenStatusItem.Title = $"Len:{(e.NewTab?.View?.Text?.Length ?? 0)}";
 
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			New ();
 		}

+ 2 - 2
UICatalog/Scenarios/ProgressBarStyles.cs

@@ -131,7 +131,7 @@ namespace UICatalog.Scenarios {
 				Application.MainLoop.Driver.Wakeup ();
 			}, null, 0, 300);
 
-			Top.Unloaded += Top_Unloaded;
+			Application.Top.Unloaded += Top_Unloaded;
 
 			void Top_Unloaded ()
 			{
@@ -143,7 +143,7 @@ namespace UICatalog.Scenarios {
 					_pulseTimer.Dispose ();
 					_pulseTimer = null;
 				}
-				Top.Unloaded -= Top_Unloaded;
+				Application.Top.Unloaded -= Top_Unloaded;
 			}
 		}
 	}

+ 76 - 0
UICatalog/Scenarios/RunTExample.cs

@@ -0,0 +1,76 @@
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "Run<T> Example", Description: "Illustrates using Application.Run<T> to run a custom class")]
+	[ScenarioCategory ("Top Level Windows")]
+	public class RunTExample : Scenario {
+		public override void Setup ()
+		{
+			// No need to call Init if Application.Run<T> is used
+		}
+
+		public override void Run ()
+		{
+			Application.Run<ExampleWindow> ();
+		}
+
+		public class ExampleWindow : Window {
+			public TextField usernameText;
+
+			public ExampleWindow ()
+			{
+				Title = "Example App (Ctrl+Q to quit)";
+
+				// Create input components and labels
+				var usernameLabel = new Label () {
+					Text = "Username:"
+				};
+
+				usernameText = new TextField ("") {
+					// Position text field adjacent to the label
+					X = Pos.Right (usernameLabel) + 1,
+
+					// Fill remaining horizontal space
+					Width = Dim.Fill (),
+				};
+
+				var passwordLabel = new Label () {
+					Text = "Password:",
+					X = Pos.Left (usernameLabel),
+					Y = Pos.Bottom (usernameLabel) + 1
+				};
+
+				var passwordText = new TextField ("") {
+					Secret = true,
+					// align with the text box above
+					X = Pos.Left (usernameText),
+					Y = Pos.Top (passwordLabel),
+					Width = Dim.Fill (),
+				};
+
+				// Create login button
+				var btnLogin = new Button () {
+					Text = "Login",
+					Y = Pos.Bottom (passwordLabel) + 1,
+					// center the login button horizontally
+					X = Pos.Center (),
+					IsDefault = true,
+				};
+
+				// When login button is clicked display a message popup
+				btnLogin.Clicked += () => {
+					if (usernameText.Text == "admin" && passwordText.Text == "password") {
+						MessageBox.Query ("Login Successful", $"Username: {usernameText.Text}", "Ok");
+						Application.RequestStop ();
+					} else {
+						MessageBox.ErrorQuery ("Error Logging In", "Incorrect username or password (hint: admin/password)", "Ok");
+					}
+				};
+
+				// Add the views to the Window
+				Add (usernameLabel, usernameText, passwordLabel, passwordText, btnLogin);
+			}
+		}
+
+	}
+}

+ 1 - 1
UICatalog/Scenarios/RuneWidthGreaterThanOne.cs

@@ -16,7 +16,7 @@ namespace UICatalog.Scenarios {
 		private Window _win;
 		private string _lastRunesUsed;
 
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
 

+ 4 - 4
UICatalog/Scenarios/Scrolling.cs

@@ -156,9 +156,9 @@ namespace UICatalog.Scenarios {
 				horizontalRuler.Text = rule.Repeat ((int)Math.Ceiling ((double)(horizontalRuler.Bounds.Width) / (double)rule.Length)) [0..(horizontalRuler.Bounds.Width)] +
 					"\n" + "|         ".Repeat ((int)Math.Ceiling ((double)(horizontalRuler.Bounds.Width) / (double)rule.Length)) [0..(horizontalRuler.Bounds.Width)];
 				verticalRuler.Text = vrule.Repeat ((int)Math.Ceiling ((double)(verticalRuler.Bounds.Height * 2) / (double)rule.Length)) [0..(verticalRuler.Bounds.Height * 2)];
-				Top.Loaded -= Top_Loaded;
+				Application.Top.Loaded -= Top_Loaded;
 			}
-			Top.Loaded += Top_Loaded;
+			Application.Top.Loaded += Top_Loaded;
 
 			var pressMeButton = new Button ("Press me!") {
 				X = 3,
@@ -313,9 +313,9 @@ namespace UICatalog.Scenarios {
 			void Top_Unloaded ()
 			{
 				pulsing = false;
-				Top.Unloaded -= Top_Unloaded;
+				Application.Top.Unloaded -= Top_Unloaded;
 			}
-			Top.Unloaded += Top_Unloaded;
+			Application.Top.Unloaded += Top_Unloaded;
 		}
 	}
 }

+ 2 - 2
UICatalog/Scenarios/SingleBackgroundWorker.cs

@@ -11,11 +11,11 @@ namespace UICatalog.Scenarios {
 	public class SingleBackgroundWorker : Scenario {
 		public override void Run ()
 		{
-			Top.Dispose ();
+			Application.Top.Dispose ();
 
 			Application.Run<MainApp> ();
 
-			Top.Dispose ();
+			Application.Top.Dispose ();
 		}
 
 		public class MainApp : Toplevel {

+ 3 - 3
UICatalog/Scenarios/SyntaxHighlighting.cs

@@ -21,7 +21,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
@@ -29,7 +29,7 @@ namespace UICatalog.Scenarios {
 				new MenuItem ("_Quit", "", () => Quit()),
 			})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			textView = new SqlTextView () {
 				X = 0,
@@ -49,7 +49,7 @@ namespace UICatalog.Scenarios {
 			});
 
 
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 		}
 
 		private void WordWrap ()

+ 3 - 7
UICatalog/Scenarios/TabViewExample.cs

@@ -24,7 +24,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
@@ -50,7 +50,7 @@ namespace UICatalog.Scenarios {
 
 					})
 				});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			tabView = new TabView () {
 				X = 0,
@@ -85,7 +85,6 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (),
 			};
 
-
 			frameRight.Add (new TextView () {
 				Text = "This demos the tabs control\nSwitch between tabs using cursor keys",
 				Width = Dim.Fill (),
@@ -94,8 +93,6 @@ namespace UICatalog.Scenarios {
 
 			Win.Add (frameRight);
 
-
-
 			var frameBelow = new FrameView ("Bottom Frame") {
 				X = 0,
 				Y = Pos.Bottom (tabView),
@@ -103,7 +100,6 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (),
 			};
 
-
 			frameBelow.Add (new TextView () {
 				Text = "This frame exists to check you can still tab here\nand that the tab control doesn't overspill it's bounds",
 				Width = Dim.Fill (),
@@ -115,7 +111,7 @@ namespace UICatalog.Scenarios {
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 		}
 
 		private void AddBlankTab ()

+ 4 - 4
UICatalog/Scenarios/TableEditor.cs

@@ -38,7 +38,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			this.tableView = new TableView () {
 				X = 0,
@@ -78,9 +78,9 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Set All MinAcceptableWidth=1", "",SetMinAcceptableWidthToOne),
 				}),
 			});
-		
 
-		Top.Add (menu);
+
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.F2, "~F2~ OpenExample", () => OpenExample(true)),
@@ -88,7 +88,7 @@ namespace UICatalog.Scenarios {
 				new StatusItem(Key.F4, "~F4~ OpenSimple", () => OpenSimple(true)),
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			Win.Add (tableView);
 

+ 84 - 33
UICatalog/Scenarios/Text.cs

@@ -1,5 +1,6 @@
 using NStack;
 using System;
+using System.IO;
 using System.Linq;
 using System.Text;
 using System.Text.RegularExpressions;
@@ -16,12 +17,12 @@ namespace UICatalog.Scenarios {
 	public class Text : Scenario {
 		public override void Setup ()
 		{
-			var s = "TAB to jump between text fields.";
-			var textField = new TextField (s) {
+			// TextField is a simple, single-line text input control
+			var textField = new TextField ("TextField with test text. Unicode shouldn't 𝔹Aℝ𝔽!") {
 				X = 1,
-				Y = 1,
-				Width = Dim.Percent (50),
-				//ColorScheme = Colors.Dialog
+				Y = 0,
+				Width = Dim.Percent (50) - 1,
+				Height = 2
 			};
 			textField.TextChanging += TextField_TextChanging;
 
@@ -36,7 +37,7 @@ namespace UICatalog.Scenarios {
 			var labelMirroringTextField = new Label (textField.Text) {
 				X = Pos.Right (textField) + 1,
 				Y = Pos.Top (textField),
-				Width = Dim.Fill (1)
+				Width = Dim.Fill (1) - 1
 			};
 			Win.Add (labelMirroringTextField);
 
@@ -44,15 +45,17 @@ namespace UICatalog.Scenarios {
 				labelMirroringTextField.Text = textField.Text;
 			};
 
+			// TextView is a rich (as in functionality, not formatting) text editing control
 			var textView = new TextView () {
 				X = 1,
-				Y = 3,
-				Width = Dim.Percent (50),
+				Y = Pos.Bottom (textField),
+				Width = Dim.Percent (50) - 1,
 				Height = Dim.Percent (30),
 			};
-			textView.Text = s;
+			textView.Text = "TextView with some more test text. Unicode shouldn't 𝔹Aℝ𝔽!" ;
 			textView.DrawContent += TextView_DrawContent;
 
+			// This shows how to enable autocomplete in TextView.
 			void TextView_DrawContent (Rect e)
 			{
 				textView.Autocomplete.AllSuggestions = Regex.Matches (textView.Text.ToString (), "\\w+")
@@ -61,40 +64,89 @@ namespace UICatalog.Scenarios {
 			}
 			Win.Add (textView);
 
-			var labelMirroringTextView = new Label (textView.Text) {
+			var labelMirroringTextView = new Label () {
 				X = Pos.Right (textView) + 1,
 				Y = Pos.Top (textView),
-				Width = Dim.Fill (1),
-				Height = Dim.Height (textView),
+				Width = Dim.Fill (1) - 1,
+				Height = Dim.Height (textView) - 1,
 			};
 			Win.Add (labelMirroringTextView);
 
-			textView.TextChanged += () => {
+			// Use ContentChanged to detect if the user has typed something in a TextView.
+			// The TextChanged property is only fired if the TextView.Text property is
+			// explicitly set
+			textView.ContentsChanged += (a) => {
+				labelMirroringTextView.Enabled = !labelMirroringTextView.Enabled;
 				labelMirroringTextView.Text = textView.Text;
 			};
 
-			var btnMultiline = new Button ("Toggle Multiline") {
-				X = Pos.Right (textView) + 1,
-				Y = Pos.Top (textView) + 1
+			// By default TextView is a multi-line control. It can be forced to 
+			// single-line mode.
+			var chxMultiline = new CheckBox ("Multiline") {
+				X = Pos.Left (textView),
+				Y = Pos.Bottom (textView), 
+				Checked = true
+			};
+			chxMultiline.Toggled += (b) => textView.Multiline = b;
+			Win.Add (chxMultiline);
+
+			var chxWordWrap = new CheckBox ("Word Wrap") {
+				X = Pos.Right (chxMultiline) + 2,
+				Y = Pos.Top (chxMultiline)
+			};
+			chxWordWrap.Toggled += (b) => textView.WordWrap = b;
+			Win.Add (chxWordWrap);
+
+			// TextView captures Tabs (so users can enter /t into text) by default;
+			// This means using Tab to navigate doesn't work by default. This shows
+			// how to turn tab capture off.
+			var chxCaptureTabs = new CheckBox ("Capture Tabs") {
+				X = Pos.Right (chxWordWrap) + 2,
+				Y = Pos.Top (chxWordWrap),
+				Checked = true
 			};
-			btnMultiline.Clicked += () => textView.Multiline = !textView.Multiline;
-			Win.Add (btnMultiline);
 
-			// BUGBUG: 531 - TAB doesn't go to next control from HexView
-			var hexView = new HexView (new System.IO.MemoryStream (Encoding.ASCII.GetBytes (s))) {
+			Key keyTab = textView.GetKeyFromCommand (Command.Tab);
+			Key keyBackTab = textView.GetKeyFromCommand (Command.BackTab);
+			chxCaptureTabs.Toggled += (b) => { 
+				if (b) {
+					textView.AddKeyBinding (keyTab, Command.Tab);
+					textView.AddKeyBinding (keyBackTab, Command.BackTab);
+				} else {
+					textView.ClearKeybinding (keyTab);
+					textView.ClearKeybinding (keyBackTab);
+				}
+				textView.WordWrap = b; 
+			};
+			Win.Add (chxCaptureTabs);
+
+			var hexEditor = new HexView (new MemoryStream (Encoding.UTF8.GetBytes ("HexEditor Unicode that shouldn't 𝔹Aℝ𝔽!"))) {
 				X = 1,
-				Y = Pos.Bottom (textView) + 1,
-				Width = Dim.Fill (1),
+				Y = Pos.Bottom (chxMultiline) + 1,
+				Width = Dim.Percent (50) - 1,
 				Height = Dim.Percent (30),
-				//ColorScheme = Colors.Dialog
 			};
-			Win.Add (hexView);
+			Win.Add (hexEditor);
+
+			var labelMirroringHexEditor = new Label () {
+				X = Pos.Right (hexEditor) + 1,
+				Y = Pos.Top (hexEditor),
+				Width = Dim.Fill (1) - 1,
+				Height = Dim.Height (hexEditor) - 1,
+			};
+			var array = ((MemoryStream)hexEditor.Source).ToArray ();
+			labelMirroringHexEditor.Text = Encoding.UTF8.GetString (array, 0, array.Length);
+			hexEditor.Edited += (kv) => {
+				hexEditor.ApplyEdits ();
+				var array = ((MemoryStream)hexEditor.Source).ToArray (); 
+				labelMirroringHexEditor.Text = Encoding.UTF8.GetString (array, 0, array.Length);
+			};
+			Win.Add (labelMirroringHexEditor);
 
 			var dateField = new DateField (System.DateTime.Now) {
 				X = 1,
-				Y = Pos.Bottom (hexView) + 1,
+				Y = Pos.Bottom (hexEditor) + 1,
 				Width = 20,
-				//ColorScheme = Colors.Dialog,
 				IsShortFormat = false
 			};
 			Win.Add (dateField);
@@ -113,9 +165,8 @@ namespace UICatalog.Scenarios {
 
 			_timeField = new TimeField (DateTime.Now.TimeOfDay) {
 				X = Pos.Right (labelMirroringDateField) + 5,
-				Y = Pos.Bottom (hexView) + 1,
+				Y = Pos.Bottom (hexEditor) + 1,
 				Width = 20,
-				//ColorScheme = Colors.Dialog,
 				IsShortFormat = false
 			};
 			Win.Add (_timeField);
@@ -130,8 +181,8 @@ namespace UICatalog.Scenarios {
 
 			_timeField.TimeChanged += TimeChanged;
 
-			// MaskedTextProvider
-			var netProviderLabel = new Label (".Net MaskedTextProvider [ 999 000 LLL >LLL| AAA aaa ]") {
+			// MaskedTextProvider - uses .NET MaskedTextProvider
+			var netProviderLabel = new Label ("NetMaskedTextProvider [ 999 000 LLL >LLL| AAA aaa ]") {
 				X = Pos.Left (dateField),
 				Y = Pos.Bottom (dateField) + 1
 			};
@@ -141,13 +192,13 @@ namespace UICatalog.Scenarios {
 
 			var netProviderField = new TextValidateField (netProvider) {
 				X = Pos.Right (netProviderLabel) + 1,
-				Y = Pos.Y (netProviderLabel)
+				Y = Pos.Y (netProviderLabel),
 			};
 
 			Win.Add (netProviderField);
 
-			// TextRegexProvider
-			var regexProvider = new Label ("Gui.cs TextRegexProvider [ ^([0-9]?[0-9]?[0-9]|1000)$ ]") {
+			// TextRegexProvider - Regex provider implemented by Terminal.Gui
+			var regexProvider = new Label ("TextRegexProvider [ ^([0-9]?[0-9]?[0-9]|1000)$ ]") {
 				X = Pos.Left (netProviderLabel),
 				Y = Pos.Bottom (netProviderLabel) + 1
 			};

+ 1 - 1
UICatalog/Scenarios/TextFormatterDemo.cs

@@ -42,7 +42,7 @@ namespace UICatalog.Scenarios {
 			blockText.Text = ustring.Make (block.ToString ()); // .Replace(" ", "\u00A0"); // \u00A0 is 'non-breaking space
 			Win.Add (blockText);
 
-			var unicodeCheckBox = new CheckBox ("Unicode", Top.HotKeySpecifier == (Rune)' ') {
+			var unicodeCheckBox = new CheckBox ("Unicode", Application.Top.HotKeySpecifier == (Rune)' ') {
 				X = 0,
 				Y = Pos.Bottom (blockText) + 1,
 			};

+ 2 - 2
UICatalog/Scenarios/TextViewAutocompletePopup.cs

@@ -33,7 +33,7 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Quit", "", () => Quit())
 				})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			textViewTopLeft = new TextView () {
 				Width = width,
@@ -89,7 +89,7 @@ namespace UICatalog.Scenarios {
 				siMultiline = new StatusItem(Key.Null, "", null),
 				siWrap = new StatusItem(Key.Null, "", null)
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			Win.LayoutStarted += Win_LayoutStarted;
 		}

+ 2 - 2
UICatalog/Scenarios/Threading.cs

@@ -96,9 +96,9 @@ namespace UICatalog.Scenarios {
 			void Top_Loaded ()
 			{
 				_btnActionCancel.SetFocus ();
-				Top.Loaded -= Top_Loaded;
+				Application.Top.Loaded -= Top_Loaded;
 			}
-			Top.Loaded += Top_Loaded;
+			Application.Top.Loaded += Top_Loaded;
 		}
 
 		private async void LoadData ()

+ 3 - 3
UICatalog/Scenarios/TreeUseCases.cs

@@ -17,7 +17,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
@@ -31,13 +31,13 @@ namespace UICatalog.Scenarios {
 				}),
 			});
 
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 			});
 
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			// Start with the most basic use case
 			LoadSimpleNodes ();

+ 107 - 78
UICatalog/Scenarios/TreeViewFileSystem.cs

@@ -2,11 +2,12 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Reflection.PortableExecutable;
 using Terminal.Gui;
 using Terminal.Gui.Trees;
 
 namespace UICatalog.Scenarios {
-	[ScenarioMetadata (Name: "TreeViewFileSystem", Description: "Hierarchical file system explorer based on TreeView.")]
+	[ScenarioMetadata (Name: "File System Explorer", Description: "Hierarchical file system explorer demonstrating TreeView.")]
 	[ScenarioCategory ("Controls"), ScenarioCategory ("TreeView"), ScenarioCategory ("Files and IO")]
 	public class TreeViewFileSystem : Scenario {
 
@@ -24,86 +25,97 @@ namespace UICatalog.Scenarios {
 		private MenuItem miUnicodeSymbols;
 		private MenuItem miFullPaths;
 		private MenuItem miLeaveLastRow;
+		private MenuItem miHighlightModelTextOnly;
 		private MenuItem miCustomColors;
 		private MenuItem miCursor;
 		private MenuItem miMultiSelect;
-		private Terminal.Gui.Attribute green;
-		private Terminal.Gui.Attribute red;
+
+		private DetailsFrame detailsFrame;
 
 		public override void Setup ()
 		{
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
-			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Win.Height = Dim.Fill ();
+			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("_Quit", "", () => Quit()),
+					new MenuItem ("_Quit", "CTRL-Q", () => Quit()),
 				}),
 				new MenuBarItem ("_View", new MenuItem [] {
-					miShowLines = new MenuItem ("_ShowLines", "", () => ShowLines()){
+					miFullPaths = new MenuItem ("_Full Paths", "", () => SetFullName()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
+					miMultiSelect = new MenuItem ("_Multi Select", "", () => SetMultiSelect()){Checked = true, CheckType = MenuItemCheckStyle.Checked},
+				}),
+				new MenuBarItem ("_Style", new MenuItem [] {
+					miShowLines = new MenuItem ("_Show Lines", "", () => ShowLines()){
 					Checked = true, CheckType = MenuItemCheckStyle.Checked
 						},
 					null /*separator*/,
-					miPlusMinus = new MenuItem ("_PlusMinusSymbols", "", () => SetExpandableSymbols('+','-')){Checked = true, CheckType = MenuItemCheckStyle.Radio},
-					miArrowSymbols = new MenuItem ("_ArrowSymbols", "", () => SetExpandableSymbols('>','v')){Checked = false, CheckType = MenuItemCheckStyle.Radio},
-					miNoSymbols = new MenuItem ("_NoSymbols", "", () => SetExpandableSymbols(null,null)){Checked = false, CheckType = MenuItemCheckStyle.Radio},
-					miUnicodeSymbols = new MenuItem ("_Unicode", "", () => SetExpandableSymbols('ஹ','﷽')){Checked = false, CheckType = MenuItemCheckStyle.Radio},
+					miPlusMinus = new MenuItem ("_Plus Minus Symbols", "+ -", () => SetExpandableSymbols('+','-')){Checked = true, CheckType = MenuItemCheckStyle.Radio},
+					miArrowSymbols = new MenuItem ("_Arrow Symbols", "> v", () => SetExpandableSymbols('>','v')){Checked = false, CheckType = MenuItemCheckStyle.Radio},
+					miNoSymbols = new MenuItem ("_No Symbols", "", () => SetExpandableSymbols(null,null)){Checked = false, CheckType = MenuItemCheckStyle.Radio},
+					miUnicodeSymbols = new MenuItem ("_Unicode", "ஹ ﷽", () => SetExpandableSymbols('ஹ','﷽')){Checked = false, CheckType = MenuItemCheckStyle.Radio},
+					null /*separator*/,
+					miColoredSymbols = new MenuItem ("_Colored Symbols", "", () => ShowColoredExpandableSymbols()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
+					miInvertSymbols = new MenuItem ("_Invert Symbols", "", () => InvertExpandableSymbols()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
+					null /*separator*/,
+					miLeaveLastRow = new MenuItem ("_Leave Last Row", "", () => SetLeaveLastRow()){Checked = true, CheckType = MenuItemCheckStyle.Checked},
+					miHighlightModelTextOnly = new MenuItem ("_Highlight Model Text Only", "", () => SetCheckHighlightModelTextOnly()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
+					null /*separator*/,
+					miCustomColors = new MenuItem ("C_ustom Colors Hidden Files", "Yellow/Red", () => SetCustomColors()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
 					null /*separator*/,
-					miColoredSymbols = new MenuItem ("_ColoredSymbols", "", () => ShowColoredExpandableSymbols()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
-					miInvertSymbols = new MenuItem ("_InvertSymbols", "", () => InvertExpandableSymbols()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
-					miFullPaths = new MenuItem ("_FullPaths", "", () => SetFullName()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
-					miLeaveLastRow = new MenuItem ("_LeaveLastRow", "", () => SetLeaveLastRow()){Checked = true, CheckType = MenuItemCheckStyle.Checked},
-					miCustomColors = new MenuItem ("C_ustomColors", "", () => SetCustomColors()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
 					miCursor = new MenuItem ("Curs_or (MultiSelect only)", "", () => SetCursor()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
-					miMultiSelect = new MenuItem ("_MultiSelect", "", () => SetMultiSelect()){Checked = true, CheckType = MenuItemCheckStyle.Checked},
 				}),
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
-			var statusBar = new StatusBar (new StatusItem [] {
-				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
-			});
-			Top.Add (statusBar);
-
-			var lblFiles = new Label ("File Tree:") {
+			treeViewFiles = new TreeView<FileSystemInfo> () {
 				X = 0,
-				Y = 1
+				Y = 0,
+				Width = Dim.Percent (50),
+				Height = Dim.Fill (),
 			};
-			Win.Add (lblFiles);
 
-			treeViewFiles = new TreeView<FileSystemInfo> () {
-				X = 0,
-				Y = Pos.Bottom (lblFiles),
+			detailsFrame = new DetailsFrame () {
+				X = Pos.Right (treeViewFiles),
+				Y = 0,
 				Width = Dim.Fill (),
 				Height = Dim.Fill (),
 			};
 
-			treeViewFiles.ObjectActivated += TreeViewFiles_ObjectActivated;
+			Win.Add (detailsFrame);
 			treeViewFiles.MouseClick += TreeViewFiles_MouseClick;
 			treeViewFiles.KeyPress += TreeViewFiles_KeyPress;
+			treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged;
 
 			SetupFileTree ();
 
 			Win.Add (treeViewFiles);
+			treeViewFiles.GoToFirst ();
+			treeViewFiles.Expand ();
 
 			SetupScrollBar ();
 
-			green = Application.Driver.MakeAttribute (Color.Green, Color.Blue);
-			red = Application.Driver.MakeAttribute (Color.Red, Color.Blue);
+			treeViewFiles.SetFocus ();
+
+		}
+
+		private void TreeViewFiles_SelectionChanged (object sender, SelectionChangedEventArgs<FileSystemInfo> e)
+		{
+			ShowPropertiesOf (e.NewValue);
 		}
 
 		private void TreeViewFiles_KeyPress (View.KeyEventEventArgs obj)
 		{
-			if(obj.KeyEvent.Key == (Key.R | Key.CtrlMask)) {
+			if (obj.KeyEvent.Key == (Key.R | Key.CtrlMask)) {
 
 				var selected = treeViewFiles.SelectedObject;
-				
+
 				// nothing is selected
 				if (selected == null)
 					return;
-				
+
 				var location = treeViewFiles.GetObjectRow (selected);
 
 				//selected object is offscreen or somehow not found
@@ -120,9 +132,9 @@ namespace UICatalog.Scenarios {
 		private void TreeViewFiles_MouseClick (View.MouseEventArgs obj)
 		{
 			// if user right clicks
-			if (obj.MouseEvent.Flags.HasFlag(MouseFlags.Button3Clicked)) {
+			if (obj.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
 
-				var rightClicked = treeViewFiles.GetObjectOnRow ( obj.MouseEvent.Y);
+				var rightClicked = treeViewFiles.GetObjectOnRow (obj.MouseEvent.Y);
 
 				// nothing was clicked
 				if (rightClicked == null)
@@ -141,33 +153,50 @@ namespace UICatalog.Scenarios {
 			menu.Position = screenPoint;
 
 			menu.MenuItems = new MenuBarItem (new [] { new MenuItem ("Properties", null, () => ShowPropertiesOf (forObject)) });
-			
-			Application.MainLoop.Invoke(menu.Show);
-		}
 
-		private void ShowPropertiesOf (FileSystemInfo fileSystemInfo)
-		{
-			if (fileSystemInfo is FileInfo f) {
-				System.Text.StringBuilder sb = new System.Text.StringBuilder ();
-				sb.AppendLine ($"Path:{f.DirectoryName}");
-				sb.AppendLine ($"Size:{f.Length:N0} bytes");
-				sb.AppendLine ($"Modified:{ f.LastWriteTime}");
-				sb.AppendLine ($"Created:{ f.CreationTime}");
-
-				MessageBox.Query (f.Name, sb.ToString (), "Close");
-			}
+			Application.MainLoop.Invoke (menu.Show);
+		}
 
-			if (fileSystemInfo is DirectoryInfo dir) {
+		class DetailsFrame : FrameView {
+			private FileSystemInfo fileInfo;
 
-				System.Text.StringBuilder sb = new System.Text.StringBuilder ();
-				sb.AppendLine ($"Path:{dir.Parent?.FullName}");
-				sb.AppendLine ($"Modified:{ dir.LastWriteTime}");
-				sb.AppendLine ($"Created:{ dir.CreationTime}");
+			public DetailsFrame ()
+			{
+				Title = "Details";
+				Visible = true;
+				CanFocus = true;				
+			}
 
-				MessageBox.Query (dir.Name, sb.ToString (), "Close");
+			public FileSystemInfo FileInfo {
+				get => fileInfo; set {
+					fileInfo = value;
+					System.Text.StringBuilder sb = null;
+					if (fileInfo is FileInfo f) {
+						Title = $"File: {f.Name}";
+						sb = new System.Text.StringBuilder ();
+						sb.AppendLine ($"Path:\n {f.FullName}\n");
+						sb.AppendLine ($"Size:\n {f.Length:N0} bytes\n");
+						sb.AppendLine ($"Modified:\n {f.LastWriteTime}\n");
+						sb.AppendLine ($"Created:\n {f.CreationTime}");
+					}
+
+					if (fileInfo is DirectoryInfo dir) {
+						Title = $"Directory: {dir.Name}";
+						sb = new System.Text.StringBuilder ();
+						sb.AppendLine ($"Path:\n {dir?.FullName}\n");
+						sb.AppendLine ($"Modified:\n {dir.LastWriteTime}\n");
+						sb.AppendLine ($"Created:\n {dir.CreationTime}\n");
+					}
+					Text = sb.ToString ();
+				}
 			}
 		}
 
+		private void ShowPropertiesOf (FileSystemInfo fileSystemInfo)
+		{
+			detailsFrame.FileInfo = fileSystemInfo;
+		}
+
 		private void SetupScrollBar ()
 		{
 			// When using scroll bar leave the last row of the control free (for over-rendering with scroll bar)
@@ -218,11 +247,6 @@ namespace UICatalog.Scenarios {
 			treeViewFiles.AddObjects (DriveInfo.GetDrives ().Select (d => d.RootDirectory));
 		}
 
-		private void TreeViewFiles_ObjectActivated (ObjectActivatedEventArgs<FileSystemInfo> obj)
-		{
-			ShowPropertiesOf (obj.ActivatedObject);
-		}
-
 		private void ShowLines ()
 		{
 			miShowLines.Checked = !miShowLines.Checked;
@@ -266,6 +290,7 @@ namespace UICatalog.Scenarios {
 			} else {
 				treeViewFiles.AspectGetter = (f) => f.Name;
 			}
+			treeViewFiles.SetNeedsDisplay ();
 		}
 
 		private void SetLeaveLastRow ()
@@ -273,41 +298,45 @@ namespace UICatalog.Scenarios {
 			miLeaveLastRow.Checked = !miLeaveLastRow.Checked;
 			treeViewFiles.Style.LeaveLastRow = miLeaveLastRow.Checked;
 		}
-		private void SetCursor()
+		private void SetCursor ()
 		{
 			miCursor.Checked = !miCursor.Checked;
 			treeViewFiles.DesiredCursorVisibility = miCursor.Checked ? CursorVisibility.Default : CursorVisibility.Invisible;
 		}
-		private void SetMultiSelect()
+		private void SetMultiSelect ()
 		{
 			miMultiSelect.Checked = !miMultiSelect.Checked;
 			treeViewFiles.MultiSelect = miMultiSelect.Checked;
 		}
-		
 
-		private void SetCustomColors()
+
+		private void SetCustomColors ()
 		{
-			var yellow = new ColorScheme
-			{
-				Focus = new Terminal.Gui.Attribute(Color.BrightYellow,treeViewFiles.ColorScheme.Focus.Background),
-				Normal = new Terminal.Gui.Attribute (Color.BrightYellow,treeViewFiles.ColorScheme.Normal.Background),
+			var hidden = new ColorScheme {
+				Focus = new Terminal.Gui.Attribute (Color.BrightRed, treeViewFiles.ColorScheme.Focus.Background),
+				Normal = new Terminal.Gui.Attribute (Color.BrightYellow, treeViewFiles.ColorScheme.Normal.Background),
 			};
 
 			miCustomColors.Checked = !miCustomColors.Checked;
 
-			if(miCustomColors.Checked)
-			{
-				treeViewFiles.ColorGetter = (m)=>
-				{
-					return m is DirectoryInfo ? yellow : null;
+			if (miCustomColors.Checked) {
+				treeViewFiles.ColorGetter = (m) => {
+					if (m is DirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) return hidden;
+					if (m is FileInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) return hidden;
+					return null;
 				};
-			}
-			else
-			{
+			} else {
 				treeViewFiles.ColorGetter = null;
 			}
+			treeViewFiles.SetNeedsDisplay ();
 		}
 
+		private void SetCheckHighlightModelTextOnly ()
+		{
+			treeViewFiles.Style.HighlightModelTextOnly = !treeViewFiles.Style.HighlightModelTextOnly;
+			miHighlightModelTextOnly.Checked = treeViewFiles.Style.HighlightModelTextOnly;
+			treeViewFiles.SetNeedsDisplay ();
+		}
 
 		private IEnumerable<FileSystemInfo> GetChildren (FileSystemInfo model)
 		{

+ 2 - 2
UICatalog/Scenarios/Unicode.cs

@@ -34,14 +34,14 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Paste", "", null)
 				})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Выход", () => Application.RequestStop()),
 				new StatusItem (Key.Unknown, "~F2~ Создать", null),
 				new StatusItem(Key.Unknown, "~F3~ Со_хранить", null),
 			});
-			Top.Add (statusBar);
+			Application.Top.Add (statusBar);
 
 			var label = new Label ("Label:") { X = 0, Y = 1 };
 			Win.Add (label);

+ 38 - 38
UICatalog/Scenarios/WindowsAndFrameViews.cs

@@ -3,57 +3,35 @@ using System.Linq;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios {
-	[ScenarioMetadata (Name: "Windows & FrameViews", Description: "Shows Windows, sub-Windows, and FrameViews.")]
+	[ScenarioMetadata (Name: "Windows & FrameViews", Description: "Stress Tests Windows, sub-Windows, and FrameViews.")]
 	[ScenarioCategory ("Layout")]
 	public class WindowsAndFrameViews : Scenario {
-		public override void Init (Toplevel top, ColorScheme colorScheme)
-		{
-			Application.Init ();
-
-			Top = top != null ? top : Application.Top;
-		}
-
-		public override void RequestStop ()
-		{
-			base.RequestStop ();
-		}
-
-		public override void Run ()
-		{
-			base.Run ();
-		}
-
+		
 		public override void Setup ()
 		{
 			static int About ()
 			{
 				return MessageBox.Query ("About UI Catalog", "UI Catalog is a comprehensive sample library for Terminal.Gui", "Ok");
-
-				//var about = new Window (new Rect (0, 0, 50, 10), "About UI catalog", 0) {
-				//	X = Pos.Center (),
-				//	Y = Pos.Center (),
-				//	Width = 50,
-				//	Height = 10,
-				//	LayoutStyle = LayoutStyle.Computed,
-				//	ColorScheme = Colors.Error,
-
-				//};
-
-				//Application.Run (about);
-				//return 0;
-
 			}
 
 			int margin = 2;
 			int padding = 1;
 			int contentHeight = 7;
+
+			// list of Windows we create
 			var listWin = new List<View> ();
+
+			//Ignore the Win that UI Catalog created and create a new one
+			Application.Top.Remove (Win);
+			Win?.Dispose ();
+
 			Win = new Window ($"{listWin.Count} - Scenario: {GetName ()}", padding) {
 				X = Pos.Center (),
 				Y = 1,
-				Width = Dim.Fill (10),
-				Height = Dim.Percent (15)
+				Width = Dim.Fill (15),
+				Height = 10
 			};
+
 			Win.ColorScheme = Colors.Dialog;
 			var paddingButton = new Button ($"Padding of container is {padding}") {
 				X = Pos.Center (),
@@ -67,9 +45,18 @@ namespace UICatalog.Scenarios {
 				Y = Pos.AnchorEnd (1),
 				ColorScheme = Colors.Error
 			});
-			Top.Add (Win);
+			Application.Top.Add (Win);
+			
+			// add it to our list
 			listWin.Add (Win);
 
+			// create 3 more Windows in a loop, adding them Application.Top
+			// Each with a
+			//	button
+			//  sub Window with
+			//		TextField
+			//	sub FrameView with
+			// 
 			for (var i = 0; i < 3; i++) {
 				Window win = null;
 				win = new Window ($"{listWin.Count} - Window Loop - padding = {i}", i) {
@@ -114,11 +101,22 @@ namespace UICatalog.Scenarios {
 				});
 				win.Add (frameView);
 
-				Top.Add (win);
+				Application.Top.Add (win);
 				listWin.Add (win);
 			}
 
-
+			// Add a FrameView (frame) to Application.Top
+			// Position it at Bottom, using the list of Windows we created above.
+			// Fill it with
+			//   a label
+			//   a SubWindow containing (subWinofFV)
+			//      a TextField
+			//	    two checkboxes
+			//   a Sub FrameView containing (subFrameViewofFV)
+			//      a TextField
+			//      two CheckBoxes	
+			//   a checkbox
+			//   a checkbox
 			FrameView frame = null;
 			frame = new FrameView ($"This is a FrameView") {
 				X = margin,
@@ -176,8 +174,10 @@ namespace UICatalog.Scenarios {
 
 			frame.Add (subFrameViewofFV);
 
-			Top.Add (frame);
+			Application.Top.Add (frame);
 			listWin.Add (frame);
+
+			Application.Top.ColorScheme = Colors.Base;
 		}
 	}
 }

+ 4 - 5
UICatalog/Scenarios/WizardAsView.cs

@@ -10,10 +10,9 @@ namespace UICatalog.Scenarios {
 	[ScenarioCategory ("Wizards")]
 	public class WizardAsView : Scenario {
 
-		public override void Init (Toplevel top, ColorScheme colorScheme)
+		public override void Init (ColorScheme colorScheme)
 		{
 			Application.Init ();
-			Top = Application.Top;
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
@@ -22,7 +21,7 @@ namespace UICatalog.Scenarios {
 					new MenuItem ("_Shutdown Server...", "", () => MessageBox.Query ("Wizaard", "Are you sure you want to cancel setup and shutdown?", "Ok", "Cancel")),
 				})
 			});
-			Top.Add (menu);
+			Application.Top.Add (menu);
 
 			// No need for a Title because the border is disabled
 			var wizard = new Wizard () {
@@ -94,8 +93,8 @@ namespace UICatalog.Scenarios {
 			wizard.AddStep (lastStep);
 			lastStep.HelpText = "The wizard is complete!\n\nPress the Finish button to continue.\n\nPressing Esc will cancel.";
 
-			Top.Add (wizard);
-			Application.Run (Top);
+			Application.Top.Add (wizard);
+			Application.Run (Application.Top);
 		}
 
 		public override void Run ()

+ 2 - 2
UICatalog/Scenarios/Wizards.cs

@@ -73,9 +73,9 @@ namespace UICatalog.Scenarios {
 			void Top_Loaded ()
 			{
 				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + 2;
-				Top.Loaded -= Top_Loaded;
+				Application.Top.Loaded -= Top_Loaded;
 			}
-			Top.Loaded += Top_Loaded;
+			Application.Top.Loaded += Top_Loaded;
 
 			label = new Label ("Action:") {
 				X = Pos.Center (),

+ 402 - 390
UICatalog/UICatalog.cs

@@ -44,35 +44,7 @@ namespace UICatalog {
 	/// <summary>
 	/// UI Catalog is a comprehensive sample app and scenario library for <see cref="Terminal.Gui"/>
 	/// </summary>
-	public class UICatalogApp {
-		private static int _nameColumnWidth;
-		private static FrameView _leftPane;
-		private static List<string> _categories;
-		private static ListView _categoryListView;
-		private static FrameView _rightPane;
-		private static List<Scenario> _scenarios;
-		private static ListView _scenarioListView;
-		private static StatusBar _statusBar;
-		private static StatusItem _capslock;
-		private static StatusItem _numlock;
-		private static StatusItem _scrolllock;
-
-		// If set, holds the scenario the user selected
-		private static Scenario _selectedScenario = null;
-		
-		private static bool _useSystemConsole = false;
-		private static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
-		private static bool _heightAsBuffer = false;
-		private static bool _isFirstRunning = true;
-
-		// When a scenario is run, the main app is killed. These items
-		// are therefore cached so that when the scenario exits the
-		// main app UI can be restored to previous state
-		private static int _cachedScenarioIndex = 0;
-		private static int _cachedCategoryIndex = 0;
-
-		private static StringBuilder _aboutMessage;
-
+	class UICatalogApp {
 		static void Main (string [] args)
 		{
 			Console.OutputEncoding = Encoding.Default;
@@ -82,17 +54,22 @@ namespace UICatalog {
 			}
 
 			_scenarios = Scenario.GetScenarios ();
+			_categories = Scenario.GetAllCategories ();
+			_nameColumnWidth = _scenarios.OrderByDescending (s => s.GetName ().Length).FirstOrDefault ().GetName ().Length;
 
 			if (args.Length > 0 && args.Contains ("-usc")) {
 				_useSystemConsole = true;
 				args = args.Where (val => val != "-usc").ToArray ();
 			}
+
+			// If a Scenario name has been provided on the commandline
+			// run it and exit when done.
 			if (args.Length > 0) {
 				var item = _scenarios.FindIndex (s => s.GetName ().Equals (args [0], StringComparison.OrdinalIgnoreCase));
 				_selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ());
 				Application.UseSystemConsole = _useSystemConsole;
 				Application.Init ();
-				_selectedScenario.Init (Application.Top, _colorScheme);
+				_selectedScenario.Init (_colorScheme);
 				_selectedScenario.Setup ();
 				_selectedScenario.Run ();
 				_selectedScenario = null;
@@ -113,43 +90,19 @@ namespace UICatalog {
 			_aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui");
 
 			Scenario scenario;
-			while ((scenario = SelectScenario ()) != null) {
-#if DEBUG_IDISPOSABLE
-				// Validate there are no outstanding Responder-based instances 
-				// after a scenario was selected to run. This proves the main UI Catalog
-				// 'app' closed cleanly.
-				foreach (var inst in Responder.Instances) {
-					Debug.Assert (inst.WasDisposed);
-				}
-				Responder.Instances.Clear ();
-#endif
-
-				scenario.Init (Application.Top, _colorScheme);
+			while ((scenario = RunUICatalogTopLevel ()) != null) {
+				VerifyObjectsWereDisposed ();
+				scenario.Init (_colorScheme);
 				scenario.Setup ();
 				scenario.Run ();
 
 				// This call to Application.Shutdown brackets the Application.Init call
-				// made by Scenario.Init()
+				// made by Scenario.Init() above
 				Application.Shutdown ();
 
-#if DEBUG_IDISPOSABLE
-				// After the scenario runs, validate all Responder-based instances
-				// were disposed. This proves the scenario 'app' closed cleanly.
-				foreach (var inst in Responder.Instances) {
-					Debug.Assert (inst.WasDisposed);
-				}
-				Responder.Instances.Clear ();
-#endif
-			}
-
-#if DEBUG_IDISPOSABLE
-			// This proves that when the user exited the UI Catalog app
-			// it cleaned up properly.
-			foreach (var inst in Responder.Instances) {
-				Debug.Assert (inst.WasDisposed);
+				VerifyObjectsWereDisposed ();
 			}
-			Responder.Instances.Clear ();
-#endif
+			VerifyObjectsWereDisposed ();
 		}
 
 		/// <summary>
@@ -158,389 +111,448 @@ namespace UICatalog {
 		/// When the Scenario exits, this function exits.
 		/// </summary>
 		/// <returns></returns>
-		private static Scenario SelectScenario ()
+		static Scenario RunUICatalogTopLevel ()
 		{
 			Application.UseSystemConsole = _useSystemConsole;
-			Application.Init ();
-			if (_colorScheme == null) {
-				// `Colors` is not initilized until the ConsoleDriver is loaded by 
-				// Application.Init. Set it only the first time though so it is
-				// preserved between running multiple Scenarios
-				_colorScheme = Colors.Base;
-			}
-			Application.HeightAsBuffer = _heightAsBuffer;
-
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("_Quit", "Quit UI Catalog", () => Application.RequestStop(), null, null, Key.Q | Key.CtrlMask)
-				}),
-				new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()),
-				new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems()),
-				new MenuBarItem ("_Help", new MenuItem [] {
-					new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui/articles/overview.html"), null, null, Key.F1),
-					new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, Key.F2),
-					new MenuItem ("_About...",
-						"About UI Catalog", () =>  MessageBox.Query ("About UI Catalog", _aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A),
-				}),
-			});
-
-			_leftPane = new FrameView ("Categories") {
-				X = 0,
-				Y = 1, // for menu
-				Width = 25,
-				Height = Dim.Fill (1),
-				CanFocus = true,
-				Shortcut = Key.CtrlMask | Key.C
-			};
-			_leftPane.Title = $"{_leftPane.Title} ({_leftPane.ShortcutTag})";
-			_leftPane.ShortcutAction = () => _leftPane.SetFocus ();
-
-			_categories = Scenario.GetAllCategories ();
-			_categoryListView = new ListView (_categories) {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (0),
-				Height = Dim.Fill (0),
-				AllowsMarking = false,
-				CanFocus = true,
-			};
-			_categoryListView.OpenSelectedItem += (a) => {
-				_rightPane.SetFocus ();
-			};
-			_categoryListView.SelectedItemChanged += CategoryListView_SelectedChanged;
-			_leftPane.Add (_categoryListView);
-
-			_rightPane = new FrameView ("Scenarios") {
-				X = 25,
-				Y = 1, // for menu
-				Width = Dim.Fill (),
-				Height = Dim.Fill (1),
-				CanFocus = true,
-				Shortcut = Key.CtrlMask | Key.S
-			};
-			_rightPane.Title = $"{_rightPane.Title} ({_rightPane.ShortcutTag})";
-			_rightPane.ShortcutAction = () => _rightPane.SetFocus ();
-
-			_nameColumnWidth = _scenarios.OrderByDescending (s => s.GetName ().Length).FirstOrDefault ().GetName ().Length;
-
-			_scenarioListView = new ListView () {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (0),
-				Height = Dim.Fill (0),
-				AllowsMarking = false,
-				CanFocus = true,
-			};
-
-			_scenarioListView.OpenSelectedItem += _scenarioListView_OpenSelectedItem;
-			_rightPane.Add (_scenarioListView);
-
-			_capslock = new StatusItem (Key.CharMask, "Caps", null);
-			_numlock = new StatusItem (Key.CharMask, "Num", null);
-			_scrolllock = new StatusItem (Key.CharMask, "Scroll", null);
-
-			_statusBar = new StatusBar () {
-				Visible = true,
-			};
-			_statusBar.Items = new StatusItem [] {
-				_capslock,
-				_numlock,
-				_scrolllock,
-				new StatusItem(Key.Q | Key.CtrlMask, "~CTRL-Q~ Quit", () => {
-					if (_selectedScenario is null){
-						// This causes GetScenarioToRun to return null
-						_selectedScenario = null;
-						Application.RequestStop();
-					} else {
-						_selectedScenario.RequestStop();
-					}
-				}),
-				new StatusItem(Key.F10, "~F10~ Hide/Show Status Bar", () => {
-					_statusBar.Visible = !_statusBar.Visible;
-					_leftPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
-					_rightPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
-					Application.Top.LayoutSubviews();
-					Application.Top.SetChildNeedsDisplay();
-				}),
-				new StatusItem (Key.CharMask, Application.Driver.GetType ().Name, null),
-			};
-
-			Application.Top.ColorScheme = _colorScheme;
-			Application.Top.KeyDown += KeyDownHandler;
-			Application.Top.Add (menu);
-			Application.Top.Add (_leftPane);
-			Application.Top.Add (_rightPane);
-			Application.Top.Add (_statusBar);
-
-			void TopHandler ()
-			{
-				if (_selectedScenario != null) {
-					_selectedScenario = null;
-					_isFirstRunning = false;
-				}
-				if (!_isFirstRunning) {
-					_rightPane.SetFocus ();
-				}
-				Application.Top.Loaded -= TopHandler;
-			}
-			Application.Top.Loaded += TopHandler;
-
-			// Restore previous selections
-			_categoryListView.SelectedItem = _cachedCategoryIndex;
-			_scenarioListView.SelectedItem = _cachedScenarioIndex;
 
 			// 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.Run (Application.Top);
+			Application.Init ();
+			Application.Run<UICatalogTopLevel> ();
 			Application.Shutdown ();
 
 			return _selectedScenario;
 		}
 
+		static List<Scenario> _scenarios;
+		static List<string> _categories;
+		static int _nameColumnWidth;
+		// When a scenario is run, the main app is killed. These items
+		// are therefore cached so that when the scenario exits the
+		// main app UI can be restored to previous state
+		static int _cachedScenarioIndex = 0;
+		static int _cachedCategoryIndex = 0;
+		static StringBuilder _aboutMessage;
+
+		// If set, holds the scenario the user selected
+		static Scenario _selectedScenario = null;
+
+		static bool _useSystemConsole = false;
+		static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
+		static bool _heightAsBuffer = false;
+		static bool _isFirstRunning = true;
+		static ColorScheme _colorScheme;
 
 		/// <summary>
-		/// Launches the selected scenario, setting the global _selectedScenario
+		/// This is the main UI Catalog app view. It is run fresh when the app loads (if a Scenario has not been passed on 
+		/// the command line) and each time a Scenario ends.
 		/// </summary>
-		/// <param name="e"></param>
-		private static void _scenarioListView_OpenSelectedItem (EventArgs e)
-		{
-			if (_selectedScenario is null) {
-				// Save selected item state
-				_cachedCategoryIndex = _categoryListView.SelectedItem;
-				_cachedScenarioIndex = _scenarioListView.SelectedItem;
-				// Create new instance of scenario (even though Scenarios contains instances)
-				_selectedScenario = (Scenario)Activator.CreateInstance (_scenarioListView.Source.ToList () [_scenarioListView.SelectedItem].GetType ());
-
-				// Tell the main app to stop
-				Application.RequestStop ();
-			}
-		}
+		class UICatalogTopLevel : Toplevel {
+			public MenuItem miIsMouseDisabled;
+			public MenuItem miHeightAsBuffer;
+	    
+			public FrameView LeftPane;
+			public ListView CategoryListView;
+			public FrameView RightPane;
+			public ListView ScenarioListView;
+	    
+			public StatusItem Capslock;
+			public StatusItem Numlock;
+			public StatusItem Scrolllock;
+			public StatusItem DriverName;
+
+			public UICatalogTopLevel ()
+			{
+				ColorScheme = _colorScheme;
+				MenuBar = new MenuBar (new MenuBarItem [] {
+					new MenuBarItem ("_File", new MenuItem [] {
+						new MenuItem ("_Quit", "Quit UI Catalog", () => RequestStop(), null, null, Key.Q | Key.CtrlMask)
+					}),
+					new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()),
+					new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems()),
+					new MenuBarItem ("_Help", new MenuItem [] {
+						new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui/articles/overview.html"), null, null, Key.F1),
+						new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, Key.F2),
+						new MenuItem ("_About...",
+							"About UI Catalog", () =>  MessageBox.Query ("About UI Catalog", _aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A),
+					}),
+				});
+
+				Capslock = new StatusItem (Key.CharMask, "Caps", null);
+				Numlock = new StatusItem (Key.CharMask, "Num", null);
+				Scrolllock = new StatusItem (Key.CharMask, "Scroll", null);
+				DriverName = new StatusItem (Key.CharMask, "Driver:", null);
+
+				StatusBar = new StatusBar () {
+					Visible = true,
+				};
+				StatusBar.Items = new StatusItem [] {
+					Capslock,
+					Numlock,
+					Scrolllock,
+					new StatusItem(Key.Q | Key.CtrlMask, "~CTRL-Q~ Quit", () => {
+						if (_selectedScenario is null){
+							// This causes GetScenarioToRun to return null
+							_selectedScenario = null;
+							RequestStop();
+						} else {
+							_selectedScenario.RequestStop();
+						}
+					}),
+					new StatusItem(Key.F10, "~F10~ Hide/Show Status Bar", () => {
+						StatusBar.Visible = !StatusBar.Visible;
+						LeftPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
+						RightPane.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
+						LayoutSubviews();
+						SetChildNeedsDisplay();
+					}),
+					DriverName,
+				};
 
-		static List<MenuItem []> CreateDiagnosticMenuItems ()
-		{
-			List<MenuItem []> menuItems = new List<MenuItem []> ();
-			menuItems.Add (CreateDiagnosticFlagsMenuItems ());
-			menuItems.Add (new MenuItem [] { null });
-			menuItems.Add (CreateSizeStyle ());
-			menuItems.Add (CreateDisabledEnabledMouse ());
-			menuItems.Add (CreateKeybindings ());
-			return menuItems;
-		}
+				LeftPane = new FrameView ("Categories") {
+					X = 0,
+					Y = 1, // for menu
+					Width = 25,
+					Height = Dim.Fill (1),
+					CanFocus = true,
+					Shortcut = Key.CtrlMask | Key.C
+				};
+				LeftPane.Title = $"{LeftPane.Title} ({LeftPane.ShortcutTag})";
+				LeftPane.ShortcutAction = () => LeftPane.SetFocus ();
+
+				CategoryListView = new ListView (_categories) {
+					X = 0,
+					Y = 0,
+					Width = Dim.Fill (0),
+					Height = Dim.Fill (0),
+					AllowsMarking = false,
+					CanFocus = true,
+				};
+				CategoryListView.OpenSelectedItem += (a) => {
+					RightPane.SetFocus ();
+				};
+				CategoryListView.SelectedItemChanged += CategoryListView_SelectedChanged;
+				LeftPane.Add (CategoryListView);
+
+				RightPane = new FrameView ("Scenarios") {
+					X = 25,
+					Y = 1, // for menu
+					Width = Dim.Fill (),
+					Height = Dim.Fill (1),
+					CanFocus = true,
+					Shortcut = Key.CtrlMask | Key.S
+				};
+				RightPane.Title = $"{RightPane.Title} ({RightPane.ShortcutTag})";
+				RightPane.ShortcutAction = () => RightPane.SetFocus ();
+
+				ScenarioListView = new ListView () {
+					X = 0,
+					Y = 0,
+					Width = Dim.Fill (0),
+					Height = Dim.Fill (0),
+					AllowsMarking = false,
+					CanFocus = true,
+				};
 
-		private static MenuItem [] CreateDisabledEnabledMouse ()
-		{
-			List<MenuItem> menuItems = new List<MenuItem> ();
-			var item = new MenuItem ();
-			item.Title = "_Disable Mouse";
-			item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
-			item.CheckType |= MenuItemCheckStyle.Checked;
-			item.Checked = Application.IsMouseDisabled;
-			item.Action += () => {
-				item.Checked = Application.IsMouseDisabled = !item.Checked;
-			};
-			menuItems.Add (item);
-
-			return menuItems.ToArray ();
-		}
-		private static MenuItem [] CreateKeybindings ()
-		{
+				ScenarioListView.OpenSelectedItem += ScenarioListView_OpenSelectedItem;
+				RightPane.Add (ScenarioListView);
 
-			List<MenuItem> menuItems = new List<MenuItem> ();
-			var item = new MenuItem ();
-			item.Title = "_Key Bindings";
-			item.Help = "Change which keys do what";
-			item.Action += () => {
-				var dlg = new KeyBindingsDialog ();
-				Application.Run (dlg);
-			};
+				KeyDown += KeyDownHandler;
+				Add (MenuBar);
+				Add (LeftPane);
+				Add (RightPane);
+				Add (StatusBar);
 
-			menuItems.Add (null);
-			menuItems.Add (item);
+				Loaded += LoadedHandler;
 
-			return menuItems.ToArray ();
-		}
+				// Restore previous selections
+				CategoryListView.SelectedItem = _cachedCategoryIndex;
+				ScenarioListView.SelectedItem = _cachedScenarioIndex;
+			}
 
-		static MenuItem [] CreateSizeStyle ()
-		{
-			List<MenuItem> menuItems = new List<MenuItem> ();
-			var item = new MenuItem ();
-			item.Title = "_Height As Buffer";
-			item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
-			item.CheckType |= MenuItemCheckStyle.Checked;
-			item.Checked = Application.HeightAsBuffer;
-			item.Action += () => {
-				item.Checked = !item.Checked;
-				_heightAsBuffer = item.Checked;
+			void LoadedHandler ()
+			{
 				Application.HeightAsBuffer = _heightAsBuffer;
-			};
-			menuItems.Add (item);
 
-			return menuItems.ToArray ();
-		}
+				if (_colorScheme == null) {
+					ColorScheme = _colorScheme = Colors.Base;
+				}
 
-		static MenuItem [] CreateDiagnosticFlagsMenuItems ()
-		{
-			const string OFF = "Diagnostics: _Off";
-			const string FRAME_RULER = "Diagnostics: Frame _Ruler";
-			const string FRAME_PADDING = "Diagnostics: _Frame Padding";
-			var index = 0;
+				miIsMouseDisabled.Checked = Application.IsMouseDisabled;
+				miHeightAsBuffer.Checked = Application.HeightAsBuffer;
+				DriverName.Title = $"Driver: {Driver.GetType ().Name}";
 
-			List<MenuItem> menuItems = new List<MenuItem> ();
-			foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ())) {
-				var item = new MenuItem ();
-				item.Title = GetDiagnosticsTitle (diag);
-				item.Shortcut = Key.AltMask + index.ToString () [0];
-				index++;
-				item.CheckType |= MenuItemCheckStyle.Checked;
-				if (GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off) == item.Title) {
-					item.Checked = (_diagnosticFlags & (ConsoleDriver.DiagnosticFlags.FramePadding
-					| ConsoleDriver.DiagnosticFlags.FrameRuler)) == 0;
-				} else {
-					item.Checked = _diagnosticFlags.HasFlag (diag);
+				if (_selectedScenario != null) {
+					_selectedScenario = null;
+					_isFirstRunning = false;
 				}
-				item.Action += () => {
-					var t = GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off);
-					if (item.Title == t && !item.Checked) {
-						_diagnosticFlags &= ~(ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler);
-						item.Checked = true;
-					} else if (item.Title == t && item.Checked) {
-						_diagnosticFlags |= (ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler);
-						item.Checked = false;
-					} else {
-						var f = GetDiagnosticsEnumValue (item.Title);
-						if (_diagnosticFlags.HasFlag (f)) {
-							SetDiagnosticsFlag (f, false);
-						} else {
-							SetDiagnosticsFlag (f, true);
-						}
-					}
-					foreach (var menuItem in menuItems) {
-						if (menuItem.Title == t) {
-							menuItem.Checked = !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FrameRuler)
-								&& !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FramePadding);
-						} else if (menuItem.Title != t) {
-							menuItem.Checked = _diagnosticFlags.HasFlag (GetDiagnosticsEnumValue (menuItem.Title));
-						}
-					}
-					ConsoleDriver.Diagnostics = _diagnosticFlags;
-					Application.Top.SetNeedsDisplay ();
-				};
-				menuItems.Add (item);
+				if (!_isFirstRunning) {
+					RightPane.SetFocus ();
+				}
+				Loaded -= LoadedHandler;
 			}
-			return menuItems.ToArray ();
 
-			string GetDiagnosticsTitle (Enum diag)
+			/// <summary>
+			/// Launches the selected scenario, setting the global _selectedScenario
+			/// </summary>
+			/// <param name="e"></param>
+			void ScenarioListView_OpenSelectedItem (EventArgs e)
 			{
-				switch (Enum.GetName (_diagnosticFlags.GetType (), diag)) {
-				case "Off":
-					return OFF;
-				case "FrameRuler":
-					return FRAME_RULER;
-				case "FramePadding":
-					return FRAME_PADDING;
+				if (_selectedScenario is null) {
+					// Save selected item state
+					_cachedCategoryIndex = CategoryListView.SelectedItem;
+					_cachedScenarioIndex = ScenarioListView.SelectedItem;
+					// Create new instance of scenario (even though Scenarios contains instances)
+					_selectedScenario = (Scenario)Activator.CreateInstance (ScenarioListView.Source.ToList () [ScenarioListView.SelectedItem].GetType ());
+
+					// Tell the main app to stop
+					Application.RequestStop ();
 				}
-				return "";
 			}
 
-			Enum GetDiagnosticsEnumValue (ustring title)
+			List<MenuItem []> CreateDiagnosticMenuItems ()
 			{
-				switch (title.ToString ()) {
-				case FRAME_RULER:
-					return ConsoleDriver.DiagnosticFlags.FrameRuler;
-				case FRAME_PADDING:
-					return ConsoleDriver.DiagnosticFlags.FramePadding;
-				}
-				return null;
+				List<MenuItem []> menuItems = new List<MenuItem []> ();
+				menuItems.Add (CreateDiagnosticFlagsMenuItems ());
+				menuItems.Add (new MenuItem [] { null });
+				menuItems.Add (CreateHeightAsBufferMenuItems ());
+				menuItems.Add (CreateDisabledEnabledMouseItems ());
+				menuItems.Add (CreateKeybindingsMenuItems ());
+				return menuItems;
 			}
 
-			void SetDiagnosticsFlag (Enum diag, bool add)
+			MenuItem [] CreateDisabledEnabledMouseItems ()
 			{
-				switch (diag) {
-				case ConsoleDriver.DiagnosticFlags.FrameRuler:
-					if (add) {
-						_diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FrameRuler;
-					} else {
-						_diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FrameRuler;
-					}
-					break;
-				case ConsoleDriver.DiagnosticFlags.FramePadding:
-					if (add) {
-						_diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FramePadding;
-					} else {
-						_diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FramePadding;
-					}
-					break;
-				default:
-					_diagnosticFlags = default;
-					break;
-				}
+				List<MenuItem> menuItems = new List<MenuItem> ();
+				miIsMouseDisabled = new MenuItem ();
+				miIsMouseDisabled.Title = "_Disable Mouse";
+				miIsMouseDisabled.Shortcut = Key.CtrlMask | Key.AltMask | (Key)miIsMouseDisabled.Title.ToString ().Substring (1, 1) [0];
+				miIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked;
+				miIsMouseDisabled.Action += () => {
+					miIsMouseDisabled.Checked = Application.IsMouseDisabled = !miIsMouseDisabled.Checked;
+				};
+				menuItems.Add (miIsMouseDisabled);
+
+				return menuItems.ToArray ();
 			}
-		}
 
-		static ColorScheme _colorScheme;
-		static MenuItem [] CreateColorSchemeMenuItems ()
-		{
-			List<MenuItem> menuItems = new List<MenuItem> ();
-			foreach (var sc in Colors.ColorSchemes) {
+			MenuItem [] CreateKeybindingsMenuItems ()
+			{
+				List<MenuItem> menuItems = new List<MenuItem> ();
 				var item = new MenuItem ();
-				item.Title = $"_{sc.Key}";
-				item.Shortcut = Key.AltMask | (Key)sc.Key.Substring (0, 1) [0];
-				item.CheckType |= MenuItemCheckStyle.Radio;
-				item.Checked = sc.Value == _colorScheme;
+				item.Title = "_Key Bindings";
+				item.Help = "Change which keys do what";
 				item.Action += () => {
-					Application.Top.ColorScheme = _colorScheme = sc.Value;
-					Application.Top?.SetNeedsDisplay ();
-					foreach (var menuItem in menuItems) {
-						menuItem.Checked = menuItem.Title.Equals ($"_{sc.Key}") && sc.Value == _colorScheme;
-					}
+					var dlg = new KeyBindingsDialog ();
+					Application.Run (dlg);
 				};
+
+				menuItems.Add (null);
 				menuItems.Add (item);
+
+				return menuItems.ToArray ();
 			}
-			return menuItems.ToArray ();
-		}
 
-		private static void KeyDownHandler (View.KeyEventEventArgs a)
-		{
-			if (a.KeyEvent.IsCapslock) {
-				_capslock.Title = "Caps: On";
-				_statusBar.SetNeedsDisplay ();
-			} else {
-				_capslock.Title = "Caps: Off";
-				_statusBar.SetNeedsDisplay ();
+			MenuItem [] CreateHeightAsBufferMenuItems ()
+			{
+				List<MenuItem> menuItems = new List<MenuItem> ();
+				miHeightAsBuffer = new MenuItem ();
+				miHeightAsBuffer.Title = "_Height As Buffer";
+				miHeightAsBuffer.Shortcut = Key.CtrlMask | Key.AltMask | (Key)miHeightAsBuffer.Title.ToString ().Substring (1, 1) [0];
+				miHeightAsBuffer.CheckType |= MenuItemCheckStyle.Checked;
+				miHeightAsBuffer.Action += () => {
+					miHeightAsBuffer.Checked = !miHeightAsBuffer.Checked;
+					Application.HeightAsBuffer = miHeightAsBuffer.Checked;
+				};
+				menuItems.Add (miHeightAsBuffer);
+
+				return menuItems.ToArray ();
 			}
 
-			if (a.KeyEvent.IsNumlock) {
-				_numlock.Title = "Num: On";
-				_statusBar.SetNeedsDisplay ();
-			} else {
-				_numlock.Title = "Num: Off";
-				_statusBar.SetNeedsDisplay ();
+			MenuItem [] CreateDiagnosticFlagsMenuItems ()
+			{
+				const string OFF = "Diagnostics: _Off";
+				const string FRAME_RULER = "Diagnostics: Frame _Ruler";
+				const string FRAME_PADDING = "Diagnostics: _Frame Padding";
+				var index = 0;
+
+				List<MenuItem> menuItems = new List<MenuItem> ();
+				foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ())) {
+					var item = new MenuItem ();
+					item.Title = GetDiagnosticsTitle (diag);
+					item.Shortcut = Key.AltMask + index.ToString () [0];
+					index++;
+					item.CheckType |= MenuItemCheckStyle.Checked;
+					if (GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off) == item.Title) {
+						item.Checked = (_diagnosticFlags & (ConsoleDriver.DiagnosticFlags.FramePadding
+						| ConsoleDriver.DiagnosticFlags.FrameRuler)) == 0;
+					} else {
+						item.Checked = _diagnosticFlags.HasFlag (diag);
+					}
+					item.Action += () => {
+						var t = GetDiagnosticsTitle (ConsoleDriver.DiagnosticFlags.Off);
+						if (item.Title == t && !item.Checked) {
+							_diagnosticFlags &= ~(ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler);
+							item.Checked = true;
+						} else if (item.Title == t && item.Checked) {
+							_diagnosticFlags |= (ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler);
+							item.Checked = false;
+						} else {
+							var f = GetDiagnosticsEnumValue (item.Title);
+							if (_diagnosticFlags.HasFlag (f)) {
+								SetDiagnosticsFlag (f, false);
+							} else {
+								SetDiagnosticsFlag (f, true);
+							}
+						}
+						foreach (var menuItem in menuItems) {
+							if (menuItem.Title == t) {
+								menuItem.Checked = !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FrameRuler)
+									&& !_diagnosticFlags.HasFlag (ConsoleDriver.DiagnosticFlags.FramePadding);
+							} else if (menuItem.Title != t) {
+								menuItem.Checked = _diagnosticFlags.HasFlag (GetDiagnosticsEnumValue (menuItem.Title));
+							}
+						}
+						ConsoleDriver.Diagnostics = _diagnosticFlags;
+						Application.Top.SetNeedsDisplay ();
+					};
+					menuItems.Add (item);
+				}
+				return menuItems.ToArray ();
+
+				string GetDiagnosticsTitle (Enum diag)
+				{
+					switch (Enum.GetName (_diagnosticFlags.GetType (), diag)) {
+					case "Off":
+						return OFF;
+					case "FrameRuler":
+						return FRAME_RULER;
+					case "FramePadding":
+						return FRAME_PADDING;
+					}
+					return "";
+				}
+
+				Enum GetDiagnosticsEnumValue (ustring title)
+				{
+					switch (title.ToString ()) {
+					case FRAME_RULER:
+						return ConsoleDriver.DiagnosticFlags.FrameRuler;
+					case FRAME_PADDING:
+						return ConsoleDriver.DiagnosticFlags.FramePadding;
+					}
+					return null;
+				}
+
+				void SetDiagnosticsFlag (Enum diag, bool add)
+				{
+					switch (diag) {
+					case ConsoleDriver.DiagnosticFlags.FrameRuler:
+						if (add) {
+							_diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FrameRuler;
+						} else {
+							_diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FrameRuler;
+						}
+						break;
+					case ConsoleDriver.DiagnosticFlags.FramePadding:
+						if (add) {
+							_diagnosticFlags |= ConsoleDriver.DiagnosticFlags.FramePadding;
+						} else {
+							_diagnosticFlags &= ~ConsoleDriver.DiagnosticFlags.FramePadding;
+						}
+						break;
+					default:
+						_diagnosticFlags = default;
+						break;
+					}
+				}
 			}
 
-			if (a.KeyEvent.IsScrolllock) {
-				_scrolllock.Title = "Scroll: On";
-				_statusBar.SetNeedsDisplay ();
-			} else {
-				_scrolllock.Title = "Scroll: Off";
-				_statusBar.SetNeedsDisplay ();
+			MenuItem [] CreateColorSchemeMenuItems ()
+			{
+				List<MenuItem> menuItems = new List<MenuItem> ();
+				foreach (var sc in Colors.ColorSchemes) {
+					var item = new MenuItem ();
+					item.Title = $"_{sc.Key}";
+					item.Shortcut = Key.AltMask | (Key)sc.Key.Substring (0, 1) [0];
+					item.CheckType |= MenuItemCheckStyle.Radio;
+					item.Checked = sc.Value == _colorScheme;
+					item.Action += () => {
+						ColorScheme = _colorScheme = sc.Value;
+						SetNeedsDisplay ();
+						foreach (var menuItem in menuItems) {
+							menuItem.Checked = menuItem.Title.Equals ($"_{sc.Key}") && sc.Value == _colorScheme;
+						}
+					};
+					menuItems.Add (item);
+				}
+				return menuItems.ToArray ();
+			}
+
+			void KeyDownHandler (View.KeyEventEventArgs a)
+			{
+				if (a.KeyEvent.IsCapslock) {
+					Capslock.Title = "Caps: On";
+					StatusBar.SetNeedsDisplay ();
+				} else {
+					Capslock.Title = "Caps: Off";
+					StatusBar.SetNeedsDisplay ();
+				}
+
+				if (a.KeyEvent.IsNumlock) {
+					Numlock.Title = "Num: On";
+					StatusBar.SetNeedsDisplay ();
+				} else {
+					Numlock.Title = "Num: Off";
+					StatusBar.SetNeedsDisplay ();
+				}
+
+				if (a.KeyEvent.IsScrolllock) {
+					Scrolllock.Title = "Scroll: On";
+					StatusBar.SetNeedsDisplay ();
+				} else {
+					Scrolllock.Title = "Scroll: Off";
+					StatusBar.SetNeedsDisplay ();
+				}
+			}
+
+			void CategoryListView_SelectedChanged (ListViewItemEventArgs e)
+			{
+				var item = _categories [e.Item];
+				List<Scenario> newlist;
+				if (e.Item == 0) {
+					// First category is "All"
+					newlist = _scenarios;
+
+				} else {
+					newlist = _scenarios.Where (s => s.GetCategories ().Contains (item)).ToList ();
+				}
+				ScenarioListView.SetSource (newlist.ToList ());
 			}
 		}
 
-		private static void CategoryListView_SelectedChanged (ListViewItemEventArgs e)
+		static void VerifyObjectsWereDisposed ()
 		{
-			var item = _categories [e.Item];
-			List<Scenario> newlist;
-			if (e.Item == 0) {
-				// First category is "All"
-				newlist = _scenarios;
-
-			} else {
-				newlist = _scenarios.Where (s => s.GetCategories ().Contains (item)).ToList ();
+#if DEBUG_IDISPOSABLE
+			// Validate there are no outstanding Responder-based instances 
+			// after a scenario was selected to run. This proves the main UI Catalog
+			// 'app' closed cleanly.
+			foreach (var inst in Responder.Instances) {
+				Debug.Assert (inst.WasDisposed);
 			}
-			_scenarioListView.SetSource (newlist.ToList ());
+			Responder.Instances.Clear ();
+
+			// Validate there are no outstanding Application.RunState-based instances 
+			// after a scenario was selected to run. This proves the main UI Catalog
+			// 'app' closed cleanly.
+			foreach (var inst in Application.RunState.Instances) {
+				Debug.Assert (inst.WasDisposed);
+			}
+			Application.RunState.Instances.Clear ();
+#endif
 		}
 
-		private static void OpenUrl (string url)
+		static void OpenUrl (string url)
 		{
 			try {
 				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {

File diff suppressed because it is too large
+ 256 - 856
UnitTests/ApplicationTests.cs


+ 3 - 3
UnitTests/MainLoopTests.cs

@@ -578,9 +578,9 @@ namespace Terminal.Gui.Core {
 			TextField tf = new ();
 			Application.Top.Add (tf);
 
-			const int numPasses = 10;
-			const int numIncrements = 10000;
-			const int pollMs = 20000;
+			const int numPasses = 5;
+			const int numIncrements = 5000;
+			const int pollMs = 10000;
 
 			var task = Task.Run (() => RunTest (r, tf, numPasses, numIncrements, pollMs));
 

+ 695 - 0
UnitTests/MdiTests.cs

@@ -0,0 +1,695 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+
+namespace Terminal.Gui.Core {
+	public class MdiTests {
+		public MdiTests ()
+		{
+#if DEBUG_IDISPOSABLE
+			Responder.Instances.Clear ();
+			Application.RunState.Instances.Clear ();
+#endif
+		}
+
+
+		[Fact]
+		public void Dispose_Toplevel_IsMdiContainer_False_With_Begin_End ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var top = new Toplevel ();
+			var rs = Application.Begin (top);
+			Application.End (rs);
+
+			Application.Shutdown ();
+
+			Assert.Equal (2, Responder.Instances.Count);
+			Assert.True (Responder.Instances [0].WasDisposed);
+			Assert.True (Responder.Instances [1].WasDisposed);
+		}
+
+		[Fact]
+		public void Dispose_Toplevel_IsMdiContainer_True_With_Begin ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var mdi = new Toplevel { IsMdiContainer = true };
+			var rs = Application.Begin (mdi);
+			Application.End (rs);
+
+			Application.Shutdown ();
+
+			Assert.Equal (2, Responder.Instances.Count);
+			Assert.True (Responder.Instances [0].WasDisposed);
+			Assert.True (Responder.Instances [1].WasDisposed);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Application_RequestStop_With_Params_On_A_Not_MdiContainer_Always_Use_Application_Current ()
+		{
+			var top1 = new Toplevel ();
+			var top2 = new Toplevel ();
+			var top3 = new Window ();
+			var top4 = new Window ();
+			var d = new Dialog ();
+
+			// top1, top2, top3, d1 = 4
+			var iterations = 4;
+
+			top1.Ready += () => {
+				Assert.Null (Application.MdiChildes);
+				Application.Run (top2);
+			};
+			top2.Ready += () => {
+				Assert.Null (Application.MdiChildes);
+				Application.Run (top3);
+			};
+			top3.Ready += () => {
+				Assert.Null (Application.MdiChildes);
+				Application.Run (top4);
+			};
+			top4.Ready += () => {
+				Assert.Null (Application.MdiChildes);
+				Application.Run (d);
+			};
+
+			d.Ready += () => {
+				Assert.Null (Application.MdiChildes);
+				// This will close the d because on a not MdiContainer the Application.Current it always used.
+				Application.RequestStop (top1);
+				Assert.True (Application.Current == d);
+			};
+
+			d.Closed += (e) => Application.RequestStop (top1);
+
+			Application.Iteration += () => {
+				Assert.Null (Application.MdiChildes);
+				if (iterations == 4) {
+					Assert.True (Application.Current == d);
+				} else if (iterations == 3) {
+					Assert.True (Application.Current == top4);
+				} else if (iterations == 2) {
+					Assert.True (Application.Current == top3);
+				} else if (iterations == 1) {
+					Assert.True (Application.Current == top2);
+				} else {
+					Assert.True (Application.Current == top1);
+				}
+				Application.RequestStop (top1);
+				iterations--;
+			};
+
+			Application.Run (top1);
+
+			Assert.Null (Application.MdiChildes);
+		}
+
+		class Mdi : Toplevel {
+			public Mdi ()
+			{
+				IsMdiContainer = true;
+			}
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MdiContainer_With_Toplevel_RequestStop_Balanced ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+			var d = new Dialog ();
+
+			// MdiChild = c1, c2, c3
+			// d1 = 1
+			var iterations = 4;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (d);
+			};
+
+			// More easy because the Mdi Container handles all at once
+			d.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				// This will not close the MdiContainer because d is a modal toplevel and will be closed.
+				mdi.RequestStop ();
+			};
+
+			// Now this will close the MdiContainer propagating through the MdiChildes.
+			d.Closed += (e) => {
+				mdi.RequestStop ();
+			};
+
+			Application.Iteration += () => {
+				if (iterations == 4) {
+					// The Dialog was not closed before and will be closed now.
+					Assert.True (Application.Current == d);
+					Assert.False (d.Running);
+				} else {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+					for (int i = 0; i < iterations; i++) {
+						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
+					}
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MdiContainer_With_Application_RequestStop_MdiTop_With_Params ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+			var d = new Dialog ();
+
+			// MdiChild = c1, c2, c3
+			// d1 = 1
+			var iterations = 4;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (d);
+			};
+
+			// Also easy because the Mdi Container handles all at once
+			d.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				// This will not close the MdiContainer because d is a modal toplevel
+				Application.RequestStop (mdi);
+			};
+
+			// Now this will close the MdiContainer propagating through the MdiChildes.
+			d.Closed += (e) => Application.RequestStop (mdi);
+
+			Application.Iteration += () => {
+				if (iterations == 4) {
+					// The Dialog was not closed before and will be closed now.
+					Assert.True (Application.Current == d);
+					Assert.False (d.Running);
+				} else {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+					for (int i = 0; i < iterations; i++) {
+						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
+					}
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MdiContainer_With_Application_RequestStop_MdiTop_Without_Params ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+			var d = new Dialog ();
+
+			// MdiChild = c1, c2, c3 = 3
+			// d1 = 1
+			var iterations = 4;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (d);
+			};
+
+			//More harder because it's sequential.
+			d.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				// Close the Dialog
+				Application.RequestStop ();
+			};
+
+			// Now this will close the MdiContainer propagating through the MdiChildes.
+			d.Closed += (e) => Application.RequestStop (mdi);
+
+			Application.Iteration += () => {
+				if (iterations == 4) {
+					// The Dialog still is the current top and we can't request stop to MdiContainer
+					// because we are not using parameter calls.
+					Assert.True (Application.Current == d);
+					Assert.False (d.Running);
+				} else {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+					for (int i = 0; i < iterations; i++) {
+						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
+					}
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void IsMdiChild_Testing ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+			var d = new Dialog ();
+
+			Application.Iteration += () => {
+				Assert.False (mdi.IsMdiChild);
+				Assert.True (c1.IsMdiChild);
+				Assert.True (c2.IsMdiChild);
+				Assert.True (c3.IsMdiChild);
+				Assert.False (d.IsMdiChild);
+
+				mdi.RequestStop ();
+			};
+
+			Application.Run (mdi);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Modal_Toplevel_Can_Open_Another_Modal_Toplevel_But_RequestStop_To_The_Caller_Also_Sets_Current_Running_To_False_Too ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+			var d1 = new Dialog ();
+			var d2 = new Dialog ();
+
+			// MdiChild = c1, c2, c3 = 3
+			// d1, d2 = 2
+			var iterations = 5;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (d1);
+			};
+			d1.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (d2);
+			};
+
+			d2.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Assert.True (Application.Current == d2);
+				Assert.True (Application.Current.Running);
+				// Trying to close the Dialog1
+				d1.RequestStop ();
+			};
+
+			// Now this will close the MdiContainer propagating through the MdiChildes.
+			d1.Closed += (e) => {
+				Assert.True (Application.Current == d1);
+				Assert.False (Application.Current.Running);
+				mdi.RequestStop ();
+			};
+
+			Application.Iteration += () => {
+				if (iterations == 5) {
+					// The Dialog2 still is the current top and we can't request stop to MdiContainer
+					// because Dialog2 and Dialog1 must be closed first.
+					// Dialog2 will be closed in this iteration.
+					Assert.True (Application.Current == d2);
+					Assert.False (Application.Current.Running);
+					Assert.False (d1.Running);
+				} else if (iterations == 4) {
+					// Dialog1 will be closed in this iteration.
+					Assert.True (Application.Current == d1);
+					Assert.False (Application.Current.Running);
+				} else {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+					for (int i = 0; i < iterations; i++) {
+						Assert.Equal ((iterations - i + 1).ToString (), Application.MdiChildes [i].Id);
+					}
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Modal_Toplevel_Can_Open_Another_Not_Modal_Toplevel_But_RequestStop_To_The_Caller_Also_Sets_Current_Running_To_False_Too ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+			var d1 = new Dialog ();
+			var c4 = new Toplevel ();
+
+			// MdiChild = c1, c2, c3, c4 = 4
+			// d1 = 1
+			var iterations = 5;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (d1);
+			};
+			d1.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				Application.Run (c4);
+			};
+
+			c4.Ready += () => {
+				Assert.Equal (4, Application.MdiChildes.Count);
+				// Trying to close the Dialog1
+				d1.RequestStop ();
+			};
+
+			// Now this will close the MdiContainer propagating through the MdiChildes.
+			d1.Closed += (e) => {
+				mdi.RequestStop ();
+			};
+
+			Application.Iteration += () => {
+				if (iterations == 5) {
+					// The Dialog2 still is the current top and we can't request stop to MdiContainer
+					// because Dialog2 and Dialog1 must be closed first.
+					// Using request stop here will call the Dialog again without need
+					Assert.True (Application.Current == d1);
+					Assert.False (Application.Current.Running);
+					Assert.True (c4.Running);
+				} else {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+					for (int i = 0; i < iterations; i++) {
+						Assert.Equal ((iterations - i + (iterations == 4 && i == 0 ? 2 : 1)).ToString (),
+							Application.MdiChildes [i].Id);
+					}
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MoveCurrent_Returns_False_If_The_Current_And_Top_Parameter_Are_Both_With_Running_Set_To_False ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+
+			// MdiChild = c1, c2, c3
+			var iterations = 3;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				c3.RequestStop ();
+				c1.RequestStop ();
+			};
+			// Now this will close the MdiContainer propagating through the MdiChildes.
+			c1.Closed += (e) => {
+				mdi.RequestStop ();
+			};
+			Application.Iteration += () => {
+				if (iterations == 3) {
+					// The Current still is c3 because Current.Running is false.
+					Assert.True (Application.Current == c3);
+					Assert.False (Application.Current.Running);
+					// But the childes order were reorder by Running = false
+					Assert.True (Application.MdiChildes [0] == c3);
+					Assert.True (Application.MdiChildes [1] == c1);
+					Assert.True (Application.MdiChildes [^1] == c2);
+				} else if (iterations == 2) {
+					// The Current is c1 and Current.Running is false.
+					Assert.True (Application.Current == c1);
+					Assert.False (Application.Current.Running);
+					Assert.True (Application.MdiChildes [0] == c1);
+					Assert.True (Application.MdiChildes [^1] == c2);
+				} else if (iterations == 1) {
+					// The Current is c2 and Current.Running is false.
+					Assert.True (Application.Current == c2);
+					Assert.False (Application.Current.Running);
+					Assert.True (Application.MdiChildes [^1] == c2);
+				} else {
+					// The Current is mdi.
+					Assert.True (Application.Current == mdi);
+					Assert.Empty (Application.MdiChildes);
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MdiContainer_Throws_If_More_Than_One ()
+		{
+			var mdi = new Mdi ();
+			var mdi2 = new Mdi ();
+
+			mdi.Ready += () => {
+				Assert.Throws<InvalidOperationException> (() => Application.Run (mdi2));
+				mdi.RequestStop ();
+			};
+
+			Application.Run (mdi);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MdiContainer_Open_And_Close_Modal_And_Open_Not_Modal_Toplevels_Randomly ()
+		{
+			var mdi = new Mdi ();
+			var logger = new Toplevel ();
+
+			var iterations = 1; // The logger
+			var running = true;
+			var stageCompleted = true;
+			var allStageClosed = false;
+			var mdiRequestStop = false;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (logger);
+			};
+
+			logger.Ready += () => Assert.Single (Application.MdiChildes);
+
+			Application.Iteration += () => {
+				if (stageCompleted && running) {
+					stageCompleted = false;
+					var stage = new Window () { Modal = true };
+
+					stage.Ready += () => {
+						Assert.Equal (iterations, Application.MdiChildes.Count);
+						stage.RequestStop ();
+					};
+
+					stage.Closed += (_) => {
+						if (iterations == 11) {
+							allStageClosed = true;
+						}
+						Assert.Equal (iterations, Application.MdiChildes.Count);
+						if (running) {
+							stageCompleted = true;
+
+							var rpt = new Window ();
+
+							rpt.Ready += () => {
+								iterations++;
+								Assert.Equal (iterations, Application.MdiChildes.Count);
+							};
+
+							Application.Run (rpt);
+						}
+					};
+
+					Application.Run (stage);
+
+				} else if (iterations == 11 && running) {
+					running = false;
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+
+				} else if (!mdiRequestStop && running && !allStageClosed) {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+
+				} else if (!mdiRequestStop && !running && allStageClosed) {
+					Assert.Equal (iterations, Application.MdiChildes.Count);
+					mdiRequestStop = true;
+					mdi.RequestStop ();
+				} else {
+					Assert.Empty (Application.MdiChildes);
+				}
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void AllChildClosed_Event_Test ()
+		{
+			var mdi = new Mdi ();
+			var c1 = new Toplevel ();
+			var c2 = new Window ();
+			var c3 = new Window ();
+
+			// MdiChild = c1, c2, c3
+			var iterations = 3;
+
+			mdi.Ready += () => {
+				Assert.Empty (Application.MdiChildes);
+				Application.Run (c1);
+			};
+			c1.Ready += () => {
+				Assert.Single (Application.MdiChildes);
+				Application.Run (c2);
+			};
+			c2.Ready += () => {
+				Assert.Equal (2, Application.MdiChildes.Count);
+				Application.Run (c3);
+			};
+			c3.Ready += () => {
+				Assert.Equal (3, Application.MdiChildes.Count);
+				c3.RequestStop ();
+				c2.RequestStop ();
+				c1.RequestStop ();
+			};
+			// Now this will close the MdiContainer when all MdiChildes was closed
+			mdi.AllChildClosed += () => {
+				mdi.RequestStop ();
+			};
+			Application.Iteration += () => {
+				if (iterations == 3) {
+					// The Current still is c3 because Current.Running is false.
+					Assert.True (Application.Current == c3);
+					Assert.False (Application.Current.Running);
+					// But the childes order were reorder by Running = false
+					Assert.True (Application.MdiChildes [0] == c3);
+					Assert.True (Application.MdiChildes [1] == c2);
+					Assert.True (Application.MdiChildes [^1] == c1);
+				} else if (iterations == 2) {
+					// The Current is c2 and Current.Running is false.
+					Assert.True (Application.Current == c2);
+					Assert.False (Application.Current.Running);
+					Assert.True (Application.MdiChildes [0] == c2);
+					Assert.True (Application.MdiChildes [^1] == c1);
+				} else if (iterations == 1) {
+					// The Current is c1 and Current.Running is false.
+					Assert.True (Application.Current == c1);
+					Assert.False (Application.Current.Running);
+					Assert.True (Application.MdiChildes [^1] == c1);
+				} else {
+					// The Current is mdi.
+					Assert.True (Application.Current == mdi);
+					Assert.False (Application.Current.Running);
+					Assert.Empty (Application.MdiChildes);
+				}
+				iterations--;
+			};
+
+			Application.Run (mdi);
+
+			Assert.Empty (Application.MdiChildes);
+		}
+	}
+}

+ 105 - 0
UnitTests/RunStateTests.cs

@@ -0,0 +1,105 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+
+namespace Terminal.Gui.Core {
+	/// <summary>
+	/// These tests focus on Application.RunState and the various ways it can be changed.
+	/// </summary>
+	public class RunStateTests {
+		public RunStateTests ()
+		{
+#if DEBUG_IDISPOSABLE
+			Responder.Instances.Clear ();
+			Application.RunState.Instances.Clear ();
+#endif
+		}
+
+		[Fact]
+		public void New_Creates_RunState ()
+		{
+			var rs = new Application.RunState (null);
+			Assert.Null (rs.Toplevel);
+			
+			var top = new Toplevel ();
+			rs = new Application.RunState (top);
+			Assert.Equal (top, rs.Toplevel);
+		}
+
+		[Fact]
+		public void Dispose_Cleans_Up_RunState ()
+		{
+			var rs = new Application.RunState (null);
+			Assert.NotNull (rs);
+
+			// Should not throw because Toplevel was null
+			rs.Dispose ();
+			Assert.True (rs.WasDisposed);
+
+			var top = new Toplevel ();
+			rs = new Application.RunState (top);
+			Assert.NotNull (rs);
+
+			// Should throw because Toplevel was not cleaned up
+			Assert.Throws<InvalidOperationException> (() => rs.Dispose ());
+
+			rs.Toplevel.Dispose ();
+			rs.Toplevel = null;
+			rs.Dispose ();
+			Assert.True (rs.WasDisposed);
+			Assert.True (top.WasDisposed);
+		}
+
+		void Init ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			Assert.NotNull (Application.Driver);
+			Assert.NotNull (Application.MainLoop);
+			Assert.NotNull (SynchronizationContext.Current);
+		}
+
+		void Shutdown ()
+		{
+			Application.Shutdown ();
+			// Validate there are no outstanding RunState-based instances left
+			foreach (var inst in Application.RunState.Instances) {
+				Assert.True (inst.WasDisposed);
+			}
+		}
+
+		[Fact]
+		public void Begin_End_Cleans_Up_RunState ()
+		{
+			// Setup Mock driver
+			Init ();
+
+			// Test null Toplevel
+			Assert.Throws<ArgumentNullException> (() => Application.Begin (null));
+
+			var top = new Toplevel ();
+			var rs = Application.Begin (top);
+			Assert.NotNull (rs);
+			Assert.Equal (top, Application.Current);
+			Application.End (rs);
+
+			Assert.Null (Application.Current);
+			Assert.NotNull (Application.Top);
+			Assert.NotNull (Application.MainLoop);
+			Assert.NotNull (Application.Driver);
+
+			Shutdown ();
+
+			Assert.True (rs.WasDisposed);
+
+			Assert.Null (Application.Top);
+			Assert.Null (Application.MainLoop);
+			Assert.Null (Application.Driver);
+		}
+	}
+}

+ 9 - 3
UnitTests/ScenarioTests.cs

@@ -64,12 +64,18 @@ namespace UICatalog {
 				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 				// Close after a short period of time
-				var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (200), closeCallback);
+				var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), closeCallback);
 
-				scenario.Init (Application.Top, Colors.Base);
+				scenario.Init (Colors.Base);
 				scenario.Setup ();
 				scenario.Run ();
 				Application.Shutdown ();
+#if DEBUG_IDISPOSABLE
+				foreach (var inst in Responder.Instances) {
+					Assert.True (inst.WasDisposed);
+				}
+				Responder.Instances.Clear ();
+#endif
 			}
 #if DEBUG_IDISPOSABLE
 			foreach (var inst in Responder.Instances) {
@@ -115,7 +121,7 @@ namespace UICatalog {
 				Assert.Equal (Key.CtrlMask | Key.Q, args.KeyEvent.Key);
 			};
 
-			generic.Init (Application.Top, Colors.Base);
+			generic.Init (Colors.Base);
 			generic.Setup ();
 			// There is no need to call Application.Begin because Init already creates the Application.Top
 			// If Application.RunState is used then the Application.RunLoop must also be used instead Application.Run.

+ 83 - 0
UnitTests/SynchronizatonContextTests.cs

@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using System.Runtime.InteropServices.ComTypes;
+using System.Threading;
+using System.Threading.Tasks;
+using Terminal.Gui;
+using Xunit;
+using Xunit.Sdk;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+
+namespace Terminal.Gui.Core {
+	public class SyncrhonizationContextTests {
+
+		[Fact, AutoInitShutdown]
+		public void SynchronizationContext_Post ()
+		{
+			var context = SynchronizationContext.Current;
+
+			var success = false;
+			Task.Run (() => {
+				Thread.Sleep (1_000);
+
+				// non blocking
+				context.Post (
+					delegate (object o) {
+						success = true;
+
+						// then tell the application to quit
+						Application.MainLoop.Invoke (() => Application.RequestStop ());
+					}, null);
+				Assert.False (success);
+			});
+
+			// blocks here until the RequestStop is processed at the end of the test
+			Application.Run ();
+			Assert.True (success);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void SynchronizationContext_Send ()
+		{
+			var context = SynchronizationContext.Current;
+
+			var success = false;
+			Task.Run (() => {
+				Thread.Sleep (1_000);
+
+				// blocking
+				context.Send (
+					delegate (object o) {
+						success = true;
+
+						// then tell the application to quit
+						Application.MainLoop.Invoke (() => Application.RequestStop ());
+					}, null);
+				Assert.True (success);
+			});
+
+			// blocks here until the RequestStop is processed at the end of the test
+			Application.Run ();
+			Assert.True (success);
+
+		}
+
+		[Fact, AutoInitShutdown]
+		public void SynchronizationContext_CreateCopy ()
+		{
+			var context = SynchronizationContext.Current;
+			Assert.NotNull (context);
+
+			var contextCopy = context.CreateCopy ();
+			Assert.NotNull (contextCopy);
+
+			Assert.NotEqual (context, contextCopy);
+		}
+
+	}
+}

+ 364 - 1
UnitTests/TextViewTests.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.Tracing;
 using System.Linq;
 using System.Reflection;
 using System.Text.RegularExpressions;
@@ -24,6 +25,7 @@ namespace Terminal.Gui.Views {
 		[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
 		public class InitShutdown : Xunit.Sdk.BeforeAfterTestAttribute {
 
+			public static string txt = "TAB to jump between text fields.";
 			public override void Before (MethodInfo methodUnderTest)
 			{
 				if (_textView != null) {
@@ -34,7 +36,6 @@ namespace Terminal.Gui.Views {
 
 				//                   1         2         3 
 				//         01234567890123456789012345678901=32 (Length)
-				var txt = "TAB to jump between text fields.";
 				var buff = new byte [txt.Length];
 				for (int i = 0; i < txt.Length; i++) {
 					buff [i] = (byte)txt [i];
@@ -1395,6 +1396,22 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("changed", _textView.Text);
 		}
 
+		[Fact]
+		[InitShutdown]
+		public void TextChanged_Event_NoFires_OnTyping ()
+		{
+			var eventcount = 0;
+			_textView.TextChanged += () => {
+				eventcount++;
+			};
+
+			_textView.Text = "ay";
+			Assert.Equal (1, eventcount);
+			_textView.ProcessKey (new KeyEvent (Key.Y, new KeyModifiers ()));
+			Assert.Equal (1, eventcount);
+			Assert.Equal ("Yay", _textView.Text.ToString ());
+		}
+
 		[Fact]
 		[InitShutdown]
 		public void Used_Is_True_By_Default ()
@@ -6409,5 +6426,351 @@ This is the second line.
 │             │
 └─────────────┘", output);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void ContentsChanged_Event_NoFires_On_CursorPosition ()
+		{
+			var tv = new TextView {
+				Width = 50,
+				Height = 10,
+			};
+
+			var eventcount = 0;
+			Assert.Null (tv.ContentsChanged);
+			tv.ContentsChanged += (e) => {
+				eventcount++;
+			};
+
+			tv.CursorPosition = new Point (0, 0);
+
+			Assert.Equal (0, eventcount);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ContentsChanged_Event_Fires_On_InsertText ()
+		{
+			var tv = new TextView {
+				Width = 50,
+				Height = 10,
+			};
+			tv.CursorPosition = new Point (0, 0);
+
+			var eventcount = 0;
+
+			Assert.Null (tv.ContentsChanged);
+			tv.ContentsChanged += (e) => {
+				eventcount++;
+			};
+
+
+			tv.InsertText ("a");
+			Assert.Equal (1, eventcount);
+
+			tv.CursorPosition = new Point (0, 0);
+			tv.InsertText ("bcd");
+			Assert.Equal (4, eventcount);
+			
+			tv.InsertText ("e");
+			Assert.Equal (5, eventcount);
+
+			tv.InsertText ("\n");
+			Assert.Equal (6, eventcount);
+
+			tv.InsertText ("1234");
+			Assert.Equal (10, eventcount);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ContentsChanged_Event_Fires_On_Init ()
+		{
+			Application.Iteration += () => {
+				Application.RequestStop ();
+			};
+
+			var expectedRow = 0;
+			var expectedCol = 0;
+			var eventcount = 0;
+
+			var tv = new TextView {
+				Width = 50,
+				Height = 10,
+				ContentsChanged = (e) => {
+					eventcount++;
+					Assert.Equal (expectedRow, e.Row);
+					Assert.Equal (expectedCol, e.Col);
+				}
+			};
+
+			Application.Top.Add (tv);
+			Application.Begin (Application.Top);
+			Assert.Equal (1, eventcount);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ContentsChanged_Event_Fires_On_Set_Text ()
+		{
+			Application.Iteration += () => {
+				Application.RequestStop ();
+			};
+			var eventcount = 0;
+
+			var expectedRow = 0;
+			var expectedCol = 0;
+
+			var tv = new TextView {
+				Width = 50,
+				Height = 10,
+				// you'd think col would be 3, but it's 0 because TextView sets
+				// row/col = 0 when you set Text
+				Text = "abc",
+				ContentsChanged = (e) => {
+					eventcount++;
+					Assert.Equal (expectedRow, e.Row);
+					Assert.Equal (expectedCol, e.Col);
+				}
+			};
+			Assert.Equal ("abc", tv.Text);
+
+			Application.Top.Add (tv);
+			var rs = Application.Begin (Application.Top);
+			Assert.Equal (1, eventcount); // for Initialize
+
+			expectedCol = 0;
+			tv.Text = "defg";
+			Assert.Equal (2, eventcount); // for set Text = "defg"
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ContentsChanged_Event_Fires_On_Typing ()
+		{
+			Application.Iteration += () => {
+				Application.RequestStop ();
+			};
+			var eventcount = 0;
+
+			var expectedRow = 0;
+			var expectedCol = 0;
+
+			var tv = new TextView {
+				Width = 50,
+				Height = 10,
+				ContentsChanged = (e) => {
+					eventcount++;
+					Assert.Equal (expectedRow, e.Row);
+					Assert.Equal (expectedCol, e.Col);
+				}
+			};
+
+			Application.Top.Add (tv);
+			var rs = Application.Begin (Application.Top);
+			Assert.Equal (1, eventcount); // for Initialize
+
+			expectedCol = 0;
+			tv.Text = "ay";
+			Assert.Equal (2, eventcount);
+
+			expectedCol = 1;
+			tv.ProcessKey (new KeyEvent (Key.Y, new KeyModifiers ()));
+			Assert.Equal (3, eventcount);
+			Assert.Equal ("Yay", tv.Text.ToString ());
+		}
+
+		[Fact, InitShutdown]
+		public void ContentsChanged_Event_Fires_Using_Kill_Delete_Tests ()
+		{
+			var eventcount = 0;
+
+			_textView.ContentsChanged = (e) => {
+				eventcount++;
+			};
+
+			var expectedEventCount = 1;
+			Kill_Delete_WordForward ();
+			Assert.Equal (expectedEventCount, eventcount); // for Initialize
+
+			expectedEventCount += 1;
+			Kill_Delete_WordBackward ();
+			Assert.Equal (expectedEventCount, eventcount);
+
+			expectedEventCount += 1;
+			Kill_To_End_Delete_Forwards_And_Copy_To_The_Clipboard ();
+			Assert.Equal (expectedEventCount, eventcount);
+
+			expectedEventCount += 1;
+			Kill_To_Start_Delete_Backwards_And_Copy_To_The_Clipboard ();
+			Assert.Equal (expectedEventCount, eventcount);
+		}
+
+
+		[Fact, InitShutdown]
+		public void ContentsChanged_Event_Fires_Using_Copy_Or_Cut_Tests ()
+		{
+			var eventcount = 0;
+
+			_textView.ContentsChanged = (e) => {
+				eventcount++;
+			};
+
+			var expectedEventCount = 1;
+
+			// reset
+			_textView.Text = InitShutdown.txt;
+			Assert.Equal (expectedEventCount, eventcount);
+
+			expectedEventCount += 3;
+			Copy_Or_Cut_And_Paste_With_No_Selection ();
+			Assert.Equal (expectedEventCount, eventcount);
+
+			// reset
+			expectedEventCount += 1;
+			_textView.Text = InitShutdown.txt;
+			Assert.Equal (expectedEventCount, eventcount);
+
+			expectedEventCount += 3;
+			Copy_Or_Cut_And_Paste_With_Selection ();
+			Assert.Equal (expectedEventCount, eventcount);
+
+			// reset
+			expectedEventCount += 1;
+			_textView.Text = InitShutdown.txt;
+			Assert.Equal (expectedEventCount, eventcount);
+
+			expectedEventCount += 1;
+			Copy_Or_Cut_Not_Null_If_Has_Selection ();
+			Assert.Equal (expectedEventCount, eventcount);
+
+			// reset
+			expectedEventCount += 1;
+			_textView.Text = InitShutdown.txt;
+			Assert.Equal (expectedEventCount, eventcount);
+
+			expectedEventCount += 1;
+			Copy_Or_Cut_Null_If_No_Selection ();
+			Assert.Equal (expectedEventCount, eventcount);
+
+			// reset
+			expectedEventCount += 1;
+			_textView.Text = InitShutdown.txt;
+			Assert.Equal (expectedEventCount, eventcount);
+
+			expectedEventCount += 4;
+			Copy_Without_Selection ();
+			Assert.Equal (expectedEventCount, eventcount);
+			
+			// reset
+			expectedEventCount += 1;
+			_textView.Text = InitShutdown.txt;
+			Assert.Equal (expectedEventCount, eventcount);
+
+			expectedEventCount += 4;
+			Copy_Without_Selection ();
+			Assert.Equal (expectedEventCount, eventcount);
+		}
+
+		[Fact, InitShutdown]
+		public void ContentsChanged_Event_Fires_On_Undo_Redo ()
+		{
+			var eventcount = 0;
+			var expectedEventCount = 0;
+
+			_textView.ContentsChanged = (e) => {
+				eventcount++;
+			};
+
+			expectedEventCount++;
+			_textView.Text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			Assert.Equal (expectedEventCount, eventcount);
+
+			expectedEventCount++;
+			Assert.True (_textView.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal (expectedEventCount, eventcount);
+
+			// Undo
+			expectedEventCount++;
+			Assert.True (_textView.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal (expectedEventCount, eventcount);
+
+			// Redo
+			expectedEventCount++;
+			Assert.True (_textView.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal (expectedEventCount, eventcount);
+
+			// Undo
+			expectedEventCount++;
+			Assert.True (_textView.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal (expectedEventCount, eventcount);
+
+			// Redo
+			expectedEventCount++;
+			Assert.True (_textView.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal (expectedEventCount, eventcount);
+		}
+
+		[Fact]
+		public void ContentsChanged_Event_Fires_ClearHistoryChanges ()
+		{
+			var eventcount = 0;
+
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView {
+				Width = 50,
+				Height = 10,
+				Text = text,
+				ContentsChanged = (e) => {
+					eventcount++;
+				}
+			};
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+
+			var expectedEventCount = 1; // for ENTER key
+			Assert.Equal (expectedEventCount, eventcount);
+			
+			tv.ClearHistoryChanges ();
+			expectedEventCount = 2;
+			Assert.Equal (expectedEventCount, eventcount);
+		}
+
+		[Fact]
+		public void ContentsChanged_Event_Fires_LoadStream ()
+		{
+			var eventcount = 0;
+
+			var tv = new TextView {
+				Width = 50,
+				Height = 10,
+				ContentsChanged = (e) => {
+					eventcount++;
+				}
+			};
+
+			var text = "This is the first line.\r\nThis is the second line.\r\n";
+			tv.LoadStream (new System.IO.MemoryStream (System.Text.Encoding.ASCII.GetBytes (text)));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}", tv.Text);
+			
+			Assert.Equal (1, eventcount);
+		}
+
+		[Fact]
+		public void ContentsChanged_Event_Fires_LoadFile ()
+		{
+			var eventcount = 0;
+
+			var tv = new TextView {
+				Width = 50,
+				Height = 10,
+				ContentsChanged = (e) => {
+					eventcount++;
+				}
+			};
+			var fileName = "textview.txt";
+			System.IO.File.WriteAllText (fileName, "This is the first line.\r\nThis is the second line.\r\n") ;
+
+			tv.LoadFile (fileName);
+			Assert.Equal (1, eventcount);
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}", tv.Text);
+		}
 	}
 }

+ 2 - 0
UnitTests/ToplevelTests.cs

@@ -431,6 +431,7 @@ namespace Terminal.Gui.Core {
 			var top = Application.Top;
 			Assert.Null (Application.MdiTop);
 			top.IsMdiContainer = true;
+			Application.Begin (top);
 			Assert.Equal (Application.Top, Application.MdiTop);
 
 			var isRunning = true;
@@ -469,6 +470,7 @@ namespace Terminal.Gui.Core {
 			Assert.Null (top.MostFocused);
 			Assert.Equal (win1.Subviews [0], win1.Focused);
 			Assert.Equal (tf1W1, win1.MostFocused);
+			Assert.True (win1.IsMdiChild);
 			Assert.Single (Application.MdiChildes);
 			Application.Begin (win2);
 			Assert.Equal (new Rect (0, 0, 40, 25), win2.Frame);

Some files were not shown because too many files changed in this diff