WPF C# - 如何从另一个线程更新进度条

15
我陷入了一个困境,尝试从其他线程更新在不同类中运行的进度条。为了更好地解释我的做法,我认为图片会更好:enter image description here 我尝试使用委托、使用ReportProgress,我觉得我基本上尝试了前100个搜索结果中的所有方法,但没有成功。我还在学习WPF,这可能是一个愚蠢的方法,我正在寻找一种快速而简单的方法来完成工作,但随时欢迎告诉我应该重新设计什么以使应用程序更加清洁。 编辑: 更多代码。
在ExecutorWindow.xaml.cs 中:
public void RunExecutor()
{
    // CREATE BACKGROUNDWORKER FOR EXECUTOR
    execBackground.DoWork += new DoWorkEventHandler(execBackground_DoWork);
    execBackground.RunWorkerCompleted += new RunWorkerCompletedEventHandler(execBackground_RunWorkerCompleted);
    execBackground.ProgressChanged += new ProgressChangedEventHandler(execBackground_ProgressChanged);
    execBackground.WorkerReportsProgress = true;
    execBackground.WorkerSupportsCancellation = true;
    // RUN BACKGROUNDWORKER
    execBackground.RunWorkerAsync();
}
private void execBackground_DoWork(object sender, DoWorkEventArgs e)
{
    myExecutor = new Executor(arg1, arg2);
    myExecutor.Run();            
}

private void execBackground_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    MessageBox.Show("RunWorkerCompleted execBackground");
}

private void execBackground_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    ExecutorProgressBar.Value += 1;
}

// TESTING 
private void updateProgressBar(int i)
{
    ExecutorProgressBar.Value += i;
}

public delegate void callback_updateProgressBar(int i);
在 Executor.cs 中:
public void Run()
{
    string[] options = new string[2];
    int i = 0;

    while (LeftToRun > 0)
    {
        if (CurrentRunningThreads < MaxThreadsRunning)
        {
            BackgroundWorker myThread = new BackgroundWorker();
            myThread.DoWork += new DoWorkEventHandler(backgroundWorkerRemoteProcess_DoWork);
            myThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorkerRemoteProcess_RunWorkerCompleted);
            myThread.ProgressChanged += new ProgressChangedEventHandler(backgroundWorkerRemoteProcess_ProgressChanged);
            myThread.WorkerReportsProgress = true;
            myThread.WorkerSupportsCancellation = true;

            myThread.RunWorkerAsync(new string[2] {opt1, opt2});

            // HERE ?
            CurrentRunningThreads++;
            i++;
            LeftToRun--;

        }
    }

    while (CurrentRunningThreads > 0) { }
    logfile.Close();
    MessageBox.Show("All Tasks finished");
}

private void backgroundWorkerRemoteProcess_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker myBackgroundWorker = sender as BackgroundWorker;
    string[] options = (string[])e.Argument;
    string machine = options[0];
    string script = options[1];
    // UPDATE HERE PROGRESSBAR ?
    RemoteProcess myRemoteProcess = new RemoteProcess(machine, script);
    string output = myRemoteProcess.TrueExec();
    // UPDATE HERE PROGRESSBAR ?
    this.logfile.WriteLine(output);
}

private void backgroundWorkerRemoteProcess_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    CurrentRunningThreads--;
}

private void backgroundWorkerRemoteProcess_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    //myExecWindow.ExecutorProgressBar.Value = e.ProgressPercentage; // TESTING
    //ExecutorWindow.callback_updateProgressBar(1); // TESTING 
}

编辑2:我搞定了!其实很简单,但我想我一直在太近的地方寻找答案。

在我的ExecutorWindow类中:

private void execBackground_DoWork(object sender, DoWorkEventArgs e)
{
    myExecutor = new Executor(arg1, arg2);
    myExecutor.Run(sender);
}

private void execBackground_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    ExecutorProgressBar.Value += 1;
}

在我的Executor类中:

private BackgroundWorker myExecutorWindow;

[...]

public void Run(object sender)
{
            myExecutorWindow = sender as BackgroundWorker;
            string[] options = new string[2];
            int i = 0;

            while (LeftToRun > 0)
            {
                if (CurrentRunningThreads < MaxThreadsRunning)
                {
                    BackgroundWorker myThread = new BackgroundWorker();
                    myThread.DoWork += new DoWorkEventHandler(backgroundWorkerRemoteProcess_DoWork);
                    myThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorkerRemoteProcess_RunWorkerCompleted);
                    myThread.ProgressChanged += new ProgressChangedEventHandler(backgroundWorkerRemoteProcess_ProgressChanged);
                    myThread.WorkerReportsProgress = true;
                    myThread.WorkerSupportsCancellation = true;

                    myThread.RunWorkerAsync(new string[2] {opt1, opt2});

                    CurrentRunningThreads++;
                    i++;
                    LeftToRun--;      
                }
            }

[...]

