AirScout/AeroWizard/AeroWizard/WizardPageContainer.cs

755 wiersze
22 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using Vanara.Interop;
namespace AeroWizard
{
/// <summary>
/// Button state for buttons controlling the wizard.
/// </summary>
public enum WizardCommandButtonState
{
/// <summary>Button is enabled and can be clicked.</summary>
Enabled,
/// <summary>Button is disabled and cannot be clicked.</summary>
Disabled,
/// <summary>Button is hidden from the user.</summary>
Hidden
}
/// <summary>
/// Control providing a collection of wizard style navigable pages.
/// </summary>
[Designer(typeof(Design.WizardBaseDesigner))]
[ToolboxItem(true), ToolboxBitmap(typeof(WizardControl), "WizardControl.bmp")]
[Description("Provides a container for wizard pages.")]
[DefaultProperty("Pages"), DefaultEvent("SelectedPageChanged")]
public class WizardPageContainer : ContainerControl, ISupportInitialize
{
private string finishBtnText;
private bool initialized;
private bool initializing;
private bool nextButtonShieldEnabled;
private string nextBtnText;
private readonly Stack<WizardPage> pageHistory;
private Timer progressTimer;
private WizardPage selectedPage;
private bool showProgressInTaskbarIcon;
private NativeMethods.ITaskbarList4 taskbar;
private ButtonBase backButton, cancelButton, nextButton;
/// <summary>
/// Initializes a new instance of the <see cref="Control"/> class.
/// </summary>
public WizardPageContainer()
{
pageHistory = new Stack<WizardPage>();
Pages = new WizardPageCollection(this);
Pages.ItemAdded += Pages_AddItem;
Pages.ItemDeleted += Pages_RemoveItem;
Pages.Reset += Pages_Reset;
OnRightToLeftChanged(EventArgs.Empty);
// Get localized defaults for button text
ResetBackButtonText();
ResetCancelButtonText();
ResetFinishButtonText();
ResetNextButtonText();
}
/// <summary>
/// Occurs when the button's state has changed.
/// </summary>
[Category("Property Changed"), Description("Occurs when any of the button's state has changed.")]
public event EventHandler ButtonStateChanged;
/// <summary>
/// Occurs when the user clicks the Cancel button and allows for programmatic cancellation.
/// </summary>
[Category("Behavior"), Description("Occurs when the user clicks the Cancel button and allows for programmatic cancellation.")]
public event CancelEventHandler Cancelling;
/// <summary>
/// Occurs when the user clicks the Next/Finish button and the page is set to <see cref="WizardPage.IsFinishPage"/> or this is the last page in the <see cref="Pages"/> collection.
/// </summary>
[Category("Behavior"), Description("Occurs when the user clicks the Next/Finish button on last page.")]
public event EventHandler Finished;
/// <summary>
/// Occurs when the <see cref="WizardControl.SelectedPage"/> property has changed.
/// </summary>
[Category("Property Changed"), Description("Occurs when the SelectedPage property has changed.")]
public event EventHandler SelectedPageChanged;
/// <summary>
/// Gets or sets the button assigned to control backing up through the pages.
/// </summary>
/// <value>The back button control.</value>
[Category("Wizard"), Description("Button used to command backward wizard flow.")]
public ButtonBase BackButton
{
get { return backButton; }
set
{
if (backButton != null)
backButton.Click -= backButton_Click;
if (value != null)
{
backButton = value;
backButton.Click += backButton_Click;
}
}
}
/// <summary>
/// Gets or sets the state of the back button.
/// </summary>
/// <value>The state of the back button.</value>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public WizardCommandButtonState BackButtonState
{
get { return GetCmdButtonState(BackButton); }
internal set { SetCmdButtonState(BackButton, value); }
}
/// <summary>
/// Gets or sets the back button text.
/// </summary>
/// <value>The cancel button text.</value>
[Category("Wizard"), Localizable(true), Description("The back button text")]
public string BackButtonText
{
get { return GetCmdButtonText(BackButton); }
set { SetCmdButtonText(BackButton, value); }
}
/// <summary>
/// Gets or sets the button assigned to canceling the page flow.
/// </summary>
/// <value>The cancel button control.</value>
[Category("Wizard"), Description("Button used to cancel wizard flow.")]
public ButtonBase CancelButton
{
get { return cancelButton; }
set
{
if (cancelButton != null)
cancelButton.Click -= cancelButton_Click;
if (value != null)
{
cancelButton = value;
cancelButton.Click += cancelButton_Click;
}
}
}
/// <summary>
/// Gets the state of the cancel button.
/// </summary>
/// <value>The state of the cancel button.</value>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public WizardCommandButtonState CancelButtonState
{
get { return GetCmdButtonState(CancelButton); }
internal set { SetCmdButtonState(CancelButton, value); }
}
/// <summary>
/// Gets or sets the cancel button text.
/// </summary>
/// <value>The cancel button text.</value>
[Category("Wizard"), Localizable(true), Description("The cancel button text")]
public string CancelButtonText
{
get { return GetCmdButtonText(CancelButton); }
set { SetCmdButtonText(CancelButton, value); }
}
/// <summary>
/// Gets or sets the finish button text.
/// </summary>
/// <value>The finish button text.</value>
[Category("Wizard"), Localizable(true), Description("The finish button text")]
public string FinishButtonText
{
get { return finishBtnText; }
set
{
finishBtnText = value;
if (selectedPage != null && selectedPage.IsFinishPage && !this.IsDesignMode())
{
SetCmdButtonText(NextButton, value);
}
}
}
/// <summary>
/// Gets or sets the button assigned to control moving forward through the pages.
/// </summary>
/// <value>The next button control.</value>
[Category("Wizard"), Description("Button used to command forward wizard flow.")]
public ButtonBase NextButton
{
get { return nextButton; }
set
{
if (nextButton != null)
nextButton.Click -= nextButton_Click;
if (value != null)
{
nextButton = value;
nextButton.Click += nextButton_Click;
}
}
}
/// <summary>
/// Gets or sets the shield icon on the next button.
/// </summary>
/// <value><c>true</c> if Next button should display a shield; otherwise, <c>false</c>.</value>
/// <exception cref="PlatformNotSupportedException">Setting a UAF shield on a button only works on Vista and later versions of Windows.</exception>
[DefaultValue(false), Category("Wizard"), Description("Show a shield icon on the next button")]
public Boolean NextButtonShieldEnabled
{
get { return nextButtonShieldEnabled; }
set
{
if (nextButtonShieldEnabled != value)
{
nextButtonShieldEnabled = value;
NextButton.SetElevationRequiredState(value);
}
}
}
/// <summary>
/// Gets the state of the next button.
/// </summary>
/// <value>The state of the next button.</value>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public WizardCommandButtonState NextButtonState
{
get { return GetCmdButtonState(NextButton); }
internal set { SetCmdButtonState(NextButton, value); }
}
/// <summary>
/// Gets or sets the next button text.
/// </summary>
/// <value>The next button text.</value>
[Category("Wizard"), Localizable(true), Description("The next button text.")]
public string NextButtonText
{
get { return nextBtnText; }
set
{
nextBtnText = value;
if (!this.IsDesignMode() && (selectedPage == null || !selectedPage.IsFinishPage))
{
SetCmdButtonText(NextButton, value);
}
}
}
/// <summary>
/// Gets the collection of wizard pages in this wizard control.
/// </summary>
/// <value>The <see cref="WizardPageCollection"/> that contains the <see cref="WizardPage"/> objects in this <see cref="WizardControl"/>.</value>
[Category("Wizard"), Description("Collection of wizard pages.")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public WizardPageCollection Pages { get; }
/// <summary>
/// Gets how far the wizard has progressed, as a percentage.
/// </summary>
/// <value>A value between 0 and 100.</value>
[Browsable(false), Description("The percentage of the current page against all pages at run-time.")]
public virtual short PercentComplete
{
get
{
var pg = SelectedPage;
if (pg == null)
return 0;
if (IsLastPage(pg))
return 100;
return Convert.ToInt16(Math.Ceiling(((double)Pages.IndexOf(SelectedPage) + 1) * 100f / Pages.Count));
}
}
/// <summary>
/// Gets the currently selected wizard page.
/// </summary>
/// <value>The selected wizard page. <c>null</c> if no page is active.</value>
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public virtual WizardPage SelectedPage
{
get
{
if (selectedPage == null || Pages.Count == 0)
return null;
return selectedPage;
}
internal set
{
if (value != null && !Pages.Contains(value))
throw new ArgumentException("WizardPage is not in the Pages collection for the control.");
System.Diagnostics.Debug.WriteLine($"SelectPage: New={(value == null ? "null" : value.Name)},Prev={(selectedPage == null ? "null" : selectedPage.Name)}");
if (value != selectedPage)
{
var prev = selectedPage;
selectedPage?.Hide();
selectedPage = value;
var idx = SelectedPageIndex;
if (!this.IsDesignMode())
{
while (selectedPage != null && idx < Pages.Count - 1 && selectedPage.Suppress)
selectedPage = Pages[++idx];
}
if (selectedPage != null)
{
//this.HeaderText = selectedPage.Text;
selectedPage.InitializePage(prev);
selectedPage.Dock = DockStyle.Fill;
selectedPage.PerformLayout();
selectedPage.Show();
selectedPage.BringToFront();
selectedPage.Focus();
}
UpdateUIDependencies();
OnSelectedPageChanged();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether to show progress in form's taskbar icon.
/// </summary>
/// <remarks>
/// This will only work on Windows 7 or later and the parent form must be showing its icon in the taskbar. No exception is thrown on failure.
/// </remarks>
/// <value>
/// <c>true</c> to show progress in taskbar icon; otherwise, <c>false</c>.
/// </value>
[Category("Wizard"), DefaultValue(false), Description("Indicates whether to show progress in form's taskbar icon")]
public bool ShowProgressInTaskbarIcon
{
get { return showProgressInTaskbarIcon; }
set
{
if (RunningOnWin7)
{
showProgressInTaskbarIcon = value;
UpdateTaskbarProgress();
}
}
}
/// <summary>
/// Gets a value indicating whether running on win7.
/// </summary>
/// <value><c>true</c> if [running on win7]; otherwise, <c>false</c>.</value>
private static bool RunningOnWin7 => (Environment.OSVersion.Platform == PlatformID.Win32NT) && (Environment.OSVersion.Version.CompareTo(new Version(6, 1)) >= 0);
/// <summary>
/// Gets the task bar interface for the current form.
/// </summary>
/// <value>The task bar.</value>
private NativeMethods.ITaskbarList4 TaskBar
{
get
{
if (taskbar == null)
{
taskbar = (NativeMethods.ITaskbarList4)new NativeMethods.CTaskbarList();
taskbar.HrInit();
if (ParentForm != null) taskbar.SetProgressState(ParentForm.Handle, NativeMethods.TBPF.NORMAL);
}
return taskbar;
}
}
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
internal bool DesignerSelected { get; set; }
/// <summary>
/// Gets the index of the currently selected page.
/// </summary>
/// <value>The index of the selected page.</value>
internal int SelectedPageIndex
{
get
{
if (selectedPage == null)
return -1;
return Pages.IndexOf(selectedPage);
}
}
/// <summary>
/// Signals the object that initialization is starting.
/// </summary>
public void BeginInit()
{
initializing = true;
}
/// <summary>
/// Signals the object that initialization is complete.
/// </summary>
public void EndInit()
{
initializing = false;
}
/// <summary>
/// Advances to the specified page.
/// </summary>
/// <param name="nextPage">The wizard page to go to next.</param>
/// <param name="skipCommit">if set to <c>true</c> skip <see cref="WizardPage.Commit"/> event.</param>
/// <exception cref="ArgumentException">When specifying a value for nextPage, it must already be in the Pages collection.</exception>
public virtual void NextPage(WizardPage nextPage = null, bool skipCommit = false)
{
if (this.IsDesignMode())
{
var idx = SelectedPageIndex;
if (idx < Pages.Count - 1)
SelectedPage = Pages[idx + 1];
return;
}
if (skipCommit || SelectedPage.CommitPage())
{
if (nextPage != null)
{
if (!Pages.Contains(nextPage))
throw new ArgumentException(@"When specifying a value for nextPage, it must already be in the Pages collection.", nameof(nextPage));
pageHistory.Push(SelectedPage);
SelectedPage = nextPage;
}
else
{
var selNext = GetNextPage(SelectedPage);
// Check for last page
if (SelectedPage.IsFinishPage || selNext == null)
{
OnFinished();
return;
}
// Set new SelectedPage value
pageHistory.Push(SelectedPage);
SelectedPage = selNext;
}
}
}
/// <summary>
/// Returns to the previous page.
/// </summary>
public virtual void PreviousPage()
{
if (this.IsDesignMode())
{
var idx = SelectedPageIndex;
if (idx > 0)
SelectedPage = Pages[idx - 1];
return;
}
if (SelectedPage.RollbackPage())
SelectedPage = pageHistory.Pop();
}
/// <summary>
/// Restarts the wizard pages from the first page.
/// </summary>
public void RestartPages()
{
initialized = false;
InitialSetup();
}
/// <summary>
/// Raises the <see cref="WizardControl.Cancelling"/> event.
/// </summary>
protected virtual void OnCancelling()
{
var arg = new CancelEventArgs(true);
Cancelling?.Invoke(this, arg);
}
/// <summary>
/// Raises the <see cref="WizardControl.Finished"/> event.
/// </summary>
protected virtual void OnFinished()
{
Finished?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.Control.GotFocus"/> event.
/// </summary>
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
selectedPage?.Focus();
}
/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.Control.HandleCreated"/> event.
/// </summary>
/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
InitialSetup();
}
/// <summary>
/// Raises the <see cref="WizardControl.SelectedPageChanged"/> event.
/// </summary>
protected void OnSelectedPageChanged()
{
SelectedPageChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Updates the buttons and taskbar according to current sequence and history.
/// </summary>
protected internal void UpdateUIDependencies()
{
System.Diagnostics.Debug.WriteLine($"UpdBtn: hstCnt={pageHistory.Count},pgIdx={SelectedPageIndex}:{Pages.Count},isFin={selectedPage != null && selectedPage.IsFinishPage}");
if (selectedPage == null)
{
CancelButtonState = this.IsDesignMode() ? WizardCommandButtonState.Disabled : WizardCommandButtonState.Enabled;
NextButtonState = BackButtonState = WizardCommandButtonState.Hidden;
}
else
{
if (this.IsDesignMode())
{
CancelButtonState = WizardCommandButtonState.Disabled;
NextButtonState = SelectedPageIndex == Pages.Count - 1 ? WizardCommandButtonState.Disabled : WizardCommandButtonState.Enabled;
BackButtonState = SelectedPageIndex <= 0 ? WizardCommandButtonState.Disabled : WizardCommandButtonState.Enabled;
}
else
{
CancelButtonState = selectedPage.ShowCancel ? (selectedPage.AllowCancel && !this.IsDesignMode() ? WizardCommandButtonState.Enabled : WizardCommandButtonState.Disabled) : WizardCommandButtonState.Hidden;
NextButtonState = selectedPage.ShowNext ? (selectedPage.AllowNext ? WizardCommandButtonState.Enabled : WizardCommandButtonState.Disabled) : WizardCommandButtonState.Hidden;
if (selectedPage.IsFinishPage || IsLastPage(SelectedPage))
SetCmdButtonText(NextButton, FinishButtonText);
else
SetCmdButtonText(NextButton, NextButtonText);
BackButtonState = pageHistory.Count == 0 || !selectedPage.AllowBack ? WizardCommandButtonState.Disabled : WizardCommandButtonState.Enabled;
UpdateTaskbarProgress();
}
}
if (Controls.ContainsKey("stepList"))
Controls["stepList"].Refresh();
}
private void backButton_Click(object sender, EventArgs e)
{
PreviousPage();
}
private void cancelButton_Click(object sender, EventArgs e)
{
OnCancelling();
}
internal WizardCommandButtonState GetCmdButtonState(ButtonBase btn)
{
if (btn?.Tag == null)
return WizardCommandButtonState.Hidden;
if (btn.Tag is WizardCommandButtonState)
return (WizardCommandButtonState)btn.Tag;
if (btn.Enabled)
return WizardCommandButtonState.Enabled;
if (!btn.Visible)
return WizardCommandButtonState.Hidden;
return WizardCommandButtonState.Disabled;
}
private string GetCmdButtonText(ButtonBase btn) => btn == null ? string.Empty : btn.Text;
private WizardPage GetNextPage(WizardPage page)
{
if (page == null || page.IsFinishPage)
return null;
do
{
var pgIdx = Pages.IndexOf(page);
if (page.NextPage != null)
page = page.NextPage;
else if (pgIdx == Pages.Count - 1)
page = null;
else
page = Pages[pgIdx + 1];
} while (page != null && page.Suppress);
return page;
}
private void InitialSetup()
{
if (!initialized)
{
pageHistory.Clear();
selectedPage = null;
var firstPage = Pages.Find(p => !p.Suppress);
if (firstPage != null) SelectedPage = firstPage;
if (selectedPage == null)
UpdateUIDependencies();
if (showProgressInTaskbarIcon)
{
progressTimer = new Timer() { Interval = 1000, Enabled = true };
progressTimer.Tick += progressTimer_Tick;
}
initialized = true;
}
}
private bool IsLastPage(WizardPage page) => GetNextPage(page) == null;
private void nextButton_Click(object sender, EventArgs e)
{
NextPage();
}
private void Pages_AddItem(object sender, EventedList<WizardPage>.ListChangedEventArgs<WizardPage> e)
{
Pages_AddItemHandler(e.Item, !initializing);
}
private void Pages_AddItemHandler(WizardPage item, bool selectAfterAdd)
{
System.Diagnostics.Debug.WriteLine($"AddPage: {(item == null ? "null" : item.Text)},sel={selectAfterAdd}");
item.Owner = this;
item.Visible = false;
if (!Contains(item))
Controls.Add(item);
if (selectAfterAdd)
SelectedPage = item;
}
private void Pages_RemoveItem(object sender, EventedList<WizardPage>.ListChangedEventArgs<WizardPage> e)
{
Controls.Remove(e.Item);
if (e.Item == selectedPage && Pages.Count > 0)
SelectedPage = Pages[e.ItemIndex == Pages.Count ? e.ItemIndex - 1 : e.ItemIndex];
else
UpdateUIDependencies();
}
private void Pages_Reset(object sender, EventedList<WizardPage>.ListChangedEventArgs<WizardPage> e)
{
var curPage = selectedPage;
SelectedPage = null;
Controls.Clear();
foreach (var item in Pages)
Pages_AddItemHandler(item, false);
if (Pages.Count > 0)
SelectedPage = Pages.Contains(curPage) ? curPage : Pages[0];
}
private void progressTimer_Tick(object sender, EventArgs e)
{
if (ParentForm != null && ParentForm.Visible)
{
progressTimer.Enabled = false;
progressTimer = null;
UpdateTaskbarProgress();
}
}
private void UpdateTaskbarProgress()
{
if (showProgressInTaskbarIcon && selectedPage != null && Pages.Count > 0 && !this.IsDesignMode() && ParentForm != null && ParentForm.ShowInTaskbar)
TaskBar.SetProgressValue(ParentForm.Handle, Convert.ToUInt64(PercentComplete), 100ul);
}
internal void ResetBackButtonText()
{
BackButtonText = Properties.Resources.WizardBackText;
}
internal void ResetCancelButtonText()
{
CancelButtonText = Properties.Resources.WizardCancelText;
}
internal void ResetFinishButtonText()
{
FinishButtonText = Properties.Resources.WizardFinishText;
}
internal void ResetNextButtonText()
{
NextButtonText = Properties.Resources.WizardNextText;
}
private void SetCmdButtonState(ButtonBase btn, WizardCommandButtonState value)
{
if (btn == null)
return;
var prevVal = GetCmdButtonState(btn);
switch (value)
{
case WizardCommandButtonState.Disabled:
btn.Enabled = false;
btn.Visible = true;
break;
case WizardCommandButtonState.Hidden:
btn.Enabled = false;
if (btn != BackButton)
btn.Visible = false;
break;
case WizardCommandButtonState.Enabled:
btn.Enabled = true;
btn.Visible = true;
break;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
if (prevVal != value)
{
btn.Tag = value;
ButtonStateChanged?.Invoke(btn, EventArgs.Empty);
}
Invalidate();
}
private void SetCmdButtonText(ButtonBase btn, string text)
{
if (btn == null) return;
btn.Text = text;
Invalidate();
}
internal bool ShouldSerializeBackButtonText() => BackButtonText != Properties.Resources.WizardBackText;
internal bool ShouldSerializeCancelButtonText() => CancelButtonText != Properties.Resources.WizardCancelText;
internal bool ShouldSerializeFinishButtonText() => FinishButtonText != Properties.Resources.WizardFinishText;
internal bool ShouldSerializeNextButtonText() => NextButtonText != Properties.Resources.WizardNextText;
}
}