为了账号安全,请及时绑定邮箱和手机立即绑定

如何从命令中获取输出以实时显示在窗体上的控件中?

如何从命令中获取输出以实时显示在窗体上的控件中?

C#
慕运维8079593 2021-11-14 17:04:43
从网络上的各种来源,我已经把下面的代码用于经由执行命令CMD.exe,并从捕获输出STDOUT和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      第一次运行不收集任何输出,只显示退出代码。第二次运行不会收集任何输出,但会显示窗口。这样做的效果是输出实时出现在控制台窗口中。第三次运行使用 GetOutput 来收集输出。这样做的效果是在运行完成之前不会出现输出。最后一次运行使用处理程序实时接收和显示输出。从外观上看,这看起来像第二次运行,但它非常不同。对于接收到的每一行输出,调用 ShowString。Show string 只是显示字符串。然而,它可以对数据做任何它需要的事情。我正在尝试调整最后一次运行,以便我可以使用命令的输出实时更新文本框。我遇到的问题是如何在正确的上下文中使用它(因为没有更好的术语)。因为 OutputHandler 是异步调用的,所以它必须使用InvokeRequired/BeginInvoke/EndInvoke机制与 UI 线程同步。我对如何使用参数执行此操作有一点问题。在我的代码中,文本框可能是选项卡控件中的几个之一,因为可能会发生多个背景“运行”。
查看完整描述

1 回答

?
潇湘沐

TA贡献1816条经验 获得超6个赞

代码在此示例中执行的操作的简要说明:


shell 命令 ( cmd.exe) 首先运行,start /WAIT用作参数。或多或少与以下功能相同/k:控制台在没有任何特定任务的情况下启动,在发送命令时等待处理命令。


StandardOutput,StandardError并且StandardInput都被重定向,将ProcessStartInfo 的RedirectStandardOutput、RedirectStandardError和RedirectStandardInput属性设置为。true


控制台输出流在写入时将引发OutputDataReceived事件;它的内容可以从DataReceivedEventArgs的e.Data成员中读取。将其ErrorDataReceived事件用于相同目的。 您可以对这两个事件使用单个事件处理程序,但是,经过一些测试后,您可能会意识到这可能不是一个好主意。将它们分开可以避免一些奇怪的重叠,并允许轻松区分错误与正常输出(注意,您可以找到写入错误流而不是输出流的程序)。

StandardError


StandardInput可以重定向,将其分配给StreamWriter流。

每次将字符串写入流时,控制台都会将该输入解释为要执行的命令。


此外,进程被指示在终止时引发它的Exited事件,将其EnableRaisingEvents属性设置为true。

在Exited当该过程被关闭,因为引发事件Exit命令被处理或调用.Close()方法(或最终的.Kill()方法,应仅用于whcich仅当一个进程不响应了,对于一些原因)。


由于我们需要将控制台输出传递给某些 UI 控件(RichTextBoxes在此示例中)并且 Process 事件在 ThreadPool 线程中引发,因此我们必须将此上下文与 UI 同步。

这可以通过使用 Process SynchronizingObject属性、将其设置为父窗体或使用Control.BeginInvoke方法来完成,该方法将在控件句柄所属的线程上执行委托函数。

在这里,代表委托的MethodInvoker用于此目的。


用于实例化 Process 并设置其属性和事件处理程序的核心函数:


using System;

using System.Diagnostics;

using System.IO;

using System.Windows.Forms;


StreamWriter stdin = null;


public partial class frmCmdInOut : Form

{

    Process cmdProcess = null;

    StreamWriter stdin = null;


    public frmCmdInOut() => InitializeComponent();


    private void MainForm_Load(object sender, EventArgs e)

    {

        rtbStdIn.Multiline = false;

        rtbStdIn.SelectionIndent = 20;

    }


    private void btnStartProcess_Click(object sender, EventArgs e)

    {

        btnStartProcess.Enabled = false;

        StartCmdProcess();

        btnEndProcess.Enabled = true;

    }


    private void btnEndProcess_Click(object sender, EventArgs e)

    {

        if (stdin.BaseStream.CanWrite) {

            stdin.WriteLine("exit");

        }

        btnEndProcess.Enabled = false;

        btnStartProcess.Enabled = true;

        cmdProcess?.Close();

    }


    private void rtbStdIn_KeyPress(object sender, KeyPressEventArgs e)

    {

        if (e.KeyChar == (char)Keys.Enter) {

            if (stdin == null) {

                rtbStdErr.AppendText("Process not started" + Environment.NewLine);

                return;

            }


            e.Handled = true;

            if (stdin.BaseStream.CanWrite) {

                stdin.Write(rtbStdIn.Text + Environment.NewLine);

                stdin.WriteLine();

                // To write to a Console app, just 

                // stdin.WriteLine(rtbStdIn.Text); 

            }

            rtbStdIn.Clear();

        }

    }


    private void StartCmdProcess()

    {

        var pStartInfo = new ProcessStartInfo {

             FileName = "cmd.exe",

            // Batch File Arguments = "/C START /b /WAIT somebatch.bat",

            // Test: Arguments = "START /WAIT /K ipconfig /all",

            Arguments = "START /WAIT",

            WorkingDirectory = Environment.SystemDirectory,

            // WorkingDirectory = Application.StartupPath,

            RedirectStandardOutput = true,

            RedirectStandardError = true,

            RedirectStandardInput = true,

            UseShellExecute = false,

            CreateNoWindow = true,

            WindowStyle = ProcessWindowStyle.Hidden,

        };


        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;

        // stdin.AutoFlush = true;  <- already true


        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) => {

            stdin?.Dispose();

            cmdProcess?.Dispose();

        };

    }

}

由于 StandardInput 已被重定向到 StreamWriter:


stdin = cmdProcess.StandardInput;

我们只需写入 Stream 以执行命令:


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

示例表单可以从 PasteBin 下载。

//img1.sycdn.imooc.com//6190d166000114ce00010020.jpg

查看完整回答
反对 回复 2021-11-14
  • 1 回答
  • 0 关注
  • 244 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信