Winforms异步更新UI模式 - 需要概括

时间:2010-08-27 07:23:47

标签: c# winforms user-interface asynchronous progress-bar

设置:带有进度条和标签的主MDI表单。

主要表单中的代码。

    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);
                }
            }

儿童表格

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;
            }
        }

它的工作非常好,但是对于N个按钮或N个操作,我必须一次又一次地编写相同的代码。有没有什么方法可以推广或更新UI的任何其他更好的方法。

3 个答案:

答案 0 :(得分:4)

如果您的操作都可以表示为单个委托类型,那么这实际上只是一个简单的重构问题。 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");
}

虽然可以使用字典存储参数值(如VinayC早期的答案中所述),但这不是必需的。就个人而言,我会因为可读性和性能原因而避免使用它,但是ymmv ......

答案 1 :(得分:2)

您可以创建一个字典/列表,其中包含每个按钮的相关详细信息。这些细节将包括操作名称和委托调用操作等。现在,您可以为所有按钮设置通用事件处理程序,这些按钮将查找字典以获取要执行的操作名称和操作。

另一种方法是创建从按钮继承的自定义按钮,自定义按钮可以具有这些属性来保存上述细节。根据自己的喜好选择。

答案 2 :(得分:1)

我厌倦了一遍又一遍地解决这个完全相同的问题,所以我写了一套通用的类来解决这个问题。基本思路是:

  • 使用您关注的字段创建一个抽象的Progress对象,例如statusMessage。

  • 创建一个抽象的Job对象。 Job就像一个线程,但除了Main和Start之外,它还有一个方法UpdateProgress(Progress p)。 UpdateProgress将当前进度复制到p。它通常是唯一可以获得锁的地方。

  • 在您的特定用法中,子类Progress并根据需要添加字段。然后继承Job来保存你想要做的东西,并实现UpdateProgress。

  • 在UI中,创建一个主调用作业的计时器。 UpdateProgress然后更新ui。

通常,定时器对于ui更新来说已经足够了,并且采用pull-and-sync-ui模型比执行推送更改模型更简单。 Pull-and-sync-ui避免了很多交叉线程问题,并且让UI进入了奇怪的状态。

BONUS:让您的Progress对象成为INotifyProperyChanged并进行数据绑定。 (这就是你可能不想使用IDictionary的原因。)