这是一个基于一些 WinForms 示例代码的源代码模式,您可以使用它,但您也可以非常轻松地将其应用于 WPF。在此示例中,我将输出重定向到控制台,然后我使用该控制台让后台工作人员在处理时将一些消息写入文本框。
它包括:
- 帮助类
TextBoxStreamWriter 用于将控制台输出重定向到文本框
- 后台工作人员写入重定向控制台
- 后台工作完成后需要重置的进度条
- 一些文本框(txtPath 和 txtResult),以及一个“开始”按钮
换句话说,有一些后台任务需要与 UI 交互。现在我将展示它是如何完成的。
从后台任务的上下文中,您需要使用 Invoke 来访问任何 UI 元素。我相信最简单的方法是使用 lambda 表达式语法,例如
progressBar1.Invoke((Action) (() =>
{ // inside this context, you can safely access the control
progressBar1.Style = ProgressBarStyle.Continuous;
}));
要更新 ProgressBar,一个本地方法,比如
private void UpdateProgress(int value)
{
progressBar1.Invoke((Action)(() => { progressBar1.Value = value; }));
}
帮助。它将value 参数作为闭包传递给进度条。
这是用于重定向控制台输出的辅助类TextBoxStreamWriter,:
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")));
}
}
您需要在表单加载事件中使用它,如下所示(其中txtResult是一个文本框,输出将被重定向到该文本框):
private void Form1_Load(object sender, EventArgs e)
{
// Instantiate the writer and redirect the console out
var _writer = new TextBoxStreamWriter(txtResult);
Console.SetOut(_writer);
}
表单上还有一个按钮启动后台工作程序,它传递了一个路径:
private void btnStart_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync(txtPath.Text);
}
这是后台工作人员的工作量,请注意它如何使用控制台将消息输出到文本框(因为之前设置的重定向):
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
var selectedPath = e.Argument as string;
Console.Out.WriteLine("Processing Path:"+selectedPath);
// ...
}
变量selectedPath 包含之前通过参数txtPath.Text 传递给backgroundWorker1 的路径,它正在通过e.Argument 访问。
如果您之后需要重置某些控件,请按以下方式进行(如上所述):
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Invoke((Action) (() =>
{
progressBar1.MarqueeAnimationSpeed = 0;
progressBar1.Style = ProgressBarStyle.Continuous;
}));
}
在此示例中,完成后,正在重置进度条。
重要提示:每当您访问 GUI 控件时,使用Invoke,就像我在上面的示例中所做的那样。
正如您在代码中看到的那样,使用 Lambda 让事情变得简单。
这是在 LinqPad 6 中运行的完整示例(只需将其复制并粘贴到一个空的 C# 程序查询中)- 这次我决定使用 LinqPad,以便您可以学习新的东西,因为你们都知道如何创建一个Visual Studio 中的新 Windows 窗体项目(如果您仍想这样做,只需复制下面的事件并将控件拖放到窗体中):
// 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")));
}
}