|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Windows.Forms;
/// <summary>
/// Represents a progress bar control of a task dialog.
/// </summary>
public sealed class TaskDialogProgressBar : TaskDialogControl
{
private TaskDialogProgressBarState _state;
private TaskDialogProgressBarState _stateWhenBound;
private int _minimum;
private int _maximum = 100;
private int _value;
private int _marqueeSpeed;
/// <summary>
/// Initializes a new instance of the <see cref="TaskDialogProgressBar"/> class.
/// </summary>
public TaskDialogProgressBar()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TaskDialogProgressBar"/> class
/// using the given <paramref name="state"/>.
/// </summary>
/// <param name="state">The state of the progress bar.</param>
public TaskDialogProgressBar(TaskDialogProgressBarState state)
: this()
{
// Use the setter which will validate the value.
State = state;
}
/// <summary>
/// Gets or sets the state of the progress bar.
/// </summary>
/// <value>
/// The state of the progress bar. The default is <see cref="TaskDialogProgressBarState.Normal"/>,
/// except if this instance is the default instance created by a <see cref="TaskDialogPage"/>,
/// where the default value is <see cref="TaskDialogProgressBarState.None"/>.
/// </value>
/// <remarks>
/// <para>
/// This control will only be shown if this property is not
/// <see cref="TaskDialogProgressBarState.None"/>.
/// </para>
/// <para>
/// This property can be set while the dialog is shown. However, while the dialog is
/// shown, it is not possible to change the state from
/// <see cref="TaskDialogProgressBarState.None"/> to any other state,
/// and vice versa.
/// </para>
/// </remarks>
/// <exception cref="InvalidOperationException">
/// The property is set on a progress bar instance that is currently bound to a task dialog, but the value
/// to be set is <see cref="TaskDialogProgressBarState.None"/>.
/// - or -
/// The property is set on a progress bar instance that is currently bound to a task dialog, but it's not visible as its initial
/// <see cref="State"/> property value was <see cref="TaskDialogProgressBarState.None"/>.
/// - or -
/// The property is set on a progress bar instance that is currently bound to a task dialog, but the dialog
/// has just started navigating to a different page.
/// </exception>
public TaskDialogProgressBarState State
{
get => _state;
set
{
SourceGenerated.EnumValidator.Validate(value);
DenyIfBoundAndNotCreated();
if (BoundPage is not null && value == TaskDialogProgressBarState.None)
{
throw new InvalidOperationException(
SR.TaskDialogCannotRemoveProgressBarWhileDialogIsShown);
}
TaskDialogProgressBarState previousState = _state;
_state = value;
try
{
if (BoundPage?.WaitingForInitialization == false)
{
UpdateState(previousState);
}
}
catch
{
// Revert to the previous state. This could happen if the dialog's
// DenyIfDialogNotUpdatable() (called by one of the Set...() methods)
// throws.
_state = previousState;
throw;
}
}
}
/// <summary>
/// Gets or sets the minimum value of the range of the control.
/// </summary>
/// <value>
/// The minimum value of the range. The default is <c>0</c>.
/// </value>
/// <remarks>
/// <para>
/// This value is only used if the progress bar is not a marquee progress bar (as defined
/// by the <see cref="State"/> property).
/// </para>
/// <para>
/// This property can be set while the dialog is shown.
/// </para>
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// The value is less than 0 or greater than <see cref="ushort.MaxValue" />.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The property is set on a progress bar instance that is currently bound to a task dialog, but it's not visible as its initial
/// <see cref="State"/> property value was <see cref="TaskDialogProgressBarState.None"/>.
/// - or -
/// The property is set on a progress bar instance that is currently bound to a task dialog, but the dialog
/// has just started navigating to a different page.
/// </exception>
public int Minimum
{
get => _minimum;
set
{
ArgumentOutOfRangeException.ThrowIfNegative(value);
ArgumentOutOfRangeException.ThrowIfGreaterThan(value, ushort.MaxValue);
DenyIfBoundAndNotCreated();
// We only update the task dialog if the current state is a
// non-marquee progress bar.
if (BoundPage?.WaitingForInitialization == false && !ProgressBarStateIsMarquee(_state))
{
BoundPage.BoundDialog!.SetProgressBarRange(value, _maximum);
}
_minimum = value;
}
}
/// <summary>
/// Gets or sets the maximum value of the range of the control.
/// </summary>
/// <value>
/// The maximum value of the range. The default is <c>100</c>.
/// </value>
/// <remarks>
/// <para>
/// This value is only used if the progress bar is not a marquee progress bar (as defined
/// by the <see cref="State"/> property).
/// </para>
/// <para>
/// This property can be set while the dialog is shown.
/// </para>
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// The value is less than 0 or greater than <see cref="ushort.MaxValue" />.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The property is set on a progress bar instance that is currently bound to a task dialog, but it's not visible as its initial
/// <see cref="State"/> property value was <see cref="TaskDialogProgressBarState.None"/>.
/// - or -
/// The property is set on a progress bar instance that is currently bound to a task dialog, but the dialog
/// has just started navigating to a different page.
/// </exception>
public int Maximum
{
get => _maximum;
set
{
ArgumentOutOfRangeException.ThrowIfNegative(value);
ArgumentOutOfRangeException.ThrowIfGreaterThan(value, ushort.MaxValue);
DenyIfBoundAndNotCreated();
// We only update the task dialog if the current state is a
// non-marquee progress bar.
if (BoundPage?.WaitingForInitialization == false && !ProgressBarStateIsMarquee(_state))
{
BoundPage.BoundDialog!.SetProgressBarRange(_minimum, value);
}
_maximum = value;
}
}
/// <summary>
/// Gets or sets the current position of the progress bar.
/// </summary>
/// <value>
/// The position within the range of the progress bar. The default is <c>0</c>.
/// </value>
/// <remarks>
/// <para>
/// This value is only used if the progress bar is not a marquee progress bar (as defined
/// by the <see cref="State"/> property).
/// </para>
/// <para>
/// This property can be set while the dialog is shown.
/// </para>
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// The value is less than 0 or greater than <see cref="ushort.MaxValue" />.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The property is set on a progress bar instance that is currently bound to a task dialog, but it's not visible as its initial
/// <see cref="State"/> property value was <see cref="TaskDialogProgressBarState.None"/>.
/// - or -
/// The property is set on a progress bar instance that is currently bound to a task dialog, but the dialog
/// has just started navigating to a different page.
/// </exception>
public int Value
{
get => _value;
set
{
ArgumentOutOfRangeException.ThrowIfNegative(value);
ArgumentOutOfRangeException.ThrowIfGreaterThan(value, ushort.MaxValue);
DenyIfBoundAndNotCreated();
// We only update the task dialog if the current state is a
// non-marquee progress bar.
if (BoundPage?.WaitingForInitialization == false && !ProgressBarStateIsMarquee(_state))
{
BoundPage.BoundDialog!.SetProgressBarPosition(value);
// We need to set the position a second time to work
// reliably if the state is not 'Normal'.
// See this comment in the TaskDialog implementation
// of the Windows API Code Pack 1.1:
// "Due to a bug that wasn't fixed in time for RTM of
// Vista, second SendMessage is required if the state
// is non-Normal."
// Apparently, this bug is still present in Win10 V1909.
if (_state != TaskDialogProgressBarState.Normal)
{
BoundPage.BoundDialog!.SetProgressBarPosition(value);
}
}
_value = value;
}
}
/// <summary>
/// Gets or sets the speed of the marquee display of a progress bar.
/// </summary>
/// <value>
/// The speed of the marquee display which is the time, in milliseconds, between marquee
/// animation updates. If this value is <c>0</c>, the marquee animation is updated every
/// 30 milliseconds. The default value is <c>0</c>.
/// </value>
/// <remarks>
/// <para>
/// This value is only used if the progress bar is a marquee progress bar (as defined
/// by the <see cref="State"/> property).
/// </para>
/// <para>
/// This property can be set while the dialog is shown.
/// </para>
/// </remarks>
/// <exception cref="InvalidOperationException">
/// The property is set on a progress bar instance that is currently bound to a task dialog, but it's not visible as its initial
/// <see cref="State"/> property value was <see cref="TaskDialogProgressBarState.None"/>.
/// - or -
/// The property is set on a progress bar instance that is currently bound to a task dialog, but the dialog
/// has just started navigating to a different page.
/// </exception>
public int MarqueeSpeed
{
get => _marqueeSpeed;
set
{
DenyIfBoundAndNotCreated();
int previousMarqueeSpeed = _marqueeSpeed;
_marqueeSpeed = value;
try
{
// We only update the task dialog if the current state is a
// marquee progress bar.
if (BoundPage?.WaitingForInitialization == false && ProgressBarStateIsMarquee(_state))
{
// Update the state which will also update the marquee speed.
UpdateState(_state);
}
}
catch
{
_marqueeSpeed = previousMarqueeSpeed;
throw;
}
}
}
internal override bool IsCreatable => base.IsCreatable && _state != TaskDialogProgressBarState.None;
private static bool ProgressBarStateIsMarquee(TaskDialogProgressBarState state) =>
state is TaskDialogProgressBarState.Marquee or
TaskDialogProgressBarState.MarqueePaused;
private static uint GetNativeProgressBarState(TaskDialogProgressBarState state) => state switch
{
TaskDialogProgressBarState.Normal => PInvoke.PBST_NORMAL,
TaskDialogProgressBarState.Paused => PInvoke.PBST_PAUSED,
TaskDialogProgressBarState.Error => PInvoke.PBST_ERROR,
_ => throw new ArgumentException(null, nameof(state))
};
private protected override TASKDIALOG_FLAGS BindCore()
{
TASKDIALOG_FLAGS flags = base.BindCore();
// When specifying the flags for the page creation, the state of the initial progress bar
// shown in the dialog will either be equivalent to the 'MarqueePaused' or 'Normal' state.
// Other states will need to wait for the initialization.
bool initialStateIsMarquee = ProgressBarStateIsMarquee(_state);
_stateWhenBound = initialStateIsMarquee ?
TaskDialogProgressBarState.MarqueePaused : TaskDialogProgressBarState.Normal;
flags |= initialStateIsMarquee ?
TASKDIALOG_FLAGS.TDF_SHOW_MARQUEE_PROGRESS_BAR : TASKDIALOG_FLAGS.TDF_SHOW_PROGRESS_BAR;
return flags;
}
private protected override void ApplyInitializationCore()
{
UpdateState(_stateWhenBound, true);
}
private void UpdateState(TaskDialogProgressBarState previousState, bool isInitialization = false)
{
Debug.Assert(BoundPage is not null);
TaskDialog taskDialog = BoundPage.BoundDialog!;
// Check if we need to switch between a marquee and a
// non-marquee bar.
bool newStateIsMarquee = ProgressBarStateIsMarquee(_state);
bool switchMode = ProgressBarStateIsMarquee(previousState) != newStateIsMarquee;
if (switchMode)
{
// When switching from non-marquee to marquee mode, we
// first need to set the state to "Normal"; otherwise
// the marquee will not show.
if (newStateIsMarquee && previousState != TaskDialogProgressBarState.Normal)
{
taskDialog.SetProgressBarState(PInvoke.PBST_NORMAL);
}
taskDialog.SwitchProgressBarMode(newStateIsMarquee);
}
// Update the properties.
if (newStateIsMarquee)
{
taskDialog.SetProgressBarMarquee(
_state == TaskDialogProgressBarState.Marquee,
_marqueeSpeed);
}
else
{
taskDialog.SetProgressBarState(GetNativeProgressBarState(_state));
if (isInitialization || switchMode)
{
// Also need to set the other properties after switching
// the mode or when applying the initialization.
taskDialog.SetProgressBarRange(_minimum, _maximum);
Value = _value;
}
}
}
}
|