Преглед на файлове

Wizard fixes (#1802)

* Fixes #1791. Added Pos/Dim Function feature to automate layout.

* Added PosFunc/DimFunc class. and some more features.

* Fixes #1793. ScrollBarView is hiding if the host fit the available space.

* Fixes #1791. View not turn off AutoSize if TextFormatter.Size fit the Anchor.

* Done requested changes.

* Addressing feedback

* Added more AutoSize unit tests.

* wip

* Refactored and enhanced API

* Fixed test

Co-authored-by: BDisp <[email protected]>
Tig Kindel преди 3 години
родител
ревизия
2451c19d15
променени са 6 файла, в които са добавени 297 реда и са изтрити 108 реда
  1. 0 2
      Terminal.Gui/Core/View.cs
  2. 4 2
      Terminal.Gui/Views/TextView.cs
  3. 190 91
      Terminal.Gui/Windows/Wizard.cs
  4. 12 0
      UICatalog/Scenarios/Dialogs.cs
  5. 59 12
      UICatalog/Scenarios/Wizards.cs
  6. 32 1
      UnitTests/WizardTests.cs

+ 0 - 2
Terminal.Gui/Core/View.cs

@@ -571,7 +571,6 @@ namespace Terminal.Gui {
 				if (autoSize && value.Anchor (0) != TextFormatter.Size.Width
 					- (TextFormatter.IsHorizontalDirection (TextDirection)
 					&& TextFormatter.Text.Contains (HotKeySpecifier) ? 1 : 0)) {
-
 					autoSize = false;
 				}
 				SetMinWidthHeight ();
@@ -600,7 +599,6 @@ namespace Terminal.Gui {
 				if (autoSize && value.Anchor (0) != TextFormatter.Size.Height
 					- (TextFormatter.IsVerticalDirection (TextDirection)
 					&& TextFormatter.Text.Contains (HotKeySpecifier) ? 1 : 0)) {
-
 					autoSize = false;
 				}
 				SetMinWidthHeight ();

+ 4 - 2
Terminal.Gui/Views/TextView.cs

@@ -1659,8 +1659,10 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Gets or sets a value indicating whether pressing the TAB key in a <see cref="TextView"/>
-		/// types a TAB character in the view instead of moving the focus to the next view in the tab order.
+		/// Gets or sets whether the <see cref="TextView"/> inserts a tab character into the text or ignores 
+		/// tab input. If set to `false` and the user presses the tab key (or shift-tab) the focus will move to the
+		/// next view (or previous with shift-tab). The default is `true`; if the user presses the tab key, a tab 
+		/// character will be inserted into the text.
 		/// </summary>
 		public bool AllowsTab {
 			get => allowsTab;

+ 190 - 91
Terminal.Gui/Windows/Wizard.cs

@@ -30,6 +30,8 @@ namespace Terminal.Gui {
 			// TODO: Update Wizard title when step title is changed if step is current - this will require step to slueth it's parent 
 			private ustring title;
 
+			// The controlPane is a separate view, so when devs add controls to the Step and help is visible, Y = Pos.AnchorEnd()
+			// will work as expected.
 			private View controlPane = new FrameView ();
 
 			/// <summary>
@@ -74,8 +76,8 @@ namespace Terminal.Gui {
 			public WizardStep (ustring title)
 			{
 				this.Title = title; // this.Title holds just the "Wizard Title"; base.Title holds "Wizard Title - Step Title"
-				this.ColorScheme = Colors.Menu;
-		
+				this.ColorScheme = Colors.Dialog;
+
 				Y = 0;
 				Height = Dim.Fill (1); // for button frame
 				Width = Dim.Fill ();
@@ -178,7 +180,7 @@ namespace Terminal.Gui {
 				if (showControls) {
 					if (showHelp) {
 						Controls.Width = Dim.Percent (70);
-						helpTextView.X = Pos.Right (Controls) ;
+						helpTextView.X = Pos.Right (Controls);
 						helpTextView.Width = Dim.Fill ();
 
 					} else {
@@ -195,29 +197,7 @@ namespace Terminal.Gui {
 				Controls.Visible = showControls;
 				helpTextView.Visible = showHelp;
 			}
-		}
-
-		/// <summary>
-		/// If the <see cref="CurrentStep"/> is not the first step in the wizard, this button causes
-		/// the <see cref="MovingBack"/> event to be fired and the wizard moves to the previous step. 
-		/// </summary>
-		/// <remarks>
-		/// Use the <see cref="MovingBack"></see> event to be notified when the user attempts to go back.
-		/// </remarks>
-		public Button BackButton { get => backBtn; }
-		private Button backBtn;
-
-		/// <summary>
-		/// If the <see cref="CurrentStep"/> is the last step in the wizard, this button causes
-		/// the <see cref="Finished"/> event to be fired and the wizard to close. If the step is not the last step,
-		/// the <see cref="MovingNext"/> event will be fired and the wizard will move next step. 
-		/// </summary>
-		/// <remarks>
-		/// Use the <see cref="MovingNext"></see> and <see cref="Finished"></see> events to be notified 
-		/// when the user attempts go to the next step or finish the wizard.
-		/// </remarks>
-		public Button NextFinishButton { get => nextfinishBtn; }
-		private Button nextfinishBtn;
+		} // WizardStep
 
 		/// <summary>
 		/// Initializes a new instance of the <see cref="Wizard"/> class using <see cref="LayoutStyle.Computed"/> positioning.
@@ -259,46 +239,90 @@ namespace Terminal.Gui {
 			nextfinishBtn.IsDefault = true;
 			AddButton (nextfinishBtn);
 
-			backBtn.Clicked += () => {
-				var args = new WizardStepEventArgs ();
-				MovingBack?.Invoke (args);
+			backBtn.Clicked += BackBtn_Clicked;
+			nextfinishBtn.Clicked += NextfinishBtn_Clicked;
+
+			Loaded += Wizard_Loaded;
+			Closing += Wizard_Closing;
+		}
+
+		private bool finishedPressed = false;
+
+		private void Wizard_Closing (ToplevelClosingEventArgs obj)
+		{
+			if (!finishedPressed) {
+				var args = new WizardButtonEventArgs ();
+				Cancelled?.Invoke (args);
+			}
+		}
+
+		private void Wizard_Loaded ()
+		{
+			foreach (var step in steps) {
+				step.Y = 0;
+			}
+			if (steps.Count > 0) {
+				CurrentStep = steps.First.Value;
+			}
+		}
+
+		private void NextfinishBtn_Clicked ()
+		{
+			if (CurrentStep == steps.Last.Value) {
+				var args = new WizardButtonEventArgs ();
+				Finished?.Invoke (args);
 				if (!args.Cancel) {
-					if (currentStep > 0) {
-						CurrentStep--;
-					}
+					finishedPressed = true;
+					Application.RequestStop (this);
 				}
-			};
-
-			nextfinishBtn.Clicked += () => {
-				if (currentStep == steps.Count - 1) {
-					var args = new WizardStepEventArgs ();
-					Finished?.Invoke (args);
-					if (!args.Cancel) {
-						Application.RequestStop (this);
-					}
-				} else {
-					var args = new WizardStepEventArgs ();
-					MovingNext?.Invoke (args);
-					if (!args.Cancel) {
-						CurrentStep++;
+			} else {
+				var args = new WizardButtonEventArgs ();
+				MovingNext?.Invoke (args);
+				if (!args.Cancel) {
+					var current = steps.Find (CurrentStep);
+					if (current != null && current.Next != null) {
+						GotoStep (current.Next.Value);
 					}
 				}
-			};
+			}
+		}
 
-			Loaded += () => {
-				foreach (var step in steps) {
-					step.Y = 0;
+		private void BackBtn_Clicked ()
+		{
+			var args = new WizardButtonEventArgs ();
+			MovingBack?.Invoke (args);
+			if (!args.Cancel) {
+				var current = steps.Find (CurrentStep);
+				if (current != null && current.Previous != null) {
+					GotoStep (current.Previous.Value);
 				}
-				if (steps.Count > 0) {
+			}
+		}
 
-					CurrentStep = 0;
-				}
-			};
+		private LinkedList<WizardStep> steps = new LinkedList<WizardStep> ();
+		private WizardStep currentStep = null;
 
-		}
+		/// <summary>
+		/// If the <see cref="CurrentStep"/> is not the first step in the wizard, this button causes
+		/// the <see cref="MovingBack"/> event to be fired and the wizard moves to the previous step. 
+		/// </summary>
+		/// <remarks>
+		/// Use the <see cref="MovingBack"></see> event to be notified when the user attempts to go back.
+		/// </remarks>
+		public Button BackButton { get => backBtn; }
+		private Button backBtn;
 
-		private List<WizardStep> steps = new List<WizardStep> ();
-		private int currentStep = 0;
+		/// <summary>
+		/// If the <see cref="CurrentStep"/> is the last step in the wizard, this button causes
+		/// the <see cref="Finished"/> event to be fired and the wizard to close. If the step is not the last step,
+		/// the <see cref="MovingNext"/> event will be fired and the wizard will move next step. 
+		/// </summary>
+		/// <remarks>
+		/// Use the <see cref="MovingNext"></see> and <see cref="Finished"></see> events to be notified 
+		/// when the user attempts go to the next step or finish the wizard.
+		/// </remarks>
+		public Button NextFinishButton { get => nextfinishBtn; }
+		private Button nextfinishBtn;
 
 		/// <summary>
 		/// Adds a step to the wizard. The Next and Back buttons navigate through the added steps in the
@@ -308,8 +332,9 @@ namespace Terminal.Gui {
 		/// <remarks>The "Next..." button of the last step added will read "Finish" (unless changed from default).</remarks>
 		public void AddStep (WizardStep newStep)
 		{
-			steps.Add (newStep);
+			steps.AddLast (newStep);
 			this.Add (newStep);
+			SetNeedsLayout ();
 		}
 
 		/// <summary>
@@ -322,7 +347,7 @@ namespace Terminal.Gui {
 			}
 			set {
 				wizardTitle = value;
-				base.Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + steps [currentStep].Title : string.Empty)}";
+				base.Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + currentStep.Title : string.Empty)}";
 			}
 		}
 		private ustring wizardTitle = ustring.Empty;
@@ -330,16 +355,16 @@ namespace Terminal.Gui {
 		/// <summary>	
 		/// <see cref="EventArgs"/> for <see cref="WizardStep"/> transition events.
 		/// </summary>
-		public class WizardStepEventArgs : EventArgs {
+		public class WizardButtonEventArgs : EventArgs {
 			/// <summary>
 			/// Set to true to cancel the transition to the next step.
 			/// </summary>
 			public bool Cancel { get; set; }
 
 			/// <summary>
-			/// Initializes a new instance of <see cref="WizardStepEventArgs"/>
+			/// Initializes a new instance of <see cref="WizardButtonEventArgs"/>
 			/// </summary>
-			public WizardStepEventArgs ()
+			public WizardButtonEventArgs ()
 			{
 				Cancel = false;
 			}
@@ -349,7 +374,7 @@ namespace Terminal.Gui {
 		/// This event is raised when the Back button in the <see cref="Wizard"/> is clicked. The Back button is always
 		/// the first button in the array of Buttons passed to the <see cref="Wizard"/> constructor, if any.
 		/// </summary>
-		public event Action<WizardStepEventArgs> MovingBack;
+		public event Action<WizardButtonEventArgs> MovingBack;
 
 		/// <summary>
 		/// This event is raised when the Next/Finish button in the <see cref="Wizard"/> is clicked. The Next/Finish button is always
@@ -357,7 +382,7 @@ namespace Terminal.Gui {
 		/// raised if the <see cref="CurrentStep"/> is the last Step in the Wizard flow 
 		/// (otherwise the <see cref="Finished"/> event is raised).
 		/// </summary>
-		public event Action<WizardStepEventArgs> MovingNext;
+		public event Action<WizardButtonEventArgs> MovingNext;
 
 		/// <summary>
 		/// This event is raised when the Next/Finish button in the <see cref="Wizard"/> is clicked. The Next/Finish button is always
@@ -365,69 +390,143 @@ namespace Terminal.Gui {
 		/// raised if the <see cref="CurrentStep"/> is the last Step in the Wizard flow 
 		/// (otherwise the <see cref="Finished"/> event is raised).
 		/// </summary>
-		public event Action<WizardStepEventArgs> Finished;
+		public event Action<WizardButtonEventArgs> Finished;
+
 
 		/// <summary>
-		/// This event is raised when the current step )<see cref="CurrentStep"/>) in the <see cref="Wizard"/> changes.
+		/// This event is raised when the user has cancelled the <see cref="Wizard"/> (with Ctrl-Q or ESC).
 		/// </summary>
-		public event Action<CurrentStepChangedEventArgs> CurrentStepChanged;
+		public event Action<WizardButtonEventArgs> Cancelled;
 
 		/// <summary>
 		/// <see cref="EventArgs"/> for <see cref="WizardStep"/> events.
 		/// </summary>
-		public class CurrentStepChangedEventArgs : EventArgs {
+		public class StepChangeEventArgs : EventArgs {
+			/// <summary>
+			/// The current (or previous) <see cref="WizardStep"/>.
+			/// </summary>
+			public WizardStep OldStep { get; }
+
+			/// <summary>
+			/// The <see cref="WizardStep"/> the <see cref="Wizard"/> is changing to or has changed to.
+			/// </summary>
+			public WizardStep NewStep { get; }
+
 			/// <summary>
-			/// The new current <see cref="WizardStep"/>.
+			/// Event handlers can set to true before returning to cancel the step transition.
 			/// </summary>
-			public int CurrentStepIndex { get; }
+			public bool Cancel { get; set; }
 
 			/// <summary>
-			/// Initializes a new instance of <see cref="CurrentStepChangedEventArgs"/>
+			/// Initializes a new instance of <see cref="StepChangeEventArgs"/>
 			/// </summary>
-			/// <param name="currentStepIndex">The new current <see cref="WizardStep"/>.</param>
-			public CurrentStepChangedEventArgs (int currentStepIndex)
+			/// <param name="oldStep">The current <see cref="WizardStep"/>.</param>
+			/// <param name="newStep">The new <see cref="WizardStep"/>.</param>
+			public StepChangeEventArgs (WizardStep oldStep, WizardStep newStep)
 			{
-				CurrentStepIndex = currentStepIndex;
+				OldStep = oldStep;
+				NewStep = newStep;
+				Cancel = false;
 			}
 		}
 
+		/// <summary>
+		/// This event is raised when the current <see cref="CurrentStep"/>) is about to change. Use <see cref="StepChangeEventArgs.Cancel"/> 
+		/// to abort the transition.
+		/// </summary>
+		public event Action<StepChangeEventArgs> StepChanging;
+
+		/// <summary>
+		/// This event is raised after the <see cref="Wizard"/> has changed the <see cref="CurrentStep"/>. 
+		/// </summary>
+		public event Action<StepChangeEventArgs> StepChanged;
+
 		/// <summary>
 		/// Gets or sets the currently active <see cref="WizardStep"/>.
 		/// </summary>
-		public int CurrentStep {
+		public WizardStep CurrentStep {
 			get => currentStep;
 			set {
-				currentStep = value;
-				OnCurrentStepChanged ();
+				GotoStep (value);
 			}
 		}
 
 		/// <summary>
-		/// Called when the current <see cref="WizardStep"/> has changed (<see cref="CurrentStep"/>).
+		/// Called when the <see cref="Wizard"/> is about to transition to another <see cref="WizardStep"/>. Fires the <see cref="StepChanging"/> event. 
+		/// </summary>
+		/// <param name="oldStep">The step the Wizard is about to change from</param>
+		/// <param name="newStep">The step the Wizard is about to change to</param>
+		/// <returns>True if the change is to be cancelled.</returns>
+		public virtual bool OnStepChanging (WizardStep oldStep, WizardStep newStep)
+		{
+			var args = new StepChangeEventArgs (oldStep, newStep);
+			StepChanging?.Invoke (args);
+			return args.Cancel;
+		}
+
+
+		/// <summary>
+		/// Called when the <see cref="Wizard"/> has completed transition to a new <see cref="WizardStep"/>. Fires the <see cref="StepChanged"/> event. 
 		/// </summary>
-		public virtual void OnCurrentStepChanged ()
+		/// <param name="oldStep">The step the Wizard changed from</param>
+		/// <param name="newStep">The step the Wizard has changed to</param>
+		/// <returns>True if the change is to be cancelled.</returns>
+		public virtual bool OnStepChanged (WizardStep oldStep, WizardStep newStep)
 		{
-			CurrentStepChanged?.Invoke (new CurrentStepChangedEventArgs (currentStep));
-			// Hide all but the first step
+			var args = new StepChangeEventArgs (oldStep, newStep);
+			StepChanged?.Invoke (args);
+			return args.Cancel;
+		}
+		/// <summary>
+		/// Changes to the specified <see cref="WizardStep"/>.
+		/// </summary>
+		/// <param name="newStep">The step to go to.</param>
+		/// <returns>True if the transition to the step succeeded. False if the step was not found or the operation was cancelled.</returns>
+		public bool GotoStep (WizardStep newStep)
+		{
+			if (OnStepChanging (currentStep, newStep)) {
+				return false;
+			}
+
+			// Hide all but the new step
 			foreach (WizardStep step in steps) {
-				step.Visible = (steps [currentStep] == step);
+				step.Visible = (step == newStep);
 			}
 
-			// TODO: Add support for "Wizard Title - Step Title"
-			base.Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + steps [currentStep].Title : string.Empty)}";
+			base.Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + newStep.Title : string.Empty)}";
+
+			// TODO: Move strings to loc
 
-			backBtn.Text = steps [currentStep].BackButtonText != ustring.Empty ? steps [currentStep].BackButtonText : "_Back";
-			if (currentStep == 0) {
-				backBtn.Visible = false;
+			// Configure the Back button
+			backBtn.Text = newStep.BackButtonText != ustring.Empty ? newStep.BackButtonText : "_Back";
+			backBtn.Visible = (newStep != steps.First.Value);
+
+			// Configure the Next/Finished button
+			if (newStep == steps.Last.Value) {
+				nextfinishBtn.Text = newStep.NextButtonText != ustring.Empty ? newStep.NextButtonText : "Fi_nish";
 			} else {
-				backBtn.Visible = true;
+				nextfinishBtn.Text = newStep.NextButtonText != ustring.Empty ? newStep.NextButtonText : "_Next...";
 			}
 
-			if (currentStep == steps.Count - 1) {
-				nextfinishBtn.Text = steps [currentStep].NextButtonText != ustring.Empty ? steps [currentStep].NextButtonText : "Fi_nish";
+			// Set focus to the nav buttons
+			if (backBtn.HasFocus) {
+				backBtn.SetFocus ();
 			} else {
-				nextfinishBtn.Text = steps [currentStep].NextButtonText != ustring.Empty ? steps [currentStep].NextButtonText : "_Next...";
+				nextfinishBtn.SetFocus ();
+			}
+
+			var oldStep = currentStep; 
+			currentStep = newStep;
+
+			LayoutSubviews ();
+			Redraw (this.Bounds);
+
+			if (OnStepChanged (oldStep, currentStep)) {
+				// For correctness we do this, but it's meaningless because there's nothing to cancel
+				return false;
 			}
+
+			return true;
 		}
 	}
 }

+ 12 - 0
UICatalog/Scenarios/Dialogs.cs

@@ -215,6 +215,18 @@ namespace UICatalog.Scenarios {
 					};
 					dialog.Add (add);
 
+					var addChar = new Button ($"Add a {Char.ConvertFromUtf32(CODE_POINT)} to each button") {
+						X = Pos.Center (),
+						Y = Pos.Center () + 1
+					};
+					addChar.Clicked += () => {
+						foreach (var button in buttons) {
+							button.Text += Char.ConvertFromUtf32 (CODE_POINT);
+						}
+						dialog.LayoutSubviews ();
+					};
+					dialog.Add (addChar);
+
 					Application.Run (dialog);
 					buttonPressedLabel.Text = $"{clicked}";
 

+ 59 - 12
UICatalog/Scenarios/Wizards.cs

@@ -16,7 +16,6 @@ namespace UICatalog.Scenarios {
 				X = Pos.Center (),
 				Y = 0,
 				Width = Dim.Percent (75),
-				Height = 10,
 				ColorScheme = Colors.Base,
 			};
 			Win.Add (frame);
@@ -69,9 +68,18 @@ namespace UICatalog.Scenarios {
 			};
 			frame.Add (titleEdit);
 
+			var useStepView = new CheckBox () {
+				Text = "Add 3rd step controls to WizardStep instead of WizardStep.Controls",
+				Checked = false,
+				X = Pos.Left (titleEdit),
+				Y = Pos.Bottom (titleEdit)
+			};
+			frame.Add (useStepView);
+
+
 			void Top_Loaded ()
 			{
-				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + 2;
+				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + Dim.Height (useStepView) + 2;
 				Top.Loaded -= Top_Loaded;
 			}
 			Top.Loaded += Top_Loaded;
@@ -127,6 +135,11 @@ namespace UICatalog.Scenarios {
 						actionLabel.Text = "Finished";
 					};
 
+					wizard.Cancelled += (args) => {
+						//args.Cancel = true;
+						actionLabel.Text = "Cancelled";
+					};
+
 					// Add 1st step
 					var firstStep = new Wizard.WizardStep ("End User License Agreement");
 					wizard.AddStep (firstStep);
@@ -138,6 +151,15 @@ namespace UICatalog.Scenarios {
 					var secondStep = new Wizard.WizardStep ("Second Step");
 					wizard.AddStep (secondStep);
 					secondStep.HelpText = "This is the help text for the Second Step.\n\nPress the button to see a message box.\n\nEnter name too.";
+
+
+					View viewForControls = secondStep.Controls;
+					ustring frameMsg = "Added to WizardStep.Controls";
+					if (useStepView.Checked) {
+						viewForControls = secondStep;
+						frameMsg = "Added to WizardStep directly";
+					}
+
 					var buttonLbl = new Label () { Text = "Second Step Button: ", AutoSize = true, X = 1, Y = 1 };
 					var button = new Button () {
 						Text = "Press Me",
@@ -147,13 +169,28 @@ namespace UICatalog.Scenarios {
 					button.Clicked += () => {
 						MessageBox.Query ("Wizard Scenario", "The Second Step Button was pressed.");
 					};
-					secondStep.Controls.Add (buttonLbl, button);
+					viewForControls.Add (buttonLbl, button);
 					var lbl = new Label () { Text = "First Name: ", AutoSize = true, X = 1, Y = Pos.Bottom (buttonLbl) };
 					var firstNameField = new TextField () { Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) };
-					secondStep.Controls.Add (lbl, firstNameField);
+					viewForControls.Add (lbl, firstNameField);
 					lbl = new Label () { Text = "Last Name:  ", AutoSize = true, X = 1, Y = Pos.Bottom (lbl) };
 					var lastNameField = new TextField () { Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) };
-					secondStep.Controls.Add (lbl, lastNameField);
+					viewForControls.Add (lbl, lastNameField);
+					var checkBox = new CheckBox () { Text = "Un-check me!", Checked = true, X = Pos.Left (lastNameField), Y = Pos.Bottom (lastNameField) };
+					viewForControls.Add (checkBox);
+
+					// Add a frame to demonstrate difference between adding controls to
+					// WizardStep.Controls vs. WizardStep directly. This is here to demonstrate why 
+					// adding to .Controls is preferred.
+					var frame = new FrameView ($"A Broken Frame - {frameMsg}") {
+						X = 0,
+						Y = Pos.Bottom (checkBox) + 2,
+						Width = Dim.Fill (),
+						Height = 4,
+						//ColorScheme = Colors.Error,
+					};
+					frame.Add (new TextField ("This is a TextField inside of the frame."));
+					viewForControls.Add (frame);
 
 					// Add 3rd step
 					var thirdStep = new Wizard.WizardStep ("Third Step");
@@ -169,20 +206,35 @@ namespace UICatalog.Scenarios {
 					thirdStep.Controls.Add (progLbl, progressBar);
 
 					// Add 4th step
-					var fourthStep = new Wizard.WizardStep ("Hidden Help pane");
+					var fourthStep = new Wizard.WizardStep ("Step Four");
 					wizard.AddStep (fourthStep);
 					fourthStep.ShowHelp = false;
 					var someText = new TextView () {
-						Text = "This step shows how to hide the Help pane. The control pane contains this TextView.",
+						Text = "This step (Step Four) shows how to hide the Help pane. The control pane contains this TextView (but it's hard to tell it's a TextView because of Issue #1800).",
 						X = 0,
 						Y = 0,
 						Width = Dim.Fill (),
 						Height = Dim.Fill (),
 						WordWrap = true,
+						AllowsTab = false
 					};
 					fourthStep.Controls.Add (someText);
+					//fourthStep.NextButtonText = "4";
 					var scrollBar = new ScrollBarView (someText, true);
 
+					wizard.StepChanging += (args) => {
+						if (args.NewStep == fourthStep) {
+							var btn = MessageBox.ErrorQuery ("Wizards", "Move to Step Four?", "Yes", "No");
+							args.Cancel = btn == 1;
+						}
+					};
+
+					wizard.StepChanged += (args) => {
+						if (args.NewStep == fourthStep) {
+							var btn = MessageBox.ErrorQuery ("Wizards", "Yay. Moved to Step Four", "Ok");
+						}
+					};
+
 					scrollBar.ChangedPosition += () => {
 						someText.TopRow = scrollBar.Position;
 						if (someText.TopRow != scrollBar.Position) {
@@ -216,13 +268,8 @@ namespace UICatalog.Scenarios {
 					wizard.AddStep (lastStep);
 					lastStep.HelpText = "The wizard is complete! Press the Finish button to continue. Pressing ESC will cancel the wizard.";
 
-
 					// TODO: Demo setting initial Pane
 
-					wizard.Finished += (args) => {
-						Application.RequestStop (wizard);
-					};
-
 					Application.Run (wizard);
 
 				} catch (FormatException) {

+ 32 - 1
UnitTests/WizardTests.cs

@@ -93,6 +93,37 @@ namespace Terminal.Gui.Views {
 		// and that the title is correct
 		public void OneStepWizard_Shows ()
 		{
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+			var stepTitle = "ABCD";
+
+			int width = 30;
+			int height = 7;
+			d.SetBufferSize (width, height);
+
+			var btnBackText = "Back";
+			var btnBack = string.Empty; // $"{d.LeftBracket} {btnBackText} {d.RightBracket}";
+			var btnNextText = "Finish"; // "Next";
+			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
+
+			var topRow = $"{d.ULDCorner} {title} - {stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 7)}{d.URDCorner}";
+			var row2 = $"{d.VDLine}{new String (' ', width - 2)}{d.VDLine}";
+			var row3 = row2;
+			var row4 = row3;
+			var separatorRow = $"{d.VDLine}{new String (d.HLine.ToString () [0], width - 2)}{d.VDLine}";
+			var buttonRow = $"{d.VDLine}{btnBack}{new String (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{d.VDLine}";
+			var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
+
+			var wizard = new Wizard (title) { Width = width, Height = height };
+			wizard.AddStep (new Wizard.WizardStep (stepTitle));
+			//wizard.LayoutSubviews ();
+			var firstIteration = false;
+			var runstate = Application.Begin (wizard);
+			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{row2}\n{row3}\n{row4}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -150,7 +181,7 @@ namespace Terminal.Gui.Views {
 			var separatorRow = $"{d.VDLine}{new String (d.HLine.ToString () [0], width - 2)}{d.VDLine}";
 
 			// Once this is fixed, revert to commented out line: https://github.com/migueldeicaza/gui.cs/issues/1791
-			var buttonRow = $"{d.VDLine}{new String (' ', width - btnNext.Length - 3)}{btnNext} {d.VDLine}";
+			var buttonRow = $"{d.VDLine}{new String (' ', width - btnNext.Length - 2)}{btnNext}{d.VDLine}";
 			//var buttonRow = $"{d.VDLine}{new String (' ', width - btnNext.Length - 2)}{btnNext}{d.VDLine}";
 			var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";