如何从命令中获取输出以实时显示在Form的控件中? [英] How do I get output from a command to appear in a control on a Form in real-time?

查看:77
本文介绍了如何从命令中获取输出以实时显示在Form的控件中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

从网络上的各种来源中,我整理了以下代码,用于通过 CMD.exe 执行命令并捕获 STDOUT <的输出/ code>和 STDERR

 公共静态类Exec 
{
公共委托void OutputHandler(String line);

//< summary>
///在子进程中运行命令
///< / summary>
///< param name = path>执行命令的目录< / param>
///< param name = cmd>要执行的命令< / param>
///< param name = args>命令参数< / param>
///< param name = hndlr>命令输出处理程序(如果没有,则为null)< / param>
///< param name = noshow>如果没有要显示的窗口,则为真< / param>
///< returns>从执行的命令中退出代码< / returns>
public static int Run(字符串路径,字符串cmd,字符串args,
OutputHandler hndlr = null,布尔值noshow = true)
{
//假定错误
int ret = 1;
//使用(var p = new Process())创建进程

{
//使用CMD.EXE运行命令
//可以将STDERR传送到STDOUT,以便它们可以一起处理)
p.StartInfo.FileName = cmd.exe;
//设置工作目录(如果提供)
if(!String.IsNullOrWhiteSpace(path))p.StartInfo.WorkingDirectory = path;
//指示命令和参数
p.StartInfo.Arguments = / c \ + cmd + + args + \ 2>& 1;
//处理noshow参数
p.StartInfo.CreateNoWindow = noshow;
p.StartInfo.UseShellExecute = false;
//查看处理程序是否提供了
if(hndlr!= null)
{
//重定向STDOUT和STDERR
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
//使用自定义事件处理程序来捕获输出
,使用(var outputWaitHandle = new AutoResetEvent(false))
{
p.OutputDataReceived + =(sender,e)=>
{
//查看是否有数据
if(e.Data == null)
{
//信号输出处理完成
outputWaitHandle 。组();
}
else
{
//将字符串传递给字符串处理程序
hndlr(e.Data);
}
};
//启动进程
p.Start();
//开始异步读取
p.BeginOutputReadLine();
//等待进程终止
p.WaitForExit();
//等待输出处理完成信号
outputWaitHandle.WaitOne();
}
}
else
{
//启动进程
p.Start();
//等待进程终止
p.WaitForExit();
}
//获取退出代码
ret = p.ExitCode;
}
//返回结果
return ret;
}

//< summary>
///在子进程中运行命令,并在变量
中返回输出///< / summary>
///< param name = path>执行命令的目录< / param>
///< param name = cmd>要执行的命令< / param>
///< param name = args>命令参数< / param>
///< param name = outp>可能包含输出< / param>
///< returns>从执行的命令中退出代码< / returns>
public static GetOutputReturn GetOutput(String path,String cmd,String args)
{
GetOutputReturn ret = new GetOutputReturn();
ret.ReturnCode = Run(path,cmd,args,(line)=>;
{
ret.Output.AppendLine(line);
});
回程;
}
}

公共类GetOutputReturn
{
public StringBuilder Output = new StringBuilder();
public int ReturnCode = 1;
}

我可以通过以下三种不同方式在控制台应用中使用它:

 静态void Main(string [] args)
{
int ret;
Console.WriteLine(执​​行目录而没有捕获,没有窗口);
ret = Exec.Run(@ C:\, dir,);
Console.WriteLine(执​​行返回 + ret);
Console.WriteLine(按Enter键继续...);
Console.ReadLine();
Console.WriteLine(执​​行没有捕获和窗口的目录);
ret = Exec.Run(@ C:\, dir,,null,false);
Console.WriteLine(执​​行返回 + ret);
Console.WriteLine(按Enter键继续...);
Console.ReadLine();
Console.WriteLine(执​​行具有捕获功能且没有窗口的目录);
var结果= Exec.GetOutput(@ C:\, dir,);
Console.WriteLine(results.Output.ToString());
Console.WriteLine(执​​行返回 +结果.ReturnCode);
Console.ReadLine();
Console.WriteLine(执​​行具有实时捕获且无窗口的目录);
ret = Exec.Run(@ C:\, dir,,ShowString);
Console.WriteLine(执​​行返回 + ret);
}

