How to change the text of Label from another Form? - c#

It is a cross threaded operation in windows application done in c#, How can i change it ?

You can write a method which you can call from any thread:
private void SetLabel(string newText)
{
Invoke(new Action(() => SomeLabel.Text = NewText));
}
Then you can just call SetLabel("Update the label, please") from any thread.
However, your question title states “from another Form” rather than “from another thread”, so it is unclear what you actually mean. You don’t need multithreading if you just want to have multiple forms. You should use threads only for tasks, e.g. downloading a file, copying a file, calculating a value, etc., but not for Forms.

You need to use a delegate and invoke...
private delegate void SetLabelSub(string NewText);
private void SetLabel(string NewText)
{
if (this.InvokeRequired()) {
SetLabelSub Del = new SetLabelSub(SetLabel);
this.Invoke(Del, new object[] { NewText });
} else {
SomeLabel.Text = NewText;
}
}
Then you can just call SetLabel("New Text Here") from any thread

How about writing a more general method to change the Text property of any control in your form like:
private void SetText(Control control, string text)
{
if (control.InvokeRequired)
this.Invoke(new Action<Control>((c) => c.Text = text),control);
else
control.Text = newText;
}
This will work for labels, buttons, etc, from either the UI thread or any other thread.

If you're dealing with threads you need to use the form.Invoke() method, assuming you're passing the form instance into the other form.roughly
Form form1 = new Form()
Form form2 = new Form();
form2.CallingForm = form1; // make this property or what ever
inside form2 add some code like
form1.Invoke(someDelagate, value);
I don't do winforms that often but if you google form.Invoke you'll get some good examples of how to do cross thread operations.

Related

How to invoke components

In my apps i use backgroundWorker, to set text in some TextBox, I need first to invoke that TextBox.
First I use:
if (someTextBox.InvokeRequired)
{
someTextBox.Invoke((MethodInvoker)delegate
{
someTextBox.Text = "some_text";
});
}
else
{
someTextBox.Text = "some_text";
}
This method work for me fine, but because i have multiple TextBox-es i wrote:
private void invComp(TextBox txtBox, String str)
{
if (txtBox.InvokeRequired)
{
txtBox.Invoke((MethodInvoker)delegate
{
txtBox.Text = str;
});
}
else
{
txtBox.Text = str;
}
}
It is better to invoke it on this way? (invComp(someTextBox, "some_text");
Or maybe i have some third, bether , way?
I invoke some buttons to, I was think to write something like this for button to, if this is ok?
Tnx
Control.InvokeRequired suffers from cargo cult. You are updating a control from a worker thread, you know that invoking is required. So there is absolutely no point in testing it. Except for one reason, there is something fundamentally wrong when it is false. Which happens a lot more often than programmers like, forgetting to stop a worker when the user closes the window is a traditional bug. This causes all kind of mayhem, you want to know about it:
private void invComp(TextBox txtBox, String str) {
if (!this.InvokeRequired) throw new InvalidOperationException("You forgot to stop the worker");
this.BeginInvoke(new Action(() => txtBox.Text = str));
}
Short and snappy and fail-safe and fast. Good qualities of code. Note that it uses the form's BeginInvoke() method, it doesn't depend on a child control being created. And that it uses BeginInvoke() instead of Invoke(), important to not bog down the worker thread and avoid deadlock. Always avoid Invoke(), it is only required when you need to know a method return value.
A completely different take is to focus on you using BackgroundWorker. It already marshals calls to the UI thread, it is just that the method has a clumsy name. You can get the ProgressChanged event to execute any code, it isn't just good enough to show progress. Write your event handler like this:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
((Action)e.UserState).Invoke();
}
Now you can make it execute any code on the UI thread:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
var worker = (BackgroundWorker)sender;
//...
worker.ReportProgress(0, new Action(() => textBox1.Text = "hello"));
}
You can slightly modify your method, to make it generic, so that you can use if for any control.
private void invComp<T>(T control, String str) where T: Control
{
if (control.InvokeRequired)
{
control.Invoke((MethodInvoker)delegate
{
control.Text = str;
});
}
else
{
control.Text = str;
}
}

Creating a MethodInvoker function

