【问题标题】:How do I update a progress dialog?如何更新进度对话框?
【发布时间】:2014-05-03 11:53:44
【问题描述】:

我已经阅读了有关 Invoke(ing) 控件和诸如此类的内容......我不知道我应该调用哪些控件,因为我的主窗体和对话框窗体都有多个,因此我的问题在这里。我读过thisthisthis ...我只是不明白如何将它应用到我的情况。是否有某个教程可供我阅读以尝试更好地理解?

我有一个 winform 应用程序 (C#) 可以完成一些工作。其中一些工作可能需要一段时间,所以我想我会提供一个进度对话框来提醒用户活动正在发生(而不是简单地依靠列表控件定期闪烁来指示更新)。

所以,我在我的项目中添加了一个新表单,添加了一些感兴趣的项目(要处理的项目数、预计完成时间、当前项目和整体进度条)。

public ProgressDialog Progress { get; set; }

public Form1()
{
    Progress = new ProgressDialog(this);
    InitializeComponent();
    backgroundWorker1.WorkerReportsProgress = true;
    backgroundWorker1.WorkerSupportsCancellation = true;
}

一旦单击 Process 按钮,我将主要工作设置为在后台工作人员中完成。

private void buttonProcess_Click(object sender, EventArgs e)
{
    if (backgroundWorker1.IsBusy != true)
    {
        backgroundWorker1.RunWorkerAsync();
        Progress.ShowDialog();
    }
}

从在该线程上调用的方法,我在我的 ProgressDialog 表单上调用一个方法:

Progress.UpdateDialog(numFiles: filesToProcess.Count, 
                      processTime: TimeSpan.FromTicks(filesToProcess.Count * TimeSpan.TicksPerSecond * 20)); // 20s is an estimated time, it will be updated after first file is processed.

那里有问题的代码(在 ProgressDialog.cs 中):

public void UpdateDialog(int percentComplete = 0, string fileName = "", int numFiles = 0, TimeSpan? processTime = null)
{
    ...
    if (numFiles > 0)
    {
        labelNumFiles.Text = Convert.ToString(numFiles);
    }
    if (!processTime.Equals(null))
    {
        labelProcessTime.Text = Convert.ToString(processTime);
    }
}

这会导致以下错误:

An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code

Additional information: Cross-thread operation not valid: Control 'ProgressDialog' accessed from a thread other than the thread it was created on.

此外,主窗体有两个列表控件需要在处理文件时更新:已处理文件列表和错误文件列表。在我得到添加进度对话框的绝妙主意之前,一切正常。 LOL 那么,处理更新进度对话框的正确方法是什么?

【问题讨论】:

  • 嗯,多线程基础可以帮助你...
  • 您提供的第二个链接正是您所需要的。谷歌后台工作者 ProgressChanged,你会找到足够的帮助你。 (第二个链接stackoverflow.com/questions/2454900/…
  • 您在不到一个小时前提出了同样的问题,显然您已将其删除。您不应该删除然后一遍又一遍地重新提出相同的问题。正如我当时告诉你的那样,BGW 已经内置了对此的支持。
  • @Servy 我想我也已经看到了。
  • @Jon ProgressChanged 应该随着进度更新 UI。 DoWork 应该做工作而不是处理进度变化。

标签: c# winforms visual-studio-2010 user-interface


【解决方案1】:

UpdateDialog 方法需要在 UI 线程上运行,因为它正在执行 UI 工作。由于它是在 BackgroundWorkerDoWork 期间调用的(很可能不在您的 UI 线程上运行),因此您需要对 Winforms 使用 InvokeRequired/Invoke 模式:

    public void UpdateDialog(int percentComplete = 0, string fileName = "", int numFiles = 0, TimeSpan? processTime = null)
    {
        if (InvokeRequired)
        {
            Invoke(new System.Windows.Forms.MethodInvoker(() => UpdateDialog(percentComplete, fileName, numFiles, processTime)));
        }

        ...
        if (numFiles > 0)
        {
            labelNumFiles.Text = Convert.ToString(numFiles);
        }
        if (!processTime.Equals(null))
        {
            labelProcessTime.Text = Convert.ToString(processTime);
        }
    }

【讨论】:

  • 所以,我将其插入并更新了对话框。我试图为主表单列表视图控件复制这个:void UpdateProcessedFileListView(string file) { if (InvokeRequired) { Invoke(new System.Windows.Forms.MethodInvoker(() => UpdateProcessedFileListView(file))); } ListViewItem item = new ListViewItem(file); listViewProcessed.Items.Add(item); listViewProcessed.Refresh(); } 而且,当列表视图更新时,该方法会抛出一个异常,给我同样的跨线程错误。
  • 更奇怪的是我忘记了我在 DoWork 开始时清除了列表视图控件并且没有给我一个跨线程错误......那么为什么“调用”方法会抛出那个异常?
  • 我不清楚你是如何调用UpdateDialog 方法的。你在处理BackgroundWorkerProgressChanged 事件吗?在您的DoWork 方法中,您应该在BackgroundWorker 上调用ReportProgress。请参阅this 了解更多信息。
  • 我正在这样做:调用 ReportProgress 以更新前台的对话框(另外我从 DoWork 调用它以将其他信息放在上面,就像我在原始问题中显示的那样)。在 DoWork 中,我还调用 UpdateProcessedFileListView 来更新我的主窗体上的列表视图(嗯,我猜你会说它在后台,在对话框后面),其中包含每个已处理文件的名称。上一条评论中的代码。 (我也有一种类似的方法来使用导致 DoWork 错误的文件名更新第二个列表视图,但我一次只关注一个。)
  • @Jon,() => ... 是内联代表的简写。我建议阅读MSDN Lambda Expressions 文章了解更多信息。
【解决方案2】:

您必须使用 Invoke() 方法在用户控制表单中访问与创建线程不同的表单

请参阅 thisthis 以获得更好的论点说明

【讨论】:

  • 不,你没有。事实上,在这种情况下,它甚至不是更可取的解决方案。
猜你喜欢
  • 1970-01-01
  • 2019-08-08
  • 1970-01-01
  • 1970-01-01
  • 2018-06-25
  • 1970-01-01
  • 2011-06-03
  • 1970-01-01
相关资源
最近更新 更多