private void backgroundWorkerRemoteProcess_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker myBackgroundWorker = sender as BackgroundWorker;
            myBackgroundWorker.ReportProgress(1);
            // PROCESSING MY STUFF HERE
            myBackgroundWorker.ReportProgress(1);
        }

        private void backgroundWorkerRemoteProcess_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            myExecutorWindow.ReportProgress(1);
        }

感谢您!


非常好的描述,+1。你是手动绘制的还是借助了一些代码到图表工具? - Teoman Soygul
糟糕的描述。图片中的代码无法缩放或复制/粘贴。代码看起来也不完整(在关键点处)。DoWork在哪里? - H H
@Teoman Soygul:我使用了Visio完成了它。 @Henk Holterman:没错,我可以用DoWork代码和其他函数进行编辑,但我认为这与主题无关,那就是为什么我没有放它们的原因。 - ack__
@Heandel:是的,我已经按照这个教程尝试过了:http://blogs.msdn.com/b/csharpfaq/archive/2004/03/17/91685.aspx。但我不明白如何正确地将其应用到我的代码中。在示例中,他调用了“m_TextBox.Invoke(...)”,但我无法从我的Executor类中调用进度条.Invoke。 - ack__
4个回答

13

你可以使用这个非常基本的示例在UI线程上运行任何方法。

this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() 
{       
   this.progressBar.Value= 20; // Do all the ui thread updates here
}));

通过在 Dispatcher.Invoke(...) 中运行命令,您实际上可以从任何工作线程与 UI 进行交互,否则会引发异常。

如果您确实需要对后台线程和主(UI)线程更新有绝对控制权,这里有一个非常棒的教程:http://blog.decarufel.net/2009/03/good-practice-to-use-dispatcher-in-wpf.html


这将是普通(池)线程的解决方案。Bgw具有特殊的进度支持(以避免此调用模式)。 - H H

1

你应该能够使用Dispatcher.Invoke方法

例如:

    Dispatcher.Invoke(
        new System.Action(() => myProgressBar.Value = newValue)
        );

1

我明白了!其实很简单,但我想我一直在太近的地方寻找答案。

在我的ExecutorWindow类中:

private void execBackground_DoWork(object sender, DoWorkEventArgs e)
{
    myExecutor = new Executor(arg1, arg2);
    myExecutor.Run(sender);
}

private void execBackground_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    ExecutorProgressBar.Value += 1;
}

在我的 Executor 类中:

private BackgroundWorker myExecutorWindow;

[...]

public void Run(object sender)
{
            myExecutorWindow = sender as BackgroundWorker;
            string[] options = new string[2];
            int i = 0;

            while (LeftToRun > 0)
            {
                if (CurrentRunningThreads < MaxThreadsRunning)
                {
                    BackgroundWorker myThread = new BackgroundWorker();
                    myThread.DoWork += new DoWorkEventHandler(backgroundWorkerRemoteProcess_DoWork);
                    myThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorkerRemoteProcess_RunWorkerCompleted);
                    myThread.ProgressChanged += new ProgressChangedEventHandler(backgroundWorkerRemoteProcess_ProgressChanged);
                    myThread.WorkerReportsProgress = true;
                    myThread.WorkerSupportsCancellation = true;

                    myThread.RunWorkerAsync(new string[2] {opt1, opt2});

                    CurrentRunningThreads++;
                    i++;
                    LeftToRun--;      
                }
            }

[...]

private void backgroundWorkerRemoteProcess_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker myBackgroundWorker = sender as BackgroundWorker;
            myBackgroundWorker.ReportProgress(1);
            // PROCESSING MY STUFF HERE
            myBackgroundWorker.ReportProgress(1);
        }

        private void backgroundWorkerRemoteProcess_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            myExecutorWindow.ReportProgress(1);
        }

0
我找到了一个非常简单的解决方案,可以创建一个线程来运行任何代码块,并处理调用返回主线程以更改控件属性。它可以在.NET 4.5下开箱即用,并且调度程序上的lambda调用可以适应较早版本的.NET。主要好处是当您只需要快速线程来运行一些基本代码时,它非常简单和完美。
所以假设您在对话框中的某个地方有一个进度条,请执行以下操作:
progBar.Minimum = 0;
progBar.Maximum = theMaxValue;
progBar.Value = 0;

Dispatcher disp = Dispatcher.CurrentDispatcher;

new Thread(() => {
    // Code executing in other thread
    while (progBar.Value < theMaxValue)
    {
        // Your application logic here


        // Invoke Main Thread UI updates
        disp.Invoke(
            () =>
            {

                progBar.Value++;
            }
        );

    }
}).Start();

你还需要确保你有对WindowsBase.dll的引用

如果你想要一个更可重用的代码片段作为线程启动,你可以使用一个方法作为委托,但我发现内联lambda对于简单任务非常容易,并且不需要像Background Worker方法那样处理事件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接