【问题标题】:Update progress bar in MainWindow from a different Thread从不同的线程更新 MainWindow 中的进度条
【发布时间】:2023-03-28 04:35:02
【问题描述】:

我知道这个问题已经被问过好几次了,我花了一整天的时间试图理解其他答案,但由于我对 C# 和 WPF 很陌生,到目前为止没有任何帮助。我会尽可能多地解释我的确切问题,以便直接帮助我。

在我的 MainWindow.xaml 中,我有一个进度条和一些按钮,开始一个新线程和一个长计算:

<ProgressBar Height="....... Name="progressBar1"/>
<Button Content="Button" Name="button1" Click="button1_Click" />

现在在我的 MainWindow.xaml.cs 中:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        Thread thread = new Thread(new ParameterizedThreadStart(MyLongCalculation));

        ParameterClass myParameters = new ParameterClass();
        thread.Start(myParameters);
    }

    public void MyLongCalculations(object myvalues)
    {
        ParameterClass values = (ParameterClass)myvalues;
        //some calculations
    }
}

public class ParameterClass
{
    //public variables...
}

现在我必须在我的方法 MyLongCalculations 中包含一些东西,它将不断更新progressBar1。但是,我就是无法让它工作。 我知道这一切都很简单,但不幸的是,这是我目前使用 C# 的水平,所以我希望答案不要太复杂且尽可能详细。

【问题讨论】:

  • 如果您阅读了很多并尝试了很多,您应该在您的问题中详细说明这一点。比如,你读了什么,你尝试了什么,结果是什么,怎么不是你想要的。
  • 你试过Dispatcher.BeginInvoke()吗?
  • 是的,现在我有,我必须先阅读很多内容并查看答案。现在它可以与 Dispatcher 和 BackgroundWorker 一起使用。

标签: c# wpf multithreading progress-bar


【解决方案1】:

后台工作者非常适合这个。

试试这个:

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        // Initialize UI
        InitializeComponent();

        // Process data
        ProcessDataAsync(new ParameterClass { Value = 20 });
    }

    /// <summary>
    /// Processes data asynchronously
    /// </summary>
    /// <param name="myClass"></param>
    private void ProcessDataAsync(ParameterClass myClass)
    {
        // Background worker
        var myWorker = new BackgroundWorker
        {
            WorkerReportsProgress = true,
        };

        // Do Work
        myWorker.DoWork += delegate(object sender, DoWorkEventArgs e)
        {
            // Set result
            e.Result = MyLongCalculations(myClass);

            // Update progress (50 is just an example percent value out of 100)
            myWorker.ReportProgress(50);
        };

        // Progress Changed
        myWorker.ProgressChanged += delegate(object sender, ProgressChangedEventArgs e)
        {
            myProgressBar.Value = e.ProgressPercentage;
        };

        // Work has been completed
        myWorker.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs e)
        {
            // Work completed, you are back in the UI thread.
            TextBox1.Text = (int) e.Result;
        };

        // Run Worker
        myWorker.RunWorkerAsync();
    }

    /// <summary>
    /// Performs calculations
    /// </summary>
    /// <param name="myvalues"></param>
    /// <returns></returns>
    public int MyLongCalculations(ParameterClass myvalues)
    {
        //some calculations
        return (myvalues.Value*2);
    }

}

/// <summary>
/// Custom class
/// </summary>
public class ParameterClass
{
    public int Value { get; set; }
}

【讨论】:

  • 非常感谢,我会试试的。但是,该方法(在这个简单的情况下:MyLongCalculation() 需要来自 UI 的输入。目前我可能刚刚创建了一个用于保存所有值的类,因为使用 ParameterizedThreadStart 只允许输入一个对象. 如何给 BackgroundWorker 提供输入?它可能来自使用我的 ParameterClass 的对象。或者可能直接来自 UI 的 TextBoxes...
  • 我更新了它,使其能够获取结果并将其返回到主线程。 RunWorkerCompleted 在 UI 线程中,因此您可以在此时将返回值添加到 UI 控件而不会发生线程争用。
  • 有没有办法使用 BackgroundWorker 并更新 UI?例如文本框的文本?!
  • 更新以演示更新“TextBox1”。请记住,RunWorkerCompleted 回到了 UI 线程中,因此您可以直接从那里调用您的 UI 对象。
  • 另一个注意事项:在您的其他评论中,您提到计算时间很长。如果您无法合理地分解您的方法并定期更新进度,最好将 ProgressBar 设置为 Indeterminate。
【解决方案2】:

您可以使用Dispatcher.BeginInvoke() 在 UI 线程而不是工作线程上推送 UI 更改。最重要的是 - 您需要访问与 UI 线程相关联的 Dispatcher,而不是您手动创建的工作线程。所以我建议缓存Dispatcher.Current,然后在工作线程中使用,你可以通过ParametersClass或只是在类级别声明一个调度程序字段。

public partial class MainWindow
{
    private Dispatcher uiDispatcher;

    public MainWindow()
    {
        InitializeComponents();

        // cache and then use in worker thread method
        this.uiDispatcher = uiDispatcher;
    }

    public void MyLongCalculations(object myvalues)
    {
        ParameterObject values = (ParameterObject)myvalues;
        this.uiDispatcher.BeginInvoke(/*a calculations delegate*/);
    }
}

此外,如果您需要在某些服务/类中传递 UI 调度程序(例如 ParametersClass),我建议您查看 this nice SO post,它展示了如何通过能够同步推送 UI 更改的接口来抽象它/asynchronously,因此它取决于调用者(基本上使用Invoke()BeginInvoke() 在 UI 消息管道中对委托进行排队)。

【讨论】:

  • 谢谢。我认为要真正理解这一点,我将真正开始学习多线程和调度程序。因为到目前为止我只用 C 编程,所以这样的事情实际上是不可能的,到目前为止,我的所有计算都是在控制台中一个接一个地完成的。突然转向面向对象 + UI + 多线程是一个很大的变化 =S
  • 好的,我设法做到了,但是,我的计算很长,我仍然需要等到其中一个循环完成。我会看看它是如何工作的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-30
  • 2012-05-22
  • 1970-01-01
  • 1970-01-01
  • 2015-04-18
  • 1970-01-01
相关资源
最近更新 更多