Winforms Updating UI Asynchronously Pattern - Need

2019-02-24 05:45发布

Setup: Main MDI form with a progress bar and a label.

Code in Main form.

    public delegate void UpdateMainProgressDelegate(string message, bool isProgressBarStopped);

            private void UpdateMainProgress(string message, bool isProgressBarStopped)
            {
                // make sure we are running on the right thread to be
                // updating this form's controls.
                if (InvokeRequired == false)
                {
                    // we are running on the right thread.  Have your way with me!
                    bsStatusMessage.Caption = message + " [ " + System.DateTime.Now.ToShortTimeString() + " ]";
                    progressBarStatus.Stopped = isProgressBarStopped;
                }
                else
                {
                    // we are running on the wrong thread.  
                    // Transfer control to the correct thread!                
                    Invoke(new ApplicationLevelValues.UpdateMainProgressDelegate(UpdateMainProgress), message, isProgressBarStopped);
                }
            }

Child Form

private readonly ApplicationLevelValues.UpdateMainProgressDelegate _UpdateMainForm;
private void btnX_Click(object sender, EventArgs e)
        {
            _UpdateMainForm.BeginInvoke("StartA", false, null, null);
            try
            {
                if(UpdateOperationA())
                { _UpdateMainForm.BeginInvoke("CompletedA", true, null, null); }
                else
                { _UpdateMainForm.BeginInvoke("CanceledA", true, null, null); }
            }
            catch (System.Exception ex)
            {
                _UpdateMainForm.BeginInvoke("ErrorA", true, null, null);
                throw ex;
            }
        }

Its working pretty fine, but for N buttons or N operations I have to write the same code again and again. Is there any way this can be generalized or any other better way to update the UI.

3条回答
Evening l夕情丶
2楼-- · 2019-02-24 06:35

This is really just a simple refactoring problem if your operations can all be represented as a single delegate type. e.g.:

private void RunOperationWithMainProgressFeedback(
    Func<bool> operation,
    string startMessage,
    string completionMessage,
    string cancellationMessage,
    string errorMessage)
{
    this._UpdateMainForm.BeginInvoke(startMessage, false, null, null);
    try
    {
        if (operation.Invoke())
        {
            this._UpdateMainForm.BeginInvoke(completionMessage, true, null, null);
        }
        else
        {
            this._UpdateMainForm.BeginInvoke(cancellationMessage, true, null, null);
        }
    }
    catch (Exception)
    {
        this._UpdateMainForm.BeginInvoke(errorMessage, true, null, null);
        throw;
    }
}

private void btnX_Click(object sender, EventArgs e)
{
    this.RunOperationWithMainProgressFeedback(
        this.UpdateOperationA,
        "StartA",
        "CompletedA",
        "CanceledA",
        "ErrorA");
}

While it's possible to use a dictionary to store the argument values (as suggested in VinayC's earlier answer), this isn't necessary. Personally, I would avoid it for both readability and performance reasons, but ymmv...

查看更多
做自己的国王
3楼-- · 2019-02-24 06:47

I got tired of solving this exact same problem over and over, so i wrote a general set of classes to solve this. The basic idea is this:

  • Create an abstract Progress object with fields that you care about, like statusMessage.

  • Create an abstract Job object. Job is like a thread, but in addition to a Main and a Start, it has a method UpdateProgress(Progress p). UpdateProgress copies the current progress to p. It is typically the only place where locks will be acquired.

  • In your particular usage, subclass Progress and add fields as needed. Then subclass Job to hold the stuff you want to do, and implement UpdateProgress.

  • In the UI, create a timer whose main calls job. UpdateProgress then updates ui.

Often, timers are good-enough for ui updates and adopting a pull-and-sync-ui model is simpler than doing a push-changes model. Pull-and-sync-ui avoids a lot of cross threading issues, and having the UI get into weird states.

BONUS: Make your Progress object an INotifyProperyChanged and do data binding. (this is why you may not want to use an IDictionary).

查看更多
女痞
4楼-- · 2019-02-24 06:51

You can create a dictionary/list holding relevant details for each button. These details would include operation name & delegate to invoke the operation etc. Now you can have generic event handler for all buttons that will look up into the dictionary to get operation name and operation to be performed.

Yet another way would be to create your custom button inherited from button and custom button can have these properties to hold above details. Choose as per your liking.

查看更多
登录 后发表回答