Run a Python script from a C # GUI application

Introduction

Python has many great libraries, including machine learning. On the other hand, C # is a language widely used for developing GUI applications. Therefore, it would be convenient for C # application developers to be able to call Python scripts from C # applications, and above all, the range of GUI applications should be expanded. So, this time, I investigated how to call a Python script from a C # GUI application and created a prototype.

environment

Prototype you want to develop

I have identified the requirements of the prototype I want to develop below.

--You can specify and execute Python paths, Working Directory, and Python scripts. ――In consideration of the case where it takes a long time to execute, you can cancel the process in the middle. --Display all standard output and standard error output in the GUI TextBox so that you can see the progress if it takes a long time to execute. --For a Python script that accepts standard input, such as Convert MOL file to SMILES via standard I / O, the file By passing the data on the GUI side to the Python script without going through it, you can easily receive the execution result. Therefore, I want to be able to specify standard input as well. --The exit code of the Python script determines whether it ended normally or with an error, and it is displayed by MessageBox.

What was made

screen

Like this. image.png

Screen description

--For Python Path, specify the location of python.exe with the full path. For Anaconda, check the location of python.exe in Anaconda's virtual environment and specify it. --Specify the execution directory of the Python script in Working Directory. When a file path is specified as an argument, it can be described as a relative path starting from here. --In Python Command, specify the location of the Python script with the full path. If there is an argument, specify it as well. --In Standard Input, enter the data you want to pass to the standard input of the Python script. Ignored for Python scripts that do not use standard input. --Standard Output and Standard Error displays all standard output and standard error output of Python scripts. --You can start the process with the "Execute" button and cancel the process with the "Cancel" button.

Source

The source is as follows. It's been very long, but it's awkward to edit, so I just paste it (cut out). The code on the design side is omitted. Please read the variable name of the object of the GUI part from the code. The source will be explained in the next section.

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

namespace PythonCommandExecutor
{

    public partial class Form1 : Form
    {
        private Process currentProcess;
        private StringBuilder outStringBuilder = new StringBuilder();
        private int readCount = 0;
        private Boolean isCanceled = false;

        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        ///Add string to Textbox
        /// </summary>
        public void AppendText(String data, Boolean console)
        {
            textBox1.AppendText(data);
            if (console)
            {
                textBox1.AppendText("\r\n");
                Console.WriteLine(data);
            }
        }

        /// <summary>
        ///Behavior when the execute button is clicked
        /// </summary>
        private void button1_Click(object sender, EventArgs e)
        {
            //Preprocessing
            button1.Enabled = false;
            button2.Enabled = true;
            isCanceled = false;
            readCount = 0;
            outStringBuilder.Clear();
            this.Invoke((MethodInvoker)(() => this.textBox1.Clear()));

            //Run
            RunCommandLineAsync();
        }

        /// <summary>
        ///Command execution process body
        /// </summary>
        public void RunCommandLineAsync()
        {

            ProcessStartInfo psInfo = new ProcessStartInfo();
            psInfo.FileName = this.textBox2.Text.Trim();
            psInfo.WorkingDirectory = this.textBox3.Text.Trim();
            psInfo.Arguments = this.textBox4.Text.Trim();

            psInfo.CreateNoWindow = true;
            psInfo.UseShellExecute = false;
            psInfo.RedirectStandardInput = true;
            psInfo.RedirectStandardOutput = true;
            psInfo.RedirectStandardError = true;

            // Process p = Process.Start(psInfo);
            Process p = new System.Diagnostics.Process();
            p.StartInfo = psInfo;

            p.EnableRaisingEvents = true;
            p.Exited += onExited;
            p.OutputDataReceived += p_OutputDataReceived;
            p.ErrorDataReceived += p_ErrorDataReceived;

            p.Start();

            //Write to standard input
            using (StreamWriter sw = p.StandardInput)
            {
                sw.Write(this.textBox5.Text.Trim()); 
            }

            //Start reading output and error asynchronously
            p.BeginOutputReadLine();
            p.BeginErrorReadLine();

            currentProcess = p;
        }