公共代表void StringData(String str);

静态void ShowString(String str)
{
Console.WriteLine(str);
}

公共代表void StringData(String str);

静态void ShowString(String str)
{
Console.WriteLine(str);
}

第一次运行不收集任何输出,仅显示退出代码。< br>
第二次运行不收集任何输出,而是显示窗口。

输出实时显示在控制台窗口中的效果。

第三次运行使用GetOutput来收集输出。

这样做的效果是直到运行完成才显示输出。

最后一次运行使用处理程序来接收和显示

在外观上看起来像第二次运行,但有很大不同。

对于接收到的每一行输出,调用ShowString。

显示字符串只是显示字符串。

但是,它可以对数据执行任何所需的操作。



我试图调整上一次运行的时间,以便可以实时更新带有命令输出的文本框。我遇到的问题是如何在正确的背景下获得它(因为缺乏更好的期限)。因为OutputHandler是异步调用的,所以它必须使用 InvokeRequired / BeginInvoke / EndInvoke 机制与UI线程同步。我对如何使用参数有一点问题。在我的代码中,textBox可能是选项卡控件中的几个,因为可能会发生多个背景运行。



到目前为止,我有这个:

  private void btnExecute_Click(object sender,EventArgs e)
{
//获取当前选择的标签页
var page = tcExecControl.SelectedTab;
//获取文本框(总是页面上的第3个控件)
var txt =(TextBox)page.Controls [2];
//创建字符串处理程序
var prc = new Exec.OutputHandler((String line)=>
{
if(txt.InvokeRequired)
txt.Invoke (new MethodInvoker(()=>
{txt.Text + = line;}));
else txt.Text + = line;
});
//命令和参数始终是页面上的第一和第二控件
var result = Exec.Run(@ C:\,page.Controls [0] .Text,page.Controls [ 1],prc);
}

但这似乎行不通。我看不到txtBox的任何输出。

实际上,该程序基本上挂在处理程序中。



如果我更改代码以使用GetOutput然后将结果输出写入文本框,一切正常。所以我知道我已经正确设置了命令。使用调试器,我可以在 if( txt.InvokeRequired )行上设置断点,并且看到输出的第一行正确显示。此时,代码采用了if语句的真实路径,但是如果我在 txt.Text + =行; 行中设置了断点



有人可以帮我吗?我确定我缺少什么。

解决方案

此示例中代码的简要说明。



