I have a console application, that launches a form application (from another class) in a different thread.
But then, I wan't to access the richTextBox1 component from my main class, in the main thread and that throws an error wich says that I'm trying to access the component from another thread.
My code:
(Form application)
public partial class ChatGui : Form
{
public static RichTextBox textBox;
public ChatGui()
{
InitializeComponent();
richTextBox1.ReadOnly = true;
richTextBox1.BackColor = SystemColors.Window;
}
public void WriteLine(string line)
{
richTextBox1.Text += line+"\r\n";
}
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
WriteLine("[You]: "+textBox1.Text);
NetworkManager.SendPacket("rchat_msg " + textBox1.Text.Replace(" ", "%20"));
textBox1.Text = "";
e.Handled = true;
}
}
public void Exit()
{
Application.Exit();
}
private void ChatGui_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
}
}
The main console application
public static void StartRemoteChat()
{
RemoteChat = true;
Program.ChatGui = new ChatGui();
new Thread(new ThreadStart(() =>
{
Application.Run(Program.ChatGui);
while (RemoteChat)
{
// ...
}
})).Start();
}
So, how can I access the richTextBox1 component from my main thread (I want to change some variables of the component) without this error happening ?
The control is owned by the thread that creates it.
In your case the thread that you start owns the form because it paints it so its going to be your forms ui thread.
However when you try to use another thread to make changes to the control, it will throw an exception just like you said.
The way around this is to invoke the thread that created it to come and make the change that you want like this:
richTextBox1.BeginInvoke(new Action(()=>
{
// make changes to control here.
}));
one that can help you is the "CheckForIllegalCrossThreadCalls = false;" but used here(at the beginning):
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
}
the advice is to use each call to "RichTextBox1" the invoke method.
this method allows to interact with elements created by other threads.
example:
richTextBox1.Invoke(new Action(() => richTextBox1.Text = "hello word"));
"CheckForIllegalCrossThreadCalls = false" is a solution that has enough dangers if it is used in complex programs.
I hope I was helpful.
Simple call invoke to invoke the method from a new thread:
if (InvokeRequired)
{
Invoke(new Action(**METHODHERE**));
return;
}
and to pass parameters:
if (InvokeRequired)
{
Invoke(new Action<string>(**METHODHERE**) **parameter**);
return;
}
Good read: https://msdn.microsoft.com/en-us/library/ms171728(v=vs.110).aspx
try setting following property.
RichTextBox.CheckForIllegalCrossThreadCalls=false;
this may help you.
Related
So I'm making a C# app which has to continuously read and display the contents of a text file, while allowing the user to enter something into a text box and append it to the end of that very file.
I'm doing this by running my read method on a separate thread, however changing the variable which stores the display text-files contents is what's causing a problem. Initially I tried having a method which did this, however that's not working and gave a 'cross-thread-operation-not-valid' error. I then tried applying some code I found on MSDN, but now after updating the variable once the thread ended!
Please help.
partial class MainForm
{
delegate void SetTextCallback(string text);
public static string msg;
public static string name;
public void InitClient()
{
name = "public.txt";
Console.WriteLine(name);
if(!File.Exists(name))
{
File.Create(name);
File.AppendAllText(name, "Welcome to " + name);
}
Thread Read = new Thread(new ThreadStart(this.Client));
Read.Start();
while(!Read.IsAlive);
}
public void WriteText()
{
File.AppendAllText(name, this.InputBox.Text);
this.InputBox.Clear();
}
private void SetText(string text)
{
if (this.OutPut.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.OutPut.Text = text;
}
}
public void Client()
{
msg = File.ReadAllText(name);
Console.WriteLine(msg);
Thread.Sleep(300);
this.SetText(msg);
}
}
Why is the thread behaving like this. How can I modify my code so that the contents of the output box always equals that of the text file.
Any suggestions welcome.
You've got multiple problems here,
the use of the File is probably not thread-safe.
your method does not repeat
your are Sleep()ing on a Thread
You can solve all of them by ditching the Thread and use a simple Timer.
Try using a background worker instead of creating a new thread. The background worker will run its content in a seperate thread, and allows you to report 'progress' while its working. This progress report will always be run on the UI-thread (or the thread which started the background worker).
It also has an event which is called when the background worker is finished. This is also run on the UI thread.
This example should get you started.
Update: Added some very basic error handling as suggested
The idea is to use the UserData (2nd argument) of ReportProgress to do updates on the UI thread whenever you need to. In this case it is a string, but this can be any object.
Furthermore, you can use the Result of the DoWorkEventArgs to produce a final result from the background work. In this case, I return any exception which was thrown, or null otherwise, but you can return whatever you want here as well.
It is, as Henk mentioned in his comment, very important to handle errors that occur inside the DoWork callback, because exceptions etc which occurs here will be swallowed and the worker will complete as if nothing bad happened.
private BackgroundWorker _backgroundWorker;
public Form1()
{
InitializeComponent();
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.WorkerSupportsCancellation = true;
// This is the background thread
_backgroundWorker.DoWork += BackgroundWorkerOnDoWork;
// Called when you report progress
_backgroundWorker.ProgressChanged += BackgroundWorkerOnProgressChanged;
// Called when the worker is done
_backgroundWorker.RunWorkerCompleted += BackgroundWorkerOnRunWorkerCompleted;
}
private void BackgroundWorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
if (runWorkerCompletedEventArgs.Result != null)
{
// Handle error or throw it
throw runWorkerCompletedEventArgs.Result as Exception;
}
textBox1.Text = "Worker completed";
}
private void BackgroundWorkerOnProgressChanged(object sender, ProgressChangedEventArgs progressChangedEventArgs)
{
textBox1.Text = progressChangedEventArgs.UserState as string;
}
private void BackgroundWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
try
{
for (int i = 0; i < 100 && !_backgroundWorker.CancellationPending; i++)
{
_backgroundWorker.ReportProgress(0, i + " cycles");
Thread.Sleep(100);
}
}
catch (Exception ex)
{
doWorkEventArgs.Result = ex;
}
}
private void startButton_Click(object sender, EventArgs e)
{
if (!_backgroundWorker.IsBusy)
_backgroundWorker.RunWorkerAsync();
}
private void cancelButton_Click(object sender, EventArgs e)
{
if(_backgroundWorker.IsBusy)
_backgroundWorker.CancelAsync();
}
Below is the method to start a thread in compact framework 3.5
public ScanEntry(string scanId)
{
InitializeComponent();
_scanId = scanId;
//reader = deviceFactory.Create();
//reader.YMEvent += new ScanEventHandler(reader_Reading);
//reader.Enable();
}
private void CasesEntry_Load(object sender, EventArgs e)
{
caseCounterLabel.Text = cases.Count.ToString();
scanIdValueLabel.Text = _scanId;
}
internal void menuItemNewScan_Click(object sender, EventArgs e)
{
System.Threading.ThreadStart threadDelegate = new System.Threading.ThreadStart(ScanEvents);
System.Threading.Thread newThread = new System.Threading.Thread(threadDelegate);
newThread.Start();
}
which calls the below method on thread
private void ScanEvents()
{
try
{
//some other codes
if (scanIdValueLabel.InvokeRequired)
{
scanIdValueLabel.Invoke((Action)(() => scanIdValueLabel.Text = "value"));
}
attributeNode = docEventFile.CreateNode(XmlNodeType.Element, "Attribute", string.Empty);
XMLUtils.CreateAttribute(docEventFile, attributeNode, "name", "SCANID");
XMLUtils.CreateAttribute(docEventFile, attributeNode, "value", scanIdValueLabel.Text);
attributeSetNode.AppendChild(attributeNode);
//some other codes
}
catch(Execption e)
{
Message.Show(e.Message);
}
}
Errors:
TextAlign = 'scanIdValueLabel.TextAlign' threw an exception of type 'System.NotSupportedException'
base {System.SystemException} = {"Control.Invoke must be used to interact with controls created on a separate thread."}
In Line
XMLUtils.CreateAttribute(docEventFile, attributeNode, "value", scanIdValueLabel.Text);
I am getting Control.Invoke must be used to interact with controls created on a separate thread at this line
XMLUtils.CreateAttribute(docEventFile, attributeNode, "value", scanIdValueLabel.Text);
I have googled and tried with that solutions but not worked for me.Can any one help me in doing this.
Thanks
When you're dealing with Winforms, WPF, Silverlight there's the following sentence which is very important:
The UI elements can only be accessed by the UI thread. WinForms, WPF, Silverlight doesn't allow access to controls from multiple threads.
However, there is a solution which can be found here:
Update: I've created a sample application to make some things clear:
I've created a form first with a button and a label on it. The label is not visible because it doesn't contain text, but it's right underneath the button.
Scenario 1: Updating without threads:
private void btnStartThread_Click(object sender, EventArgs e)
{
lblMessage.Text = "Button has been clicked.";
}
Off course this is not a problem. It's some standard code:
Scenario 2: Updating with threads:
private void btnStartThread_Click(object sender, EventArgs e)
{
System.Threading.ThreadStart threadDelegate = new System.Threading.ThreadStart(ScanEvents);
System.Threading.Thread newThread = new System.Threading.Thread(threadDelegate);
newThread.Start();
}
private void ScanEvents()
{
lblMessage.Text = "Exected in another thread.";
}
This will fail because I'm MODIFYING the controls on my form from another thread:
Now, I will modify the code so that I'm changing the label with an action through an invoke on the label.
private void ScanEvents()
{
if (lblMessage.InvokeRequired)
{
lblMessage.Invoke((Action)(() => lblMessage.Text = "This text was placed from within a thread."));
}
}
This will make the text change.
So, I hope that it helps. If not, please shout :-)
I have a Main form, which is running a synchronous operation(thus freezing the form).
Before that starts to happen I call my function showWaitWindow().
private void showWaitWindow()
{
Wait x = new Wait();
x.Show(this); //"this" is allowing the form to later centralize itself to the parent
}
This is where it is exactly happening:
if (result)
{
System.Threading.Thread t = new System.Threading.Thread(new
System.Threading.ThreadStart(showWaitWindow));
t.Start();
}
else
{
return;
}
propertyGrid1.SelectedObject = z.bg_getAllPlugins(); //Heavy synchronous call
//This should be closing the form, which is not happening.
for (int index = Application.OpenForms.Count; index >= 0; index--)
{
if (Application.OpenForms[index].Name == "Wait")
{
MessageBox.Show("found");
Application.OpenForms[index].Close();
}
}
I've tried this without threading as well, which didn't work as well. Also, because it's trying to centralize to the parent, while being created in another thread, it throws an exception "tried to access in different thread that it was created in" rephrasing.
How do I approach that?
I would suggest using a BackgroundWorker -- available in the WinForms toolbox.
public partial class Form1 : Form
{
private BackgroundWorker backgroundWorker1;
public Form1()
{
InitializeComponent();
this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//perform lengthy operation in here.
}
}
I have an windows application developed using C#. In this application, I am creating one process. I want to enable and disable few buttons when Process_Exited() event occures.
In Process_Exited() method, I have written code to enable buttons but at runtime I get error as
"Cross-thread operation not valid:
Control
'tabPage_buttonStartExtraction'
accessed from a thread other than the
thread it was created on."
My code snippet is :
void rinxProcess_Exited(object sender, EventArgs e)
{
tabPage_buttonStartExtraction.Enabled = true;
tabPageExtraction_StopExtractionBtn.Enabled = false;
}
Can anyone suggest how to make this possible?
Move the enable/disable lines in a separate method and call that method from rinxProcess_Exited using Control.Invoke method.
You're attempting to change the UI from a different thread.
Try something like this;
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
You shouldn't be doing much work on the UI from another thread, as the invocations are quite expensive.
Source: http://msdn.microsoft.com/en-us/library/ms171728.aspx
You must make UI changes on the UI thread. See this question for more details.
Here's the solution applied to your example:
void rinxProcess_Exited(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke((Action)(() => ProcessExited()));
return;
}
ProcessExited();
}
private void ProcessExited()
{
tabPage_buttonStartExtraction.Enabled = true;
tabPageExtraction_StopExtractionBtn.Enabled = false;
}
private void launchbutton_Click(object sender, EventArgs e)
{
launchbutton.Enabled = false;
Process proc = new Process();
proc.EnableRaisingEvents = true;
proc.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
//The arguments/filename is set here, just removed for privacy.
proc.Exited += new EventHandler(procExit);
proc.Start();
}
private void procExit(object sender, EventArgs e)
{
MessageBox.Show("YAY","WOOT");
Thread.Sleep(2000);
launchbutton.Enabled = true;
}
2 Seconds after I quit the created process, my program crashes. Why?
You're modifying a winform control on a different thread than the one that created that control (the main UI thread). Winform controls are not thread-safe and typically will throw an exception if you modify their state from any thread other than the one that created it.
You can accomplish this using the InvokeRequired property and BeginInvoke method found on the Form or control object.
For example, something like this:
private void procExit(object sender, EventArgs e)
{
MessageBox.Show("YAY", "WOOT");
Thread.Sleep(2000);
// ProcessStatus is just a class I made up to demonstrate passing data back to the UI
processComplete(new ProcessStatus { Success = true });
}
private void processComplete(ProcessStatus status)
{
if (this.InvokeRequired)
{
// We are in the wrong thread! We need to use BeginInvoke in order to execute on the correct thread.
// create a delegate pointing back to this same function, passing in the same data
this.BeginInvoke(new Action<ProcessStatus>(this.processComplete), status);
}
else
{
// check status info
if (status.Success)
{
// handle success, if applicable
}
else
{
// handle failure, if applicable
}
// this line of code is now safe to execute, because the BeginInvoke method ensured that the correct thread was used to execute this code.
launchbutton.Enabled = true;
}
}