        void onExited(object sender, EventArgs e)
        {
            int exitCode;

            if (currentProcess != null)
            {
                currentProcess.WaitForExit();

                //Exhaling data that remains without being exhaled
                this.Invoke((MethodInvoker)(() => AppendText(outStringBuilder.ToString(), false)));
                outStringBuilder.Clear();

                exitCode = currentProcess.ExitCode;
                currentProcess.CancelOutputRead();
                currentProcess.CancelErrorRead();
                currentProcess.Close();
                currentProcess.Dispose();
                currentProcess = null;

                this.Invoke((MethodInvoker)(() => this.button1.Enabled = true));
                this.Invoke((MethodInvoker)(() => this.button2.Enabled=false));


                if (isCanceled)
                {
                    //Completion message
                    this.Invoke((MethodInvoker)(() => MessageBox.Show("Canceled processing")));
                }
                else
                {
                    if (exitCode == 0)
                    {
                        //Completion message
                        this.Invoke((MethodInvoker)(() => MessageBox.Show("Processing is complete")));
                    }
                    else
                    {
                        //Completion message
                        this.Invoke((MethodInvoker)(() => MessageBox.Show("An error has occurred")));
                    }
                }
            }
        }

        /// <summary>
        ///Processing when standard output data is received
        /// </summary>
        void p_OutputDataReceived(object sender,
            System.Diagnostics.DataReceivedEventArgs e)
        {
            processMessage(sender, e);
        }

        /// <summary>
        ///What to do when a standard error is received
        /// </summary>
        void p_ErrorDataReceived(object sender,
            System.Diagnostics.DataReceivedEventArgs e)
        {
            processMessage(sender, e);
        }

        /// <summary>
        ///Receives CommandLine program data and spits it into a TextBox
        /// </summary>
        void processMessage(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            if (e != null && e.Data != null && e.Data.Length > 0)
            {
                outStringBuilder.Append(e.Data + "\r\n");
            }
            readCount++;
            //Exhale at a cohesive timing
            if (readCount % 5 == 0)
            {
                this.Invoke((MethodInvoker)(() => AppendText(outStringBuilder.ToString(), false)));
                outStringBuilder.Clear();
                //Put sleep to not occupy threads
                if (readCount % 1000 == 0)
                {
                    Thread.Sleep(100);
                }
            }
        }

        /// <summary>
        ///Behavior when the cancel button is clicked
        /// </summary>
        private void button2_Click(object sender, EventArgs e)
        {
            if (currentProcess != null)
            {
                try
                {
                    currentProcess.Kill();
                    isCanceled = true;
                }
                catch (Exception e2)
                {
                    Console.WriteLine(e2);
                }
            }
        }

        private void button3_Click(object sender, EventArgs e)
        {
            //Clear standard input area
            this.textBox5.Clear();
            //Clear standard output area
            this.textBox1.Clear();
        }
    }
}

Source commentary

It's basically a collection of references, but the explanation is given below.

--The Python script is being executed by the Process class in the RunCommandLineAsync method. Since the processing becomes asynchronous after `p.Start ()`, if you operate the UI after that, you will get angry if you do not execute it from the UI thread. `this.Invoke ((MethodInvoker) (() => AppendText (outStringBuilder.ToString (), false))); This is why there are some calls like. --``` p.EnableRaisingEvents = true; , p.Exited + = onExited; executes the onExit event handler at the end of the process, so cleanup processing and cleanup processing here It describes the judgment of the end code, the display of the completion dialog, etc. --For cancellation, the Kill method of Process class is called in the event handler executed at the time of cancellation. Then the onExit event handler is executed, which is the same as when it normally ends, so nothing else is done. --The place where the standard input feeds the data is done from the beginning of ```using (StreamWriter sw = p.StandardInput) . --For standard output and standard error output, `` p.OutputDataReceived + = p_OutputDataReceived; , ErrorDataReceived + = p_ErrorDataReceived;` `` to receive standard output and standard error in each event handler. I am trying to process the output.

p.BeginOutputReadLine();,p.BeginErrorReadLine();Since each event handler is executed every time there is one line output, the output to TextBox is performed in it. If you write each line to a TextBox, it may take a long time to process the GUI in the case of an application that has a large amount of output, so we are trying to output it all together to some extent.