首先使用 start / WAIT运行shell命令( cmd.exe 作为参数。与 / k 大致相同的功能:控制台启动时没有任何特定任务,等待发送命令后处理命令。




From various sources on the web, I have put together the following code for executing a command via CMD.exe and capturing output from STDOUT and STDERR.

public static class Exec
{
    public delegate void OutputHandler(String line);

    // <summary>
    /// Run a command in a subprocess
    /// </summary>
    /// <param name="path">Directory from which to execute the command</param>
    /// <param name="cmd">Command to execute</param>
    /// <param name="args">Arguments for command</param>
    /// <param name="hndlr">Command output handler (null if none)</param>
    /// <param name="noshow">True if no windows is to be shown</param>
    /// <returns>Exit code from executed command</returns>
    public static int Run(String path, String cmd, String args,
                          OutputHandler hndlr = null, Boolean noshow = true)
    {
        // Assume an error
        int ret = 1;
        // Create a process
        using (var p = new Process())
        {
            // Run command using CMD.EXE
            // (this way we can pipe STDERR to STDOUT so they can get handled together)
            p.StartInfo.FileName = "cmd.exe";
            // Set working directory (if supplied)
            if (!String.IsNullOrWhiteSpace(path)) p.StartInfo.WorkingDirectory = path;
            // Indicate command and arguments
            p.StartInfo.Arguments = "/c \"" + cmd + " " + args + "\" 2>&1";
            // Handle noshow argument
            p.StartInfo.CreateNoWindow = noshow;
            p.StartInfo.UseShellExecute = false;
            // See if handler provided
            if (hndlr != null)
            {
                // Redirect STDOUT and STDERR
                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.RedirectStandardError = true;
                // Use custom event handler to capture output
                using (var outputWaitHandle = new AutoResetEvent(false))
                {
                    p.OutputDataReceived += (sender, e) =>
                    {
                        // See if there is any data
                        if (e.Data == null)
                        {
                            // Signal output processing complete
                            outputWaitHandle.Set();
                        }
                        else
                        {
                            // Pass string to string handler
                            hndlr(e.Data);
                        }
                    };
                    // Start process
                    p.Start();
                    // Begin async read
                    p.BeginOutputReadLine();
                    // Wait for process to terminate
                    p.WaitForExit();
                    // Wait on output processing complete signal
                    outputWaitHandle.WaitOne();
                }
            }
            else
            {
                // Start process
                p.Start();
                // Wait for process to terminate
                p.WaitForExit();
            }
            // Get exit code
            ret = p.ExitCode;
        }
        // Return result
        return ret;
    }

    // <summary>
    /// Run a command in a subprocess and return output in a variable
    /// </summary>
    /// <param name="path">Directory from which to execute the command</param>
    /// <param name="cmd">Command to execute</param>
    /// <param name="args">Arguments for command</param>
    /// <param name="outp">Variable to contain the output</param>
    /// <returns>Exit code from executed command</returns>
    public static GetOutputReturn GetOutput(String path, String cmd, String args)
    {
        GetOutputReturn ret = new GetOutputReturn();
        ret.ReturnCode = Run(path, cmd, args, (line) =>
                             {
                               ret.Output.AppendLine(line);
                             });
        return ret;
    }
}

public class GetOutputReturn
{
    public StringBuilder Output = new StringBuilder();
    public int ReturnCode = 1;
}

I am able to use this in a console app in three different manners as follows:

static void Main(string[] args)
{
    int ret;
    Console.WriteLine("Executing dir with no capture and no window");
    ret = Exec.Run(@"C:\", "dir", "");
    Console.WriteLine("Execute returned " + ret);
    Console.WriteLine("Press enter to continue ...");
    Console.ReadLine();
    Console.WriteLine("Executing dir with no capture and window");
    ret = Exec.Run(@"C:\", "dir", "", null, false);
    Console.WriteLine("Execute returned " + ret);
    Console.WriteLine("Press enter to continue ...");
    Console.ReadLine();
    Console.WriteLine("Executing dir with capture and no window");
    var results = Exec.GetOutput(@"C:\", "dir", "");
    Console.WriteLine(results.Output.ToString());
    Console.WriteLine("Execute returned " + results.ReturnCode);
    Console.ReadLine();
    Console.WriteLine("Executing dir with real-time capture and no window");
    ret = Exec.Run(@"C:\", "dir", "", ShowString);
    Console.WriteLine("Execute returned " + ret);
}

public delegate void StringData(String str);

static void ShowString(String str)
{
    Console.WriteLine(str);
}

public delegate void StringData(String str);

static void ShowString(String str)
{
    Console.WriteLine(str);
}

The first run does not gather any output and just shows the exit code.
The second run does not gather any output but shows the window.
The effect of this that the output appears in the console window real-time.
The third run uses GetOutput to gather the output.
The effect of this is that the output does not appear until the run is completed.
The last run uses a handler to receive and display the output real-time.
In appearance this looks like the second run but it is very different.
For each line of output that is received ShowString is called.
Show string simply displays the string.
However, it could do anything it needs with the data.

I am trying to adapt the last run such that I can update a text box with the output of the command in real time. The issue that I am having is how to get it in the right context (for lack of a better term). Because OutputHandler is called asynchronously, it has to use the InvokeRequired/BeginInvoke/EndInvoke mechanism to get in sync with the UI thread. I am having a little problem with how to do this with parameters. In my code the textBox could be one of several in a tab control as several background "Run"'s could be taking place.

So far I have this:

private void btnExecute_Click(object sender, EventArgs e)
{
    // Get currently selected tab page
    var page = tcExecControl.SelectedTab;
    // Get text box (always 3rd control on the page)
    var txt = (TextBox)page.Controls[2];
    // Create string handler
    var prc = new Exec.OutputHandler((String line) =>
                  {
                      if (txt.InvokeRequired)
                          txt.Invoke(new MethodInvoker(() =>
                                     { txt.Text += line; }));
                          else txt.Text += line;
                   });
    // Command and arguments are always 1st and 2nd controls on the page
    var result = Exec.Run(@"C:\", page.Controls[0].Text, page.Controls[1], prc);                              
}

But this does not seem to be working. I am not seeing any output to the txtBox.
In fact the program basically hangs in the handler.

If I change the code to use GetOutput and then write the resulting output to the text box everything works. So I know that I have the command set up properly. Using the debugger, I am able to set a break point on the "if (txt.InvokeRequired)" line and I see the first line of output coming correctly. At this point the code takes the true path of the if statement, but if I set a breakpoint on the txt.Text += line; line it never gets there.

Can anyone help me out? I'm sure I'm missing something.

解决方案

A brief description of what the code performs in this example.

The shell command (cmd.exe) is run first, using start /WAIT as parameter. More or less the same functionality as /k: the console is started without any specific task, waiting to process a command when one is sent.

StandardOutput, StandardError and StandardInput are all redirected, setting RedirectStandardOutput, RedirectStandardError and RedirectStandardInput properties of the ProcessStartInfo to true.

The console Output stream, when written to, will raise the OutputDataReceived event; it's content can be read from the e.Data member of the DataReceivedEventArgs.
StandardError will use its ErrorDataReceived event for the same purpose.
(You could use a single event handler for both the events, but, after some testing, you might realize that is probably not a good idea. Having them separated avoids some weird overlapping and allows to easily tell apart errors from normal output).

StandardInput can be redirected assigning it to a StreamWriter stream.
Each time a string is written to the stream, the console will interpret that input as a command to be executed.

Also, the Process is instructed to rise it's Exited event upon termination, setting its EnableRaisingEvents property to true.
The Exited event is raised when the Process is closed because an Exit command is processed, calling the .Close() method or the .Kill() method.
The .Kill() method should only be used when a Process is not responding anymore, for some reason.

Since we need to pass the console Output to some UI controls (RichTextBoxes in this example), we must synchronize the events context with the UI's.
This can be done using the Process SynchronizingObject property, setting it to an UI control (the container Window being probably the first choice, since there's more than one control to synch) or using the Control.BeginInvoke method, that will execute a delegate function on the thread where the control's handle belongs.
Here, a MethodInvoker representing the delegate is used for this purpose.


The core function used to instantiate the Process and set its properties and event handlers:

using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;

StreamWriter stdin = null;

private void StartCmdProcess()
{
    ProcessStartInfo pStartInfo = new ProcessStartInfo {
        FileName = "cmd.exe",
        Arguments = "start /WAIT",
        WorkingDirectory = Environment.SystemDirectory,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        RedirectStandardInput = true,
        UseShellExecute = false,
        CreateNoWindow = true,
    };

    Process cmdProcess = new Process {
        StartInfo = pStartInfo,
        EnableRaisingEvents = true,
        // Test without and with this
        // When SynchronizingObject is set, no need to BeginInvoke()
        //SynchronizingObject = this
    };

    cmdProcess.Start();
    cmdProcess.BeginErrorReadLine();
    cmdProcess.BeginOutputReadLine();
    stdin = cmdProcess.StandardInput;

    cmdProcess.OutputDataReceived += (s, evt) => {
        if (evt.Data != null)
        {
            BeginInvoke(new MethodInvoker(() => {
                rtbStdOut.AppendText(evt.Data + Environment.NewLine);
                rtbStdOut.ScrollToCaret();
            }));
        }
    };

    cmdProcess.ErrorDataReceived += (s, evt) => {
        if (evt.Data != null) {
            BeginInvoke(new Action(() => {
                rtbStdErr.AppendText(evt.Data + Environment.NewLine);
                rtbStdErr.ScrollToCaret();
            }));
        }
    };

    cmdProcess.Exited += (s, evt) => {
        cmdProcess.Close();
        if (cmdProcess != null) {
            cmdProcess.Dispose();
        }
    };
}


Since the StandardInput has beed redirected to a StreamWriter:

stdin = cmdProcess.StandardInput;

we just write to the Stream to execute a command:

stdin.WriteLine(["Command Text"]);


The sample Form can be downloaded from PasteBin.

这篇关于如何从命令中获取输出以实时显示在Form的控件中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