Here's a source code pattern you can use based on some WinForms example code, but you can apply it for WPF as well very easily. In this example, I am redirecting output to a Console which I then use to let the background worker write some messages to a textbox while it is processing.
It consists of:
- A helper class
TextBoxStreamWriter
used to redirect console output to a textbox
- A background worker writing to the redirected console
- A progress bar which needs to be reset after completion of background worker
- Some text boxes (txtPath and txtResult), and a "Start" button
In other words, there is some background task which needs to interact with the UI. Now I am going to show how that is done.
From the context of the background task, you need to use Invoke
to access any UI element. I believe the simplest way to do that is to use lambda expression syntax, like
progressBar1.Invoke((Action) (() =>
{ // inside this context, you can safely access the control
progressBar1.Style = ProgressBarStyle.Continuous;
}));
To update the ProgressBar, a local method like
private void UpdateProgress(int value)
{
progressBar1.Invoke((Action)(() => { progressBar1.Value = value; }));
}
helps. It is passing the value
parameter to the progress bar as a closure.
This is the helper class TextBoxStreamWriter, which is used to redirect console output:
public class TextBoxStreamWriter : TextWriter
{
TextBox _output = null;
public TextBoxStreamWriter(TextBox output)
{
_output = output;
}
public override void WriteLine(string value)
{
// When character data is written, append it to the text box.
// using Invoke so it works in a different thread as well
_output.Invoke((Action)(() => _output.AppendText(value+"\r\n")));
}
}
You need to use it in the form load event as follows (where txtResult
is a textbox, to which the output will be redirected):
private void Form1_Load(object sender, EventArgs e)
{
// Instantiate the writer and redirect the console out
var _writer = new TextBoxStreamWriter(txtResult);
Console.SetOut(_writer);
}
There is also a button on the form which starts the background worker, it passes a path to it:
private void btnStart_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync(txtPath.Text);
}
This is the workload of the background worker, note how it uses the console to output messages to the textbox (because of the redirection that was set up earlier):
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
var selectedPath = e.Argument as string;
Console.Out.WriteLine("Processing Path:"+selectedPath);
// ...
}
The variable selectedPath
consists of the path that was passed to the backgroundWorker1
earlier via the parameter txtPath.Text
, it is being accessed via e.Argument
.
If you need to reset some controls afterwards, do it in the following way (as already mentioned above):
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Invoke((Action) (() =>
{
progressBar1.MarqueeAnimationSpeed = 0;
progressBar1.Style = ProgressBarStyle.Continuous;
}));
}
In this example, after completion, a progress bar is being reset.
Important: Whenever you access a GUI control, use Invoke
as I did in the examples above.
Using Lambda's makes it easy, as you could see in the code.
And here's the complete example, which runs in LinqPad 6 (just copy and paste it into an empty C# Program query) - I decided to use LinqPad this time so you can learn something new, because you all know how to create a new Windows Forms project in Visual Studio (and if you still want to do so, just copy the events below and drag and drop the controls to the form):
// see: https://stackoverflow.com/a/27566468/1016343
using System.ComponentModel;
using System.Windows.Forms;
BackgroundWorker backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
ProgressBar progressBar1 = new ProgressBar() { Text = "Progress", Width = 250, Height=20, Top=10, Left=0 };
TextBox txtPath = new TextBox() { Text =@"C:\temp\", Width = 100, Height=20, Top=30, Left=0 };
TextBox txtResult = new TextBox() { Text = "", Width = 200, Height=250, Top=70, Left=0, Multiline=true, Enabled=false };
Button btnStart = new Button() { Text = "Start", Width = 100, Height=30, Top=320, Left=0 };
void Main()
{
// see: https://www.linqpad.net/CustomVisualizers.aspx
// Instantiate the writer and redirect the console out
var _writer = new TextBoxStreamWriter(txtResult);
Console.SetOut(_writer);
// wire up events
btnStart.Click += (object sender, EventArgs e) => btnStart_Click(sender, e);
backgroundWorker1.DoWork += (object sender, DoWorkEventArgs e) => backgroundWorker1_DoWork(sender, e);
backgroundWorker1.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e)
=> backgroundWorker1_RunWorkerCompleted(sender, e);
using var frm = new Form() {Text="Form", Width = 300, Height=400, Top=0, Left=0};
frm.Controls.Add(progressBar1);
frm.Controls.Add(txtPath);
frm.Controls.Add(txtResult);
frm.Controls.Add(btnStart);
// display controls
frm.ShowDialog();
}
private void btnStart_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync(txtPath.Text);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
InitProgress();
var selectedPath = e.Argument as string;
Console.Out.WriteLine("Processing Path: " + selectedPath);
UpdateProgress(0); Thread.Sleep(300); UpdateProgress(30); Thread.Sleep(300);
UpdateProgress(50); Thread.Sleep(300);
Console.Out.WriteLine("Done.");
// ...
}
private void UpdateProgress(int value)
{
progressBar1.Invoke((Action)(() =>
{
progressBar1.Value = value;
}));
}
private void InitProgress()
{
progressBar1.Invoke((Action)(() =>
{
progressBar1.MarqueeAnimationSpeed = 0;
progressBar1.Style = ProgressBarStyle.Continuous;
}));
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
UpdateProgress(100); // always show 100% when done
}
// You can define other methods, fields, classes and namespaces here
public class TextBoxStreamWriter : TextWriter
{
TextBox _output = null;
public TextBoxStreamWriter(TextBox output)
{
_output = output;
}
public override Encoding Encoding => throw new NotImplementedException();
public override void WriteLine(string value)
{
// When character data is written, append it to the text box.
// using Invoke so it works in a different thread as well
_output.Invoke((Action)(() => _output.AppendText(value + "\r\n")));
}
}