# Execution example (1) Execute a Python script with a lot of output (an error in the middle)
 The following is an example of executing a Python script created as a sample, which has a certain amount of standard output and standard error output. It can be seen that the error message output to the standard error is also output to the TextBox, and the error MessageBox is displayed by the exit code.

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/f2c9e895-314f-f7e9-795a-2588ca113c6d.png)


# Execution example (2) Execute Python script via standard input
 The following is a diagram of executing the script of "[Convert MOL file to SMILES via standard input / output](https://qiita.com/kimisyo/items/c7eb3a6a10298590438e)" through this GUI. You will realize that you can pass the processing results of C # and Python just by writing a simple Python script.
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/f46c8aee-2950-836c-7a00-5d10fad6021d.png)


# in conclusion
 --I was initially proceeding with the method using async / await, but I gave up because a phenomenon like a deadlock in the UI occurred and I could not solve it even after a whole day.
 --By outputting progress information to the standard output of Python script, I think you can easily display the progress with the progress bar on the C # side.
 ――Since the operation is fairly stable, I would like to make an attractive application by using the convenient functions of Python from C # based on this prototype.

# 2020/2/24 revised

 Fixed as follows because there was a fatal bug that started the process twice. We apologize for the inconvenience.
        // Process p = Process.Start(psInfo);
        Process p = new System.Diagnostics.Process();
        p.StartInfo = psInfo;

# References
 -[Process class](https://docs.microsoft.com/ja-jp/dotnet/api/system.diagnostics.process?view=netframework-4.8)
 -[Notes on receiving standard output by starting asynchronous external process](https://qiita.com/skitoy4321/items/10c47eea93e5c6145d48)
 --[Start an external application and wait for it to finish](https://dobon.net/vb/dotnet/process/openfile.html)
 -[What if the process freezes when the external program is executed? ](Https://www.atmarkit.co.jp/fdotnet/dotnettips/805pipeasync/pipeasync.html)



Recommended Posts

Run a Python script from a C # GUI application
Call a Python script from Embedded Python in C ++ / C ++
Run a python script from excel (using xlwings)
Run illustrator script from python
Execute Python code from C # GUI
Run Python scripts synchronously from C #
How to run a Python program from within a shell script
Run the Python interpreter in a script
Use Django from a local Python script
Run a Python web application with Docker
How to run a Maya Python script
I made a GUI application with Python + PyQt5
Create a C array from a Python> Excel sheet
Create a New Todoist Task from Python Script
Run python from excel
"Python Kit" that calls a Python script from Swift
Run a Python file from html using Django
A memorandum to run a python script in a bat file
Python points from the perspective of a C programmer
Python script to create a JSON file from a CSV file
[Python] How to call a c function from python (ctypes)
Run a multi-line script in a PDB
Execute Python script from batch file
Call a Python function from p5.js.
Solve ABC163 A ~ C with Python
Call C from Python with DragonFFI
Touch a Python object from Elixir
ABC127 A, B, C Explanation (python)
Create a python GUI using tkinter
Let's make a GUI with python.
ABC166 in Python A ~ C problem
Call popcount from Ruby / Python / C #
python / Make a dict from a list.
Solve ABC168 A ~ C with Python
Run Aprili from Python with Orange
Solve ABC036 A ~ C in Python
Tips for calling Python from C
Python error detection run from Powershell
Solve ABC162 A ~ C with Python
Solve ABC167 A ~ C with Python
ABC128 A, B, C commentary (python)
Solve ABC158 A ~ C with Python
ABC126 A, B, C Explanation (python)
Solve ABC037 A ~ C in Python
Run Ansible from Python using API
Launch a Python script as a service
Run a simple algorithm in Python
Write a batch script with Python3.5 ~
Call C / C ++ from Python on Mac
Run Python Scripts from Cisco Memorandum_EEM
Call c language from python (python.h)
Create a shell script to run the python file multiple times
Call dlm from python to run a time-varying coefficient regression model
Pass a list by reference from Python to C ++ with pybind11
[Python] Web application from 0! Hands-on (3) -API implementation-
Run Cloud Dataflow (Python) from App Engine
Solve ABC175 A, B, C in Python
Execute a script from Jupyter to process
Run a local script on a remote host
Create a simple GUI app in Python
Send a message from Python to Slack