So I have multi threaded my application. I ran into this error "Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on."
My thread was calling a windows forms control. So to get around this I used
Control.Invoke(new MethodInvoker(delegate { ControlsAction; }));
I am trying to figure out a way that I could make this generic method so I can reuse code and make the app alot cleaner.
So for instance, on of my invokes I do the following with a rich text box.
rtbOutput.Invoke(new MethodInvoker(delegate {
rtbOutput.AppendText(fields[0].TrimStart().TrimEnd().ToString() + " Profile not
removed. Check Logs.\n"); }));
Another is with a combo box where I am simply setting the text.
cmbEmailProfile.Invoke(new MethodInvoker(delegate { EmailProfileNameToSetForUsers =
cmbEmailProfile.Text; }));
Another example is again with a rich text box where I am simply clearing it.
rtbOutput.Invoke(new MethodInvoker(delegate { rtbOutput.Clear(); }));
How would I create a generic function that could do this for me where I just need to pass in the control with the action i want it to do?
This is what we have come up so far.
private void methodInvoker(Control sender, Action act)
{
sender.Invoke(new MethodInvoker(act));
}
so the problem is something like appendtext, it doesn't seem to like.
Something like this should do the trick:
public static class FormsExt
{
public static void InvokeOnMainThread(this System.Windows.Forms.Control control, Action act)
{
control.Invoke(new MethodInvoker(act), null);
}
}
And then using it is as simple as:
var lbl = new System.Windows.Forms.Label();
lbl.InvokeOnMainThread(() =>
{
// Code to run on main thread here
});
With your original label:
rtbOutput.InvokeOnMainThread(() =>
{
// Code to run on main thread here
rtbOutput.AppendText(fields[0].TrimStart().TrimEnd().ToString() + " Profile not removed. Check Logs.\n"); }));
});

Managing the dialog form in C#

On click of a button I have this code with is should show a dialog on top of the current form and display text, wait for one second, change the text and then finally close it:
Form p = new Form();
p.ShowDialog();
p.Text = "Start.";
Thread.Sleep(1000);
p.Text = "Counting.";
Thread.Sleep(1000);
p.Text = "End.";
Thread.Sleep(1000);
p.Close();
However once it executes p.ShowDialog(); it stops the code until the form p is closed and it doesn't work as I intended it to. Can I get some guidance on this? Not necessarily the solution, but at least maybe some keywords I could google on?
UPDATE: due to the difficulties I am facing trying to access business logic, which is irrelevant to the problem, I am delaying providing the working example. Stay tuned and sorry :)
SOLUTION: what I did is in fact used Show() instead of ShowDialog(). Since i was impossible to access form from business logic, BackgroundWorker came in handy and was being used between them. I cannot share any code or the layout of the project structure, but in conclusion, the accepted answer's main statement was the key to the solution :)
That is the point of ShowDialog(). It creates a modal form and does not return control to the calling function until you are done. If it doesn't need to be modal, then use .Show(). If it does need to be modal, then put code in the Form Load method to update the text as needed.
http://msdn.microsoft.com/en-us/library/c7ykbedk.aspx
taken from the link above:
When this method is called, the code following it is not executed until after the dialog box is closed.
if you want to form to display whatever it is you want to display you should write the code inside the the form itself, do that in an eventhandler of the form show event.
As you have found, ShowDialog is a blocking method that does not return until the dialog is closed. Your code to change the text and handle the delay needs to be within the dialogue itself.
However, it's worth noting the next problem that you'll find: if you call Thread.Sleep(1000) from the UI thread, your application will become unresponsive for 1 second at a time. This is probably not what you're aiming for! I'd suggest you look into the Timer or BackgroundWorker classes to handle this more smoothly.
Check this out:
public partial class Form2 : Form
{
delegate void SetTextCallback(string text);
delegate void CloseFormCallback();
public Form2()
{
InitializeComponent();
new Thread(DoMagic).Start();
}
public void DoMagic()
{
this.SetText("Start.");
Thread.Sleep(1000);
this.SetText("Counting.");
Thread.Sleep(1000);
this.SetText("End");
Thread.Sleep(1000);
this.CloseForm();
}
private void CloseForm()
{
if (this.InvokeRequired)
{
CloseFormCallback c = new CloseFormCallback(CloseForm);
this.Invoke(c);
}
else
{
this.Close();
}
}
private void SetText(string text)
{
if (this.label1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.label1.Text = text;
}
}
}

Threading, let one thread know the others progress

