Update progress bar in MainWindow from a different

2019-02-07 19:25发布

问题:

I know this question has been asked several times an I spent all day trying to understand other answers, but since I am very new to C# and WPF nothing helped me so far. I will try to explain my exact problem as much as I can so it will directly help me.

In my MainWindow.xaml I have a progress bar and some button starting a new thread and a long calculation:

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

Now within my 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...
}

Now somehow I have to include somethign in my method MyLongCalculations that will keep updating progressBar1. However, I just can't manage to get it working. I know all this is very simple, but unfortunately it is the level I am at the moment on with C# so I hope an answer not too complicated and as detailed as possible would be great.

回答1:

Background worker is well suited for this.

try this:

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


回答2:

You can use Dispatcher.BeginInvoke() to push UI changes on the UI thread rather than worker thread. Most important thing - you need to access Dispatcher which is associated with UI thread not a worker thread you are creating manually. So I would suggest cache Dispatcher.Current and then use in a worker thread, you can do this via ParametersClass or just declaring a dispatchr field on a class level.

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

Also if you need to pass a UI dispatcher in some service/class (like ParametersClass) I would suggest take a look at this nice SO post which show how you can abstract it by an interfaces with ability to push UI changes synchronously/asynchronously so it would be up to a caller (basically use Invoke() or BeginInvoke() to queue a delegate in the UI messages pipeline).