Pages

Search This Blog

Example of a progress dialog form with global and detail progress indicators

When running long operations showing the progress is very important for user experience. But in some cases a long operation can be seen as a set of other sub operations, so displaying the overall process and indicating the specific progress for each sub operation is more user friendly.

I came across such situation and i decided to write this little Form that has built in support for displaying overall and detail progresses and manages to dispatch thread calls into the thread owning the callers. 





Using the Form:

// Basic configs.
formProgress = new FormProgress();
formProgress.OperationCancelRequested += 
new FormProgress.OperationCancelRequestedDelegate(formProgress_OperationCancelRequested);
formProgress.ShowPercentageInTitle = true;
formProgress.ShowProgressDetails = true;
formProgress.SupportCancelation = true;
formProgress.ResetOnCancel = true;

// These will be performed likely from other threads , or backgroud worker.
formProgress.OverallProgressHeaderText = "Overall Progress:";
formProgress.OverallProgressPercentage = 30;
formProgress.DetailProgressHeaderText = "Operation n in progress...";
formProgress.DetailProgressPercentage = 20;

Source Code:

private static object syncLock = new object();
public delegate void OperationCancelRequestedDelegate();
public event OperationCancelRequestedDelegate OperationCancelRequested;

private bool showPercentageInTitle;
///
/// If set to true then the global progress percentage will be appended to the title.
///
public bool ShowPercentageInTitle
{
    get { return showPercentageInTitle; }
    set
    {
        if (showPercentageInTitle != value)
        {
            showPercentageInTitle = value;
            UpdateTitleDisplay();
        }
    }
}


private bool supportCancelation = false;

///
/// if set to true, a cancel button will apear in the form when clicked raises OperationCancelRequested event.
///
public bool SupportCancelation
{
    get
    {
        return supportCancelation;
    }
    set
    {
        supportCancelation = value;
        SetControlPropertyValue(btnCancel, "Visible", value);
    }

}

private bool cancelRequested = false;

///
/// Returns a value indicating if canceling the operation is requested.
///
public bool CancelRequested { get { return cancelRequested; } }
      
        
private string title;

///
/// Main progress dialog title.
///
public string Title
{
    get
    {
        return this.title;
    }
    set
    {
        if (this.title != value)
        {
            this.title = value;
            UpdateTitleDisplay();
        }
    }
}



///
/// Global progress percentage
///
public int OverallProgressPercentage
{
    get
    {
        return mainProgressBar.Value;
    }
    set
    {
        if (value != mainProgressBar.Value)
        {
            if (value < 0 || value > 100)
            {
                throw new ArgumentOutOfRangeException("OverallProgressPercentage", value,
                    "OverallProgressPercentage should be between 0 and 100");
            }
            else
            {
                SetControlPropertyValue(mainProgressBar, "Value", value);
                UpdateTitleDisplay();
            }
        }
    }
}


///
/// Detail progress percentage
///
public int DetailProgressPercentage
{
    get
    {
        return detailProgressBar.Value;
    }
    set
    {
        if (value != detailProgressBar.Value)
        {
            if (value < 0 || value > 100)
            {
                throw new ArgumentOutOfRangeException("DetailProgressPercentage", value,
                    "DetailProgressPercentage should be between 0 and 100");
            }
            else
            {
                SetControlPropertyValue(detailProgressBar, "Value", value);
            }
        }
    }
}

///
/// Text to be displayed as a global progress header information.
///
public string OverallProgressHeaderText
{
    get
    {
        return lblOverallProgress.Text;
    }
    set
    {
        SetControlPropertyValue(lblOverallProgress, "Text", value);
    }
}


///
/// Text to be displayed as a detail progress header information.
///
public string DetailProgressHeaderText
{
    get
    {
        return lblDetailProgress.Text;
    }
    set
    {
        SetControlPropertyValue(lblDetailProgress, "Text", value);
    }
}

private bool showProgressDetails = true;

private int orignalGroupDetailsHeight = 0;
///
/// If set to true it will display progress details.
///
public bool ShowProgressDetails
{
    get
    {
        return showProgressDetails;
    }
    set
    {
        if (showProgressDetails != value)
        {
            showProgressDetails = value;
            // Set the new details groupbox visibility.
            SetControlPropertyValue(groupBoxDetails, "Visible", showProgressDetails);
            // Update the control's Height.
            int height = this.Height;

            if (showProgressDetails)
            {
                height += orignalGroupDetailsHeight;
            }
            else
                height -= orignalGroupDetailsHeight;

            SetControlPropertyValue(this, "Height", height);
        }
    }
}

private void UpdateTitleDisplay()
{
    string valueToDisplay = title;
    if (showPercentageInTitle)
    {
        valueToDisplay += (" " + OverallProgressPercentage.ToString() + "%");
    }
    if (String.IsNullOrEmpty(valueToDisplay))
        valueToDisplay = " ";

    SetControlPropertyValue(this, "Text", valueToDisplay);

}

private delegate void SetControlPropertyValueDelegate(Control control, System.Reflection.PropertyInfo pInfo, object value);
  
private void SetControlPropertyValue(Control control, string propertyName, object value)
{
    // Set to control thread's access to this Form properties.
    lock (syncLock)
    {
        if (control == null)
        {
            throw new ArgumentNullException("control");
        }
        if (String.IsNullOrEmpty(propertyName))
        {
            throw new ArgumentNullException("propertyName");
        }
        System.Reflection.PropertyInfo pInfo = control.GetType().GetProperty(propertyName);
        if (pInfo == null)
        {
            throw new Exception("control: " + control.Name + " does not have the property: " + propertyName);
        }

        if (control.InvokeRequired)
        {
            control.Invoke(new SetControlPropertyValueDelegate(SetControlPropertyValue),
                control, pInfo, value);
        }
        else
        {
            pInfo.SetValue(control, value, null);
        }
    }
}

private void SetControlPropertyValue(Control control, System.Reflection.PropertyInfo pInfo, object value)
{
    pInfo.SetValue(control, value, null);
}

public FormProgress()
{
    InitializeComponent();
    // Main title can't be null or empty otherwise the form will become locked and can not be moved.
    this.Text = " ";
    orignalGroupDetailsHeight = groupBoxDetails.Height;
}

private void btnCancel_Click(object sender, EventArgs e)
{
    btnCancel.Text = "Canceling...";
    btnCancel.Enabled = false;
    this.cancelRequested = true;

    // Process all remainig messages for this form.
    Application.DoEvents();

    lock (syncLock)
    {
        if (OperationCancelRequested != null)
        {
            // Make sure to call the target on its own thread.
            if (OperationCancelRequested.Target is Control)
            {
                Control target = OperationCancelRequested.Target as Control;
                if (target.InvokeRequired)
                {
                    target.Invoke(OperationCancelRequested, null);
                }
                else
                    OperationCancelRequested();
            }
            else
                OperationCancelRequested();
        }

        if (resetOnCancel)
            Reset();
               
    }


}

private bool resetOnCancel = true;

///
/// If set to true, the value of the progress bars will we set to 0 and labels will be empty.
///
public bool ResetOnCancel
{
    get
    {
        return resetOnCancel;
    }
    set
    {
        resetOnCancel = value;
    }
}

private void Reset()
{
    btnCancel.Text = "Cancel";
    btnCancel.Enabled = true;
    lblDetailProgress.Text = "";
    lblOverallProgress.Text = "";
    Text = " ";
    ResetProgress();
}

private void ResetProgress()
{
    mainProgressBar.Value = 0;
    detailProgressBar.Value = 0;
}


No comments:

Post a Comment