From 492966048cd16cf635753ee8a74556dae3608b0d Mon Sep 17 00:00:00 2001 From: Tig Kindel Date: Thu, 21 Jul 2022 08:57:25 -0400 Subject: [PATCH] Fixes #1849. Wizard as non-popup is broken (#1853) * trying to make it work * Fixes #1849. Wizard as non-modal doesn't work * Fixes #1855. Window and Frame content view without the margin frame. * Fixing layout of non-modal * WizardSTep is now a FrameView * Now use Modal = false to set visual style automatically * Removed Controls as an explicit construct. Now just Add to WizardStep Co-authored-by: BDisp --- Terminal.Gui/Core/Application.cs | 8 +- Terminal.Gui/Core/Toplevel.cs | 2 +- Terminal.Gui/Windows/Dialog.cs | 2 +- Terminal.Gui/Windows/Wizard.cs | 259 ++++++++++++++--------- UICatalog/Properties/launchSettings.json | 4 + UICatalog/Scenarios/Dialogs.cs | 9 +- UICatalog/Scenarios/WizardAsView.cs | 102 +++++++++ UICatalog/Scenarios/Wizards.cs | 113 ++++++---- UnitTests/WizardTests.cs | 2 +- 9 files changed, 353 insertions(+), 148 deletions(-) create mode 100644 UICatalog/Scenarios/WizardAsView.cs diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index cb8933e8e..7332b994e 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -1099,8 +1099,12 @@ namespace Terminal.Gui { { if (_initialized && Driver != null) { var top = new T (); - if (top.GetType ().BaseType != typeof (Toplevel)) { - throw new ArgumentException (top.GetType ().BaseType.Name); + 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 { diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index 6c30899ed..2dd70d66f 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -164,7 +164,7 @@ namespace Terminal.Gui { /// /// Called from before the is redraws for the first time. /// - internal virtual void OnLoaded () + virtual public void OnLoaded () { Loaded?.Invoke (); } diff --git a/Terminal.Gui/Windows/Dialog.cs b/Terminal.Gui/Windows/Dialog.cs index 8da3b9b4a..84da2f026 100644 --- a/Terminal.Gui/Windows/Dialog.cs +++ b/Terminal.Gui/Windows/Dialog.cs @@ -182,7 +182,7 @@ namespace Terminal.Gui { } else { if (i == 0) { // first (leftmost) button - always hard flush left - var left = Bounds.Width - 2; + var left = Bounds.Width - ((Border.DrawMarginFrame ? 2 : 0) + Border.BorderThickness.Left + Border.BorderThickness.Right); button.X = Pos.AnchorEnd (left); } else { shiftLeft += button.Frame.Width + (spacing); diff --git a/Terminal.Gui/Windows/Wizard.cs b/Terminal.Gui/Windows/Wizard.cs index 2ef215f91..2b9ddc6c4 100644 --- a/Terminal.Gui/Windows/Wizard.cs +++ b/Terminal.Gui/Windows/Wizard.cs @@ -11,14 +11,18 @@ namespace Terminal.Gui { /// bottom of the Wizard view are customizable buttons enabling the user to navigate forward and backward through the Wizard. /// /// + /// The Wizard can be shown either as a modal pop-up (the default) or embedded in a containing . To use a a , + /// set to `false`. /// public class Wizard : Dialog { /// - /// One step for the Wizard. The view hosts two sub-views: 1) add s to , - /// 2) use to set the contents of the that shows on the - /// right side. Use and to - /// control wether the control or help pane are shown. + /// One step for the Wizard. The view is divided horizontally in two. On the left is the + /// content view where s can be added, On the right is the help for the step. + /// Set to set the help text. If the help text is empty the help pane will not + /// be shown. + /// If there are no Views added to the WizardStep, and the help text is not empty the help text will + /// fill the wizard step. /// /// /// If s are added, do not set to true as this will conflict @@ -29,11 +33,11 @@ namespace Terminal.Gui { /// To enable or disable a step from being shown to the user, set . /// /// - public class WizardStep : View { + public class WizardStep : FrameView { /// /// The title of the . /// - public ustring Title { + public new ustring Title { get => title; set { if (!OnTitleChanging (title, value)) { @@ -114,23 +118,21 @@ namespace Terminal.Gui { // 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 (); + private View contentView = new View (); /// - /// THe pane that holds the controls for the . Use `Add(View`) to add - /// controls. Note that the Controls view is sized to take 70% of the Wizard's width and the - /// takes the other 30%. This can be adjusted by setting `Width` from `Dim.Percent(70)` to - /// another value. If is set to `false` the control pane will fill the entire - /// Wizard. - /// - public View Controls { get => controlPane; } - - /// - /// Sets or gets help text for the .If is set to - /// `false` the control pane will fill the entire wizard. + /// Sets or gets help text for the .If is empty + /// the help pane will not be visible and the content will fill the entire WizardStep. /// /// The help text is displayed using a read-only . - public ustring HelpText { get => helpTextView.Text; set => helpTextView.Text = value; } + public ustring HelpText { + get => helpTextView.Text; + set { + helpTextView.Text = value; + ShowHide (); + SetNeedsDisplay (); + } + } private TextView helpTextView = new TextView (); /// @@ -158,23 +160,14 @@ 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.Dialog; + this.Border.BorderStyle = BorderStyle.Rounded; - Y = 0; - Height = Dim.Fill (1); // for button frame - Width = Dim.Fill (); + base.Add (contentView); - Controls.ColorScheme = Colors.Dialog; - Controls.Border.BorderStyle = BorderStyle.None; - Controls.Border.Padding = new Thickness (0); - Controls.Border.BorderThickness = new Thickness (0); - this.Add (Controls); - - helpTextView.ColorScheme = Colors.Menu; - helpTextView.Y = 0; + helpTextView.ColorScheme = Colors.TopLevel; helpTextView.ReadOnly = true; helpTextView.WordWrap = true; - this.Add (helpTextView); + base.Add (helpTextView); ShowHide (); @@ -222,70 +215,79 @@ namespace Terminal.Gui { scrollBar.LayoutSubviews (); scrollBar.Refresh (); }; - this.Add (scrollBar); + base.Add (scrollBar); } - //public override void OnEnabledChanged() - //{ - // if (Enabled) { } - // base.OnEnabledChanged (); - //} - - /// - /// If true (the default) the help will be visible. If false, the help will not be shown and the control pane will - /// fill the wizard step. + /// Does the work to show and hide the contentView and helpView as appropriate /// - public bool ShowHelp { - get => showHelp; - set { - showHelp = value; - ShowHide (); - } - } - private bool showHelp = true; - - /// - /// If true (the default) the View will be visible. If false, the controls will not be shown and the help will - /// fill the wizard step. - /// - public bool ShowControls { - get => showControls; - set { - showControls = value; - ShowHide (); - } - } - private bool showControls = true; - - /// - /// Does the work to show and hide the controls, help, and buttons as appropriate - /// - private void ShowHide () + internal void ShowHide () { - Controls.Height = Dim.Fill (1); - helpTextView.Height = Dim.Fill (1); + contentView.Height = Dim.Fill (); + helpTextView.Height = Dim.Fill (); helpTextView.Width = Dim.Fill (); - if (showControls) { - if (showHelp) { - Controls.Width = Dim.Percent (70); - helpTextView.X = Pos.Right (Controls); + if (contentView.InternalSubviews?.Count > 0) { + if (helpTextView.Text.Length > 0) { + contentView.Width = Dim.Percent (70); + helpTextView.X = Pos.Right (contentView); helpTextView.Width = Dim.Fill (); } else { - Controls.Width = Dim.Percent (100); + contentView.Width = Dim.Percent (100); } } else { - if (showHelp) { + if (helpTextView.Text.Length > 0) { helpTextView.X = 0; } else { // Error - no pane shown } } - Controls.Visible = showControls; - helpTextView.Visible = showHelp; + contentView.Visible = contentView.InternalSubviews?.Count > 0; + helpTextView.Visible = helpTextView.Text.Length > 0; + } + + /// + /// Add the specified to the . + /// + /// to add to this container + public override void Add (View view) + { + contentView.Add (view); + if (view.CanFocus) + CanFocus = true; + ShowHide (); + } + + /// + /// Removes a from . + /// + /// + /// + public override void Remove (View view) + { + if (view == null) + return; + + SetNeedsDisplay (); + var touched = view.Frame; + contentView.Remove (view); + + if (contentView.InternalSubviews.Count < 1) + this.CanFocus = false; + ShowHide (); + } + + /// + /// Removes all s from the . + /// + /// + /// + public override void RemoveAll () + { + contentView.RemoveAll (); + ShowHide (); } } // WizardStep @@ -316,12 +318,13 @@ namespace Terminal.Gui { // the left and right edge ButtonAlignment = ButtonAlignments.Justify; this.Border.BorderStyle = BorderStyle.Double; + this.Border.Padding = new Thickness (0); - // Add a horiz separator - var separator = new LineView (Graphs.Orientation.Horizontal) { - Y = Pos.AnchorEnd (2) - }; - Add (separator); + //// Add a horiz separator + //var separator = new LineView (Graphs.Orientation.Horizontal) { + // Y = Pos.AnchorEnd (2) + //}; + //Add (separator); // BUGBUG: Space is to work around https://github.com/migueldeicaza/gui.cs/issues/1812 backBtn = new Button (Strings.wzBack) { AutoSize = true }; @@ -338,6 +341,11 @@ namespace Terminal.Gui { Closing += Wizard_Closing; } + private void Wizard_Loaded () + { + CurrentStep = GetFirstStep (); // gets the first step if CurrentStep == null + } + private bool finishedPressed = false; private void Wizard_Closing (ToplevelClosingEventArgs obj) @@ -348,22 +356,19 @@ namespace Terminal.Gui { } } - private void Wizard_Loaded () - { - foreach (var step in steps) { - step.Y = 0; - } - CurrentStep = GetNextStep (); // gets the first step if CurrentStep == null - } - private void NextfinishBtn_Clicked () { - if (CurrentStep == GetLastStep()) { + if (CurrentStep == GetLastStep ()) { var args = new WizardButtonEventArgs (); Finished?.Invoke (args); if (!args.Cancel) { finishedPressed = true; - Application.RequestStop (this); + if (IsCurrentTop) { + Application.RequestStop (this); + } else { + // Wizard was created as a non-modal (just added to another View). + // Do nothing + } } } else { var args = new WizardButtonEventArgs (); @@ -374,6 +379,19 @@ namespace Terminal.Gui { } } + /// + public override bool ProcessKey (KeyEvent kb) + { + switch (kb.Key) { + case Key.Esc: + // Dialog causes ESC to close/cancel; we dont want that with wizard + // Use QuitKey instead. + return false; + } + return base.ProcessKey (kb); + } + + /// /// Causes the wizad to move to the next enabled step (or last step if is not set). /// If there is no previous step, does nothing. @@ -396,7 +414,7 @@ namespace Terminal.Gui { { LinkedListNode step = null; if (CurrentStep == null) { - // Get last step, assume it is next + // Get first step, assume it is next step = steps.First; } else { // Get the step after current @@ -518,10 +536,12 @@ namespace Terminal.Gui { /// The "Next..." button of the last step added will read "Finish" (unless changed from default). public void AddStep (WizardStep newStep) { - steps.AddLast (newStep); - this.Add (newStep); + SizeStep (newStep); + newStep.EnabledChanged += UpdateButtonsAndTitle; newStep.TitleChanged += (args) => UpdateButtonsAndTitle (); + steps.AddLast (newStep); + this.Add (newStep); UpdateButtonsAndTitle (); } @@ -679,6 +699,7 @@ namespace Terminal.Gui { // Hide all but the new step foreach (WizardStep step in steps) { step.Visible = (step == newStep); + step.ShowHide (); } var oldStep = currentStep; @@ -717,9 +738,55 @@ namespace Terminal.Gui { } else { nextfinishBtn.Text = CurrentStep.NextButtonText != ustring.Empty ? CurrentStep.NextButtonText : Strings.wzNext; // "_Next..."; } + + SizeStep (CurrentStep); + SetNeedsLayout (); LayoutSubviews (); Redraw (Bounds); } + + private void SizeStep (WizardStep step) + { + if (Modal) { + // If we're modal, then we expand the WizardStep so that the top and side + // borders and not visible. The bottom border is the separator above the buttons. + step.X = step.Y = -1; + step.Height = Dim.Fill (1); // for button frame + step.Width = Dim.Fill (-1); + } else { + // If we're not a modal, then we show the border around the WizardStep + step.X = step.Y = 0; + step.Height = Dim.Fill (1); // for button frame + step.Width = Dim.Fill (0); + } + } + + /// + public new bool Modal { + get => base.Modal; + set { + base.Modal = value; + foreach (var step in steps) { + SizeStep (step); + } + if (base.Modal) { + ColorScheme = Colors.Dialog; + Border.BorderStyle = BorderStyle.Rounded; + Border.Effect3D = true; + Border.DrawMarginFrame = true; + } else { + if (SuperView != null) { + ColorScheme = SuperView.ColorScheme; + } else { + ColorScheme = Colors.Base; + } + CanFocus = true; + Border.Effect3D = false; + Border.BorderStyle = BorderStyle.None; + Border.DrawMarginFrame = false; + } + } + } } } \ No newline at end of file diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json index 37390f995..994b6b34d 100644 --- a/UICatalog/Properties/launchSettings.json +++ b/UICatalog/Properties/launchSettings.json @@ -18,6 +18,10 @@ "Buttons": { "commandName": "Project", "commandLineArgs": "Buttons" + }, + "WizardAsView": { + "commandName": "Project", + "commandLineArgs": "WizardAsView" } } } \ No newline at end of file diff --git a/UICatalog/Scenarios/Dialogs.cs b/UICatalog/Scenarios/Dialogs.cs index 696ff1933..c3aac85a4 100644 --- a/UICatalog/Scenarios/Dialogs.cs +++ b/UICatalog/Scenarios/Dialogs.cs @@ -146,6 +146,8 @@ namespace UICatalog.Scenarios { }; showDialogButton.Clicked += () => { try { + Dialog dialog = null; + int width = 0; int.TryParse (widthEdit.Text.ToString (), out width); int height = 0; @@ -182,7 +184,7 @@ namespace UICatalog.Scenarios { // This tests dynamically adding buttons; ensuring the dialog resizes if needed and // the buttons are laid out correctly - var dialog = new Dialog (titleEdit.Text, width, height, + dialog = new Dialog (titleEdit.Text, width, height, buttons.ToArray ()) { ButtonAlignment = (Dialog.ButtonAlignments)styleRadioGroup.SelectedItem }; @@ -204,6 +206,7 @@ namespace UICatalog.Scenarios { button.Clicked += () => { clicked = buttonId; Application.RequestStop (); + }; buttons.Add (button); dialog.AddButton (button); @@ -223,10 +226,12 @@ namespace UICatalog.Scenarios { } dialog.LayoutSubviews (); }; + dialog.Closed += (args) => { + buttonPressedLabel.Text = $"{clicked}"; + }; dialog.Add (addChar); Application.Run (dialog); - buttonPressedLabel.Text = $"{clicked}"; } catch (FormatException) { buttonPressedLabel.Text = "Invalid Options"; diff --git a/UICatalog/Scenarios/WizardAsView.cs b/UICatalog/Scenarios/WizardAsView.cs new file mode 100644 index 000000000..3fd610e2b --- /dev/null +++ b/UICatalog/Scenarios/WizardAsView.cs @@ -0,0 +1,102 @@ +using NStack; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Terminal.Gui; + +namespace UICatalog.Scenarios { + [ScenarioMetadata (Name: "WizardAsView", Description: "Shows using the Wizard class in an non-modal way")] + [ScenarioCategory ("Dialogs")] + public class WizardAsView : Scenario { + + public override void Init (Toplevel top, ColorScheme colorScheme) + { + Top = Application.Top; + + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_File", new MenuItem [] { + new MenuItem ("_Restart Configuration...", "", () => MessageBox.Query ("Wizaard", "Are you sure you want to reset the Wizard and start over?", "Ok", "Cancel")), + new MenuItem ("Re_boot Server...", "", () => MessageBox.Query ("Wizaard", "Are you sure you want to reboot the server start over?", "Ok", "Cancel")), + new MenuItem ("_Shutdown Server...", "", () => MessageBox.Query ("Wizaard", "Are you sure you want to cancel setup and shutdown?", "Ok", "Cancel")), + }) + }); + Top.Add (menu); + + // No need for a Title because the border is disabled + var wizard = new Wizard () { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill (), + }; + + // Set Mdoal to false to cause the Wizard class to render without a frame and + // behave like an non-modal View (vs. a modal/pop-up Window). + wizard.Modal = false; + + wizard.MovingBack += (args) => { + //args.Cancel = true; + //actionLabel.Text = "Moving Back"; + }; + + wizard.MovingNext += (args) => { + //args.Cancel = true; + //actionLabel.Text = "Moving Next"; + }; + + wizard.Finished += (args) => { + //args.Cancel = true; + MessageBox.Query ("Setup Wizard", "Finished", "Ok"); + Application.RequestStop (); + }; + + // Add 1st step + var firstStep = new Wizard.WizardStep ("End User License Agreement"); + wizard.AddStep (firstStep); + firstStep.NextButtonText = "Accept!"; + firstStep.HelpText = "This is the End User License Agreement.\n\n\n\n\n\nThis is a test of the emergency broadcast system. This is a test of the emergency broadcast system.\nThis is a test of the emergency broadcast system.\n\n\nThis is a test of the emergency broadcast system.\n\nThis is a test of the emergency broadcast system.\n\n\n\nThe end of the EULA."; + + // Add 2nd step + 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 change the Title.\n\nIf First Name is empty the step will prevent moving to the next step."; + + var buttonLbl = new Label () { Text = "Second Step Button: ", X = 0, Y = 0 }; + var button = new Button () { + Text = "Press Me to Rename Step", + X = Pos.Right (buttonLbl), + Y = Pos.Top (buttonLbl) + }; + button.Clicked += () => { + secondStep.Title = "2nd Step"; + MessageBox.Query ("Wizard Scenario", "This Wizard Step's title was changed to '2nd Step'", "Ok"); + }; + secondStep.Add (buttonLbl, button); + var lbl = new Label () { Text = "First Name: ", X = Pos.Left (buttonLbl), Y = Pos.Bottom (buttonLbl) }; + var firstNameField = new TextField () { Text = "Number", Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) }; + secondStep.Add (lbl, firstNameField); + lbl = new Label () { Text = "Last Name: ", X = Pos.Left (buttonLbl), Y = Pos.Bottom (lbl) }; + var lastNameField = new TextField () { Text = "Six", Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) }; + secondStep.Add (lbl, lastNameField); + + // Add last step + var lastStep = new Wizard.WizardStep ("The last step"); + wizard.AddStep (lastStep); + lastStep.HelpText = "The wizard is complete!\n\nPress the Finish button to continue.\n\nPressing ESC will cancel the wizard."; + + // When run as a modal, Wizard gets a Loading event where it sets the + // Current Step. But when running non-modal it must be done manually. + wizard.CurrentStep = wizard.GetNextStep (); + + Top.Add (wizard); + Application.Run (Top); + } + + public override void Run () + { + // Do nothing in the override because we call Application.Run above + // (just to make it clear how the Top is being run and not the Wizard). + } + } +} diff --git a/UICatalog/Scenarios/Wizards.cs b/UICatalog/Scenarios/Wizards.cs index 07ebc827e..bed6cafdd 100644 --- a/UICatalog/Scenarios/Wizards.cs +++ b/UICatalog/Scenarios/Wizards.cs @@ -6,12 +6,14 @@ using System.Text; using Terminal.Gui; namespace UICatalog.Scenarios { - [ScenarioMetadata (Name: "Wizards", Description: "Demonstrates how to the Wizard class")] + [ScenarioMetadata (Name: "Wizards", Description: "Demonstrates the Wizard class")] [ScenarioCategory ("Dialogs")] public class Wizards : Scenario { public override void Setup () { + // Set the colorschem to Base so the non-modal part of the demo looks right Win.ColorScheme = Colors.Base; + var frame = new FrameView ("Wizard Options") { X = Pos.Center (), Y = 0, @@ -71,18 +73,9 @@ 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) + Dim.Height (useStepView) + 2; + frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + 2; Top.Loaded -= Top_Loaded; } Top.Loaded += Top_Loaded; @@ -93,17 +86,28 @@ namespace UICatalog.Scenarios { TextAlignment = Terminal.Gui.TextAlignment.Right, }; Win.Add (label); - var actionLabel = new Label (" ") { + + var actionLabel = new Label ("") { X = Pos.Right (label), Y = Pos.AnchorEnd (1), ColorScheme = Colors.Error, }; + Win.Add (actionLabel); + + var modalCheckBox = new CheckBox ("Modal (pop-up)") { + X = Pos.Center (), + Y = Pos.Bottom (frame) + 2, + AutoSize = true, + Checked = true + }; + Win.Add (modalCheckBox); var showWizardButton = new Button ("Show Wizard") { X = Pos.Center (), - Y = Pos.Bottom (frame) + 2, + Y = Pos.Bottom (modalCheckBox) + 1, IsDefault = true, }; + showWizardButton.Clicked += () => { try { int width = 0; @@ -116,6 +120,8 @@ namespace UICatalog.Scenarios { return; } + actionLabel.Text = ustring.Empty; + var wizard = new Wizard (titleEdit.Text) { Width = width, Height = height @@ -143,22 +149,14 @@ namespace UICatalog.Scenarios { // Add 1st step var firstStep = new Wizard.WizardStep ("End User License Agreement"); - wizard.AddStep (firstStep); - firstStep.ShowControls = false; firstStep.NextButtonText = "Accept!"; firstStep.HelpText = "This is the End User License Agreement.\n\n\n\n\n\nThis is a test of the emergency broadcast system. This is a test of the emergency broadcast system.\nThis is a test of the emergency broadcast system.\n\n\nThis is a test of the emergency broadcast system.\n\nThis is a test of the emergency broadcast system.\n\n\n\nThe end of the EULA."; + wizard.AddStep (firstStep); // Add 2nd step 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 demo changing the Title.\n\nIf First Name is empty the step will prevent moving to the next step."; - - View viewForControls = secondStep.Controls; - ustring frameMsg = "Added to WizardStep.Controls"; - if (useStepView.Checked) { - viewForControls = secondStep; - frameMsg = "Added to WizardStep directly"; - } + secondStep.HelpText = "This is the help text for the Second Step.\n\nPress the button to change the Title.\n\nIf First Name is empty the step will prevent moving to the next step."; var buttonLbl = new Label () { Text = "Second Step Button: ", X = 1, Y = 1 }; var button = new Button () { @@ -170,28 +168,25 @@ namespace UICatalog.Scenarios { secondStep.Title = "2nd Step"; MessageBox.Query ("Wizard Scenario", "This Wizard Step's title was changed to '2nd Step'"); }; - viewForControls.Add (buttonLbl, button); + secondStep.Add (buttonLbl, button); var lbl = new Label () { Text = "First Name: ", X = 1, Y = Pos.Bottom (buttonLbl) }; var firstNameField = new TextField () { Text = "Number", Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) }; - viewForControls.Add (lbl, firstNameField); + secondStep.Add (lbl, firstNameField); lbl = new Label () { Text = "Last Name: ", X = 1, Y = Pos.Bottom (lbl) }; var lastNameField = new TextField () { Text = "Six", Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) }; - viewForControls.Add (lbl, lastNameField); + secondStep.Add (lbl, lastNameField); var thirdStepEnabledCeckBox = new CheckBox () { Text = "Enable Step _3", Checked = false, X = Pos.Left (lastNameField), Y = Pos.Bottom (lastNameField) }; - viewForControls.Add (thirdStepEnabledCeckBox); + secondStep.Add (thirdStepEnabledCeckBox); - // 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}") { + // Add a frame + var frame = new FrameView ($"A Broken Frame (by Depeche Mode)") { X = 0, Y = Pos.Bottom (thirdStepEnabledCeckBox) + 2, Width = Dim.Fill (), - Height = 4, - //ColorScheme = Colors.Error, + Height = 4 }; frame.Add (new TextField ("This is a TextField inside of the frame.")); - viewForControls.Add (frame); + secondStep.Add (frame); wizard.StepChanging += (args) => { if (args.OldStep == secondStep && firstNameField.Text.IsEmpty) { args.Cancel = true; @@ -208,7 +203,7 @@ namespace UICatalog.Scenarios { X = 0, Y = 0 }; - thirdStep.Controls.Add (step3Label); + thirdStep.Add (step3Label); var progLbl = new Label () { Text = "Third Step ProgressBar: ", X = 1, Y = 10 }; var progressBar = new ProgressBar () { X = Pos.Right (progLbl), @@ -216,7 +211,7 @@ namespace UICatalog.Scenarios { Width = 40, Fraction = 0.42F }; - thirdStep.Controls.Add (progLbl, progressBar); + thirdStep.Add (progLbl, progressBar); thirdStep.Enabled = thirdStepEnabledCeckBox.Checked; thirdStepEnabledCeckBox.Toggled += (args) => { thirdStep.Enabled = thirdStepEnabledCeckBox.Checked; @@ -225,17 +220,30 @@ namespace UICatalog.Scenarios { // Add 4th step var fourthStep = new Wizard.WizardStep ("Step Four"); wizard.AddStep (fourthStep); - fourthStep.ShowHelp = false; var someText = new 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).", + Text = "This step (Step Four) shows how to show/hide the Help pane. The step 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 (), + Height = Dim.Fill (1), WordWrap = true, - AllowsTab = false + AllowsTab = false, }; - fourthStep.Controls.Add (someText); + var help = "This is helpful."; + fourthStep.Add (someText); + var hideHelpBtn = new Button () { + Text = "Press me to show/hide help", + X = Pos.Center (), + Y = Pos.AnchorEnd(1) + }; + hideHelpBtn.Clicked += () => { + if (fourthStep.HelpText.Length > 0) { + fourthStep.HelpText = ustring.Empty; + } else { + fourthStep.HelpText = help; + } + }; + fourthStep.Add (hideHelpBtn); fourthStep.NextButtonText = "Go To Last Step"; var scrollBar = new ScrollBarView (someText, true); @@ -265,7 +273,7 @@ namespace UICatalog.Scenarios { scrollBar.LayoutSubviews (); scrollBar.Refresh (); }; - fourthStep.Controls.Add (scrollBar); + fourthStep.Add (scrollBar); // Add last step var lastStep = new Wizard.WizardStep ("The last step"); @@ -283,15 +291,30 @@ namespace UICatalog.Scenarios { finalFinalStep.Enabled = finalFinalStepEnabledCeckBox.Checked; }; - Application.Run (wizard); + if (modalCheckBox.Checked) { + Application.Run (wizard); + } else { + // Disable the Show button so this only happens once + showWizardButton.Visible = false; + // To use Wizard as a View, you must set Modal = false + wizard.Modal = false; + + // When run as a modal, Wizard gets a Loading event where it sets the + // Current Step. But when running non-modal it must be done manually. + wizard.CurrentStep = wizard.GetNextStep (); + + Win.Add (wizard); + + // Ensure the wizard has focus + wizard.SetFocus (); + + } } catch (FormatException) { actionLabel.Text = "Invalid Options"; } }; Win.Add (showWizardButton); - - Win.Add (actionLabel); } } } diff --git a/UnitTests/WizardTests.cs b/UnitTests/WizardTests.cs index fc4aa2d70..06830785d 100644 --- a/UnitTests/WizardTests.cs +++ b/UnitTests/WizardTests.cs @@ -121,7 +121,7 @@ namespace Terminal.Gui.Views { var topRow = $"{d.ULDCorner} {title}{stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}"; var row2 = $"{d.VDLine}{new String (' ', width - 2)}{d.VDLine}"; var row3 = row2; - var separatorRow = $"{d.VDLine}{new String (d.HLine.ToString () [0], width - 2)}{d.VDLine}"; + var separatorRow = $"{d.VDLine}{new String (' ', 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}";