我有一个包含Classes的程序
Upload
类使用Process
来运行命令行FTP应用程序。我想返回FTP应用程序生成的输出,以便在GUI中的textbox
中显示
我尝试使用以下被截断的代码。
上传类(beginProcess()是用于启动线程的方法(此处未显示)):
public delegate void WputOutputHandler(object sender, DataReceivedEventArgs e);
class Upload
{
private WputOutputHandler wputOutput;
beginProcess()
{
Process pr = new Process();
pr.StartInfo.FileName = @"wput.exe";
pr.StartInfo.RedirectStandardOutput = true;
pr.StartInfo.UseShellExecute = false;
pr.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
pr.OutputDataReceived += new DataReceivedEventHandler(OnDataReceived);
pr.ErrorDataReceived += new DataReceivedEventHandler(OnDataReceived);
pr.Start();
pr.BeginOutputReadLine();
pr.WaitForExit();
}
public void OnDataReceived(object sender, DataReceivedEventArgs e)
{
if(wputOutput != null)
{
wputOutput(sender, e);
}
}
public event WputOutputHandler WputOutput
{
add
{
wputOutput += value;
}
remove
{
wputOutput -= value;
}
}
}
缓冲类:
public void EventSubscriber()
{
uploadSession.WputOutput += Main.writeToTextBoxEvent;
}
主类:
public void writeToTextBoxEvent(object sender, DataReceivedEventArgs e)
{
if(this.textBox1.InvokeRequired)
{
MethodInvoker what now?
}
else
{
textBox1.Text = e.Data;
}
}
正如您所看到的,当它来到Main方法的writeToTextBoxEvent时,我已经没有想法了。我不确定使用自定义事件进行UI更新是否是最好的方法。如果有人能指出我正确的方向,我将非常感激。
答案 0 :(得分:3)
这个怎么样:
public void writeToTextBoxEvent(object sender, DataReceivedEventArgs e)
{
if(this.textBox1.InvokeRequired)
{
// In .Net 2.0
this.textBox1.BeginInvoke(new MethodInvoker(() => writeToTextBoxEvent(sender, e)));
// In .Net 3.5 (above is also possible, but looks nicer)
this.textBox1.BeginInvoke(new Action(() => writeToTextBoxEvent(sender, e)));
}
else
{
textBox1.Text = e.Data;
}
}
此方法对Richard解决方案的优势在于您无需编写执行代码两次(在BeginInvoke()
内并再次在else
路径中)。
如果您使用.Net 3.5,请将其作为扩展方法:
public static class ControlExtensions
{
public static void SafeInvoke(this Control control, Action action)
{
if (control.InvokeRequired)
{
control.Invoke(action);
}
else
{
action();
}
}
public static void SafeBeginInvoke(this Control control, Action action)
{
if (control.InvokeRequired)
{
control.BeginInvoke(action);
}
else
{
action();
}
}
}
并以这种方式使用:
public void writeToTextBoxEvent(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
// Write it as a single line
this.textBox1.SafeBeginInvoke(new Action(() => textBox1.Text = e.Data));
this.textBox1.SafeBeginInvoke(new Action(() =>
{
//Write it with multiple lines
textBox1.Text = e.Data;
}));
}
答案 1 :(得分:2)
if(this.textBox1.InvokeRequired)
{
Action a = () => { textBox1.Text = e.Data; };
textBox1.Invoke(a);
}
即。使用Invoke
方法使用委托(可以是用于捕获局部变量的闭包)。这将调度委托在文本框的线程上执行。当代表完成执行时,Invoke
将返回。
当您不需要等待(BeginInvoke
)时,还有一个异步版本。
编辑:忘记因为Invoke
采用Delegate
类型推断失败,所以请使用local。 NB 您当然可以在较早的时候创建委托,并在两个分支中使用它以避免重复的代码。
答案 2 :(得分:2)
这是一种流行的扩展机制:
public static void InvokeIfRequired(this System.Windows.Forms.Control c,
Action action) {
if (c.InvokeRequired) {
c.Invoke((Action)(() => action()));
}
else {
action();
}
}
用法:
public void writeToTextBoxEvent(object sender, DataReceivedEventArgs e) {
this.textBox1.InvokeIfRequired(() => { textBox1.Text = e.Data; }
}
答案 3 :(得分:0)
如果您需要响应式UI,则需要工作线程和主线程来更新UI。
我认为您需要一个工作线程来查找正在接收的数据,然后从您的工作线程使用委托将数据写入您的TextBox。
类似的东西:
public delegate void textBoxWriteDelegate(string msg);
private void textBoxWrite(string sMess) {
textBox.AppendText(sMess);
}
从你的工作线程:
Invoke(new textBoxWriteDelegate(textBoxWrite), new object [] { "Launching ftp cmd line .... \n" });
对不起,这是净1.1;)有一个更好的方式来编写2.0及更高版本的代表......
答案 4 :(得分:0)
我为此创建了一个小帮手:
class LayoutsEngine
{
internal static void ThreadSafeDoer(Form Form, Delegate Whattodo)
{
if (!Form.IsDisposed)
{
if (Form.InvokeRequired)
{
Form.Invoke(Whattodo);
}
else
{
Whattodo.DynamicInvoke();
}
}
}
}
我正在使用它:
LayoutsEngine.ThreadSafeDoer(this, new MethodInvoker(delegate()
{
txtWhatever.Text=whatever();
// and any other GUI meddling
}));
此外,如果有人可以在此处减少MethodInvoker()
,请发表评论。