I wrote simple application where C# (winforms) call Python ML script (so I cannot use IronPython etc.) All worked fine and I used "invoke" correctly (I had only one process and not used class):
public static void appendTextData(string text)
{
if (richTextBox1.InvokeRequired)
{
richTextBox1.Invoke(new AppendTextDelegate(appendTextData), new object[] { text });
}
else
{
richTextBox1.AppendText(text);
}
}
But when I move process creating in class (InnerPerson) then I get error on richTextBox1: "CS0120: An object reference is required for the nonstatic field, method, or property":
public static void appendTextData(string text)
{
ERROR -> if (richTextBox1.InvokeRequired)
{
ERROR -> richTextBox1.Invoke(new AppendTextDelegate(appendTextData), new object[] { text });
}
else
{
ERROR -> richTextBox1.AppendText(text);
}
}
For temporary solution I simple moved richBox declaration from Designer to Aplication and all works fine again. But am I right? Is it possible work with richBox defined in Designer?
This is my working code:
public partial class Form1 : Form
{
delegate void AppendTextDelegate(string text);
public static string err = "";
public static InnerPerson[] innerPerson = new InnerPerson[16];
public static System.Windows.Forms.RichTextBox richTextBox1;
public Form1()
{
richTextBox1 = new System.Windows.Forms.RichTextBox();
richTextBox1.Location = new System.Drawing.Point(25, 12);
richTextBox1.Name = "richTextBox1";
richTextBox1.Size = new System.Drawing.Size(400, 350);
Controls.Add(richTextBox1);
InitializeComponent();
}
void Form1_Shown(object sender, EventArgs e)
{
Start16();
}
void Start16()
{
innerPerson[1] = new InnerPerson();
innerPerson[2] = new InnerPerson();
innerPerson[1].Init("Uncle_Bob", 1);
innerPerson[2].Init("Aunt_Julia", 2);
}
public static void OnDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
appendTextData(e.Data "\n");
}
}
public static void appendTextData(string text)
{
if (richTextBox1.InvokeRequired)
{
richTextBox1.Invoke(new AppendTextDelegate(appendTextData), new object[] { text });
}
else
{
richTextBox1.AppendText(text);
}
}
void textBox1_KeyDown(object sender, KeyEventArgs e)
{
string txt = textBox1.Text;
if (e.KeyCode == Keys.Enter)
{
richTextBox1.Text = "Me: " txt "";
richTextBox1.Text = innerPerson[1].Dialog(txt, 1) "";
richTextBox1.Text = innerPerson[2].Dialog(txt, 2) "";
richTextBox1.Text = "\n";
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.ScrollToCaret();
textBox1.Text = "";
}
}
}
public class InnerPerson
{
public string Name = "";
// Constructor
public InnerPerson()
{
Name = "NoName";
}
public void SetName(string n) { Name = n; }
public StreamWriter sw = null;
public bool Init(string n, int i)
{
bool successRunPythonFlag = false;
SetName(n);
string executable = @"C:\Users\user\AppData\Local\Programs\Python\Python37\Python.exe";
string script = @"..\BOTS\chatbot.py " i;
ProcessStartInfo startInfo = new ProcessStartInfo(executable, script);
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardError = true;
Process p = new Process();
try
{
p.StartInfo = startInfo;
p.EnableRaisingEvents = true;
p.OutputDataReceived = new DataReceivedEventHandler(Form1.OnDataReceived);
// p.ErrorDataReceived = new DataReceivedEventHandler(Form1.OnErrorReceived);
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
sw = p.StandardInput;
successRunPythonFlag = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
successRunPythonFlag = false;
}
finally
{
p.Close();
}
return successRunPythonFlag;
}
public string Dialog(string d, int i)
{
sw.WriteLine(Name " " d);
return "";
}
}
So my problem is why I get errors on richbox when I define it in Constructor\Designer.
CodePudding user response:
Add a SynchronizationContext
to your Form
private static SynchronizationContext Context;
And initialize in the constructor:
Context = SynchronizationContext.Current;
This allow you run code in your gui thread easily:
Context.Post(state => { /* this code run in your gui thread */ }, null);
So, in that part you can set values in any control of the form, not only in the RichTextBox
. You can also redirect the event to the gui thread at first and forget this problem.
I did another change in your code, creating a Message event in the InnerPerson
class.
public event EventHandler<MessageEventArgs> Message;
With this EventArgs
:
public class MessageEventArgs : EventArgs
{
public MessageEventArgs(string message)
{
this.Message = message;
}
public string Message { get; }
}
Now, your InnerPerson
has an own message/event.
Check the full class:
public class InnerPerson
{
private StreamWriter _writer = null;
public InnerPerson()
: this("NoName")
{
}
public InnerPerson(string name)
{
this.Name = name;
}
public string Name { get; set; }
public event EventHandler<MessageEventArgs> Message;
public bool Execute(int index)
{
bool successRunPythonFlag = false;
string executable = @"C:\Users\user\AppData\Local\Programs\Python\Python37\Python.exe";
string script = @"..\BOTS\chatbot.py " index;
var startInfo = new ProcessStartInfo(executable, script)
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
RedirectStandardError = true
};
Process p = new Process();
try
{
p.StartInfo = startInfo;
p.EnableRaisingEvents = true;
p.OutputDataReceived = this.OnDataReceived;
// p.ErrorDataReceived = new DataReceivedEventHandler(Form1.OnErrorReceived);
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
_writer = p.StandardInput;
successRunPythonFlag = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
successRunPythonFlag = false;
}
finally
{
p.Close();
}
return successRunPythonFlag;
}
protected virtual void OnDataReceived(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Data) && this.Message != null)
{
var e2 = new MessageEventArgs(e.Data "\n");
this.Message(this, e2);
}
}
public string Dialog(string d, int i)
{
this._writer.WriteLine(this.Name " " d);
return string.Empty;
}
}
And now, in the form, you need to do some adjustments:
public partial class Form1 : Form
{
private static SynchronizationContext Context;
public Form1()
{
this.InitializeComponent();
Context = SynchronizationContext.Current;
}
public static string err = "";
public static InnerPerson[] innerPerson = new InnerPerson[16];
void Form1_Shown(object sender, EventArgs e)
{
Start16();
}
void Start16()
{
string[] names = new[] { "Uncle_Bob", "Aunt_Julia" };
for (int i = 0; i < names.Length; i )
{
var person = innerPerson[i] = new InnerPerson(names[i]);
person.Message = (sender, e) =>
Context.Post(state => this.OnPerson_Message(sender, e), null);
person.Execute(i 1);
}
}
private void OnPerson_Message(object sender, MessageEventArgs e)
{
richTextBox1.AppendText(e.Message);
}
void textBox1_KeyDown(object sender, KeyEventArgs e)
{
string txt = textBox1.Text;
if (e.KeyCode == Keys.Enter)
{
richTextBox1.Text = "Me: " txt "";
richTextBox1.Text = innerPerson[1].Dialog(txt, 1) "";
richTextBox1.Text = innerPerson[2].Dialog(txt, 2) "";
richTextBox1.Text = "\n";
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.ScrollToCaret();
textBox1.Text = string.Empty;
}
}
}
As you see, we use the Message
event of InnerPerson
to get the messages and Context
to redirect these messages to GUI thread: you don't need to do nothing about BeginInvoke
in OnPerson_Message
, just work with your controls.