Ok, well I have been at it for a while now and I decided to just use threads. I am making a syntax highlighter but I keep getting terrible performance with the file sizes that it will usually be used for. So I made two forms, the first shows the file in plain text and has a button that says "openincolor" when you click that I start a new thread as such
private void button1_Click(object sender, EventArgs e)
{
ColoringThread colorer = new ColoringThread(this.m_bruteView.Text);
Thread theThread = new Thread(new ThreadStart(colorer.OpenColorWindow));
theThread.Start();
}
public class ColoringThread
{
string text;
public ColoringThread(string initText)
{
text = initText;
}
public void OpenColorWindow()
{
Form2 form2 = new Form2(text);
form2.ShowDialog();
}
};
I want this form to send back a message each time it has complete say x lines of coloring. Then I will take that and figure out the progress and display it to the user.
How might I go about sending a message, or event(...? can I do that) to my first form to let it know of the others progress?
One very simple way to do this is with BackgroundWorker. It already provides an event to report progress.
How about something like this? This adds an event to the ColoringThread class which is subscribed to by the calling class.
private void button1_Click(object sender, EventArgs e) {
ColoringThread colorer = new ColoringThread(this.m_bruteView.Text);
colorer.HighlightProgressChanged += UpdateProgress;
Thread theThread = new Thread(new ThreadStart(colorer.OpenColorWindow));
theThread.Start();
}
private void UpdateProgress(int linesComplete) {
// update progress bar here
}
public class ColoringThread
{
string text;
public delegate void HighlightEventHandler(int linesComplete);
public event HighlightEventHandler HighlightProgressChanged;
public ColoringThread(string initText) {
text = initText;
}
public void OpenColorWindow() {
Form2 form2 = new Form2(text);
form2.ShowDialog();
int linesColored = 0;
foreach (String line in text.Split(Environment.NewLine)) {
// colorize line here
// raise event
if (HighlightProgressChanged != null)
HighlightProgressChanged(++linesColored);
}
}
};
You can pass an object as argument to the Thread.Start and share your data between the current thread and the initiating thread.
Here is a good example:
How to share data between different threads In C# using AOP?
Or you can use BackgroundWorker which has ReportProgress
What you need is System.Windows.Threading.Dispatcher's BeginInvoke method. You can't directly modify a WPF object from your background thread, however you can dispatch a delegate to do that.
In your derived Window class object you have the Property Dispatcher, so you use it as follows:
Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(status) => { StatusTextBox.Text = status },
thestatus
);
I'm sorry that I can't test that currently and I don't have the project here, where I did that. But I'm sure it will work, good luck ;)
Update: Oops, you're using Form's... I've written about WPF, sorry.

update button property from another class and thread in c#

im new to C# language and i would appreciate any help/feedback on the following issue.
Basically, im trying to develop a program such that it has two classes class1(main thread) and class2. class1 has a button and class2 starts a thread and do some calculations. How can i update any property of the button in class1(main) from class2 within the thread in class2?
here is an example i would like to do
// CLASS 1 is a win form
class1:form
{
public btn_click()
{
// call function startthread from class2
btn.Enabled = false;
startthread()
}
}
// CLASS 2 is not a winform
class2
{
public startthread()
{
Thread worker = new Thread(doCalculation)
}
public doCalculation()
{
// do some calculation then
// blahhh
// blahhh
// HERE I NEED SOMEHOW ENABLED MY BUTTON
}
}
This depends a bit on the UI framework you're using. As an example, say you wanted to update the text written on the button.
If you're using WPF, you could use:
class1Instance.TheButton.Dispatcher
.BeginInvoke( (Action) () => class1Instance.TheButton.Content = "Foo" );
With Windows Forms, you'd use:
class1Instance.TheButton
.BeginInvoke( (Action) () => class1Instance.TheButton.Text = "Foo" );
You have to bring the modifying code back onto the the main thread, regardless whether its WPF or Winforms, cross-thread access to the UI is either explicitly disallowed (usually) or strongly discouraged and buggy.
There are a lot of ways to do this, in Winforms I typically use Control.Invoke like this:
public void UpdateMyTextBox(string NewText)
{
if(InvokeRequired)
Invoke(new Action<string>(UpdateMyTextBox), NewText);
else
myTextBox.Text = NewText;
}
In this example, the InvokeRequired property will check to see if it's being called on the UI thread, and if not, we will call Invoke which will place the request onto the UI thread.
In WPF, you watch to use the Dispatcher, and you do so in a very similar way:
public void UpdateMyWpfTextBox(string NewText)
{
if(!CheckAccess())
Dispatcher.Invoke(new Action<string>(UpdateMyWpfTextBox), NewText);
else
myTextBox.Text = NewText;
}
Not to sure what your trying to do here, but this is best an answer I can come up with, considering the questions. I'm going to assume this is a winforms application
Class2 is going to need a reference to the button in Class 1. So you'll need to pass that to it.
Once Class2 has reference to the button you can attempt to modify the property as
btn.Text = "New Text";
I'm going to assume again that you've tried this route, got an exception and so you posted your question. The reason you got the exception was that your btn and your class2 are running in separate threads. To get around this, you can call invoke on the button. I often handle this like this.
private void SetButtonText(string newText) {
if (btn.InvokeRequired) {
Invoke((MethodInvoker)(() => SetButtonText(newText)));
}
else {
btn.Text = newText;
}
}

Categories