This question already has answers here:
Invoke in Class in c# winforms
(4 answers)
Closed 2 years ago.
so I want to access a label in my main form from a static Method in another class within another thread
How do I do that?
by the way, I used this code to start Threads
new Thread(new ThreadStart(WebHelper.Check)).Start();
The UI can only be altered by the UI Thread. If your thread is in the same class as the form, you could access the UI Thread by using the Control.Invoke method.
this.Invoke(new Action(() => {
//UI Code here
}));
However, if you want to access it from another class, I would recommend using events to access the UI's class, and then you can use Invoke.
//This would be in your class
public delegate void UIStuff(string text);
class AClass
{
public static event UIStuff ChangeLabel;
public static void StaticFunction() {
ChangeLabel.Invoke("Test");
}
}
//This would be your form class
public partial class Form1 : Form
{
public Form1(){
AClass.ChangeLabel += AClass_ChangeLabel; //Note you can press tab for auto complete
}
AClass_ChangeLabel(string text) {
this.Invoke(new Action(() => {
this.MainLabel = text;
}));
}
}
Note: I believe you could also change the label by creating a new instance in your static method Form1 f1 = new Form1();, but I don't believe this is what you want.
Related
I want to show DateTime into a text box of a form "Form1".I have created a class "schedule" to create a timer with an interval 1 sec. But cannot access and update Form1's textbox field "xdatetxt". I cannot understand why is it not accessing the controls xdatetxt in Form1.
Schedule.cs
class Schedule{
System.Timers.Timer oTimer = null;
int interval = 1000;
public Form anytext_Form;
public Schedule(Form anytext_form)
{
this.anytext_Form = anytext_form;
}
public void Start()
{
oTimer = new System.Timers.Timer(interval);
oTimer.Enabled = true;
oTimer.AutoReset = true;
oTimer.Start();
oTimer.Elapsed += new System.Timers.ElapsedEventHandler(oTimer_Elapsed);
}
private void oTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{//i want to put here a line like "anytext_Form.xdatetxt.Text = System.DateTime.Now.ToString();"
}
}
in form1.cs:
public partial class Form1 : Form{
public Form1()
{
InitializeComponent();
InitializeScheduler();
}
void InitializeScheduler()
{
Schedule objschedule = new Schedule(this);
objschedule.Start();
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
Check this SO thread -
How to access Winform textbox control from another class?
Basically expose a public property that updates Textbox
Make Textbox public
Also note, you need to update Form Controls in UI thread
So you need to get the instance of the form that you want to modify the text. You can do it by passing a reference to the objschedule or by using Application.openforms.
The first way is perfect if you already have the form referenced but if you don, just:
private void oTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
dynamic f = System.Windows.Forms.Application.OpenForms["anytext_Form"];
f.xdatetxt.Text=System.DateTime.Now.ToString();
}
There are a few problems here, but both are rather straight-forward to address.
Problem 1: Referencing the form by its base class
Your Schedule class' constructor takes an instance of Form. That is the base class of your Form1 class, and it has no xdatetxt field. Change your Schedule constructor to accept and store an instance of Form1 instead:
public Form1 anytext_Form;
public Schedule(Form1 anytext_form)
{
this.anytext_Form = anytext_form;
}
Problem 2: Updating a control from a non-UI thread
Once you get the compiler error fixed, you will encounter a runtime error. The reason is that the Timer class executes its callback on a background thread. Your callback method attempts to access a UI control from that background thread, and that is not allowed.
I could give a solution inline here, but instead I will point you at another StackOverflow post with far more details about the problem and how to fix it: Cross-thread operation not valid.
I’m using a separate class that instantiates different barcode objects depending on the type of machine, which raise events once a barcode is read.
So to transmit it to the form, I also raise an UNIQUE event ‘myEvent’ that all my forms listens, but inside the form I have to use this.Invoke((Action) mymethod(argument)); to be able to update the components at the user interface, as long as the handler to myEvent is executed on another thread different from that of the form.
So I have this:
My class -> barcode.readed(..) is triggered, then from its handler, I Raise myEvent, that is captured on my Form, and from the handler for myEvent on my form, I execute the this.Invoke…
I know this is a somewhat standard procedure, but I’d like to get rid of that Invoke, and instead, calling directly to the myMethod(argument); function.
I think that this is related to make my class thread safe, but I don’t know how to implement that in my case.
In fact, if I use the original manufacturer barcode.readed() event from inside the form it does not need to call the invoke, as long as this is thread safe, but I don’t know how to mimic that, and I really need to wrap all the different barcode handlers inside a different project, for my forms to use only one ‘myEvent’ that returns the desired barcode, and thus, not repeating code.
Thanks in advance,
Roger Tranchez
If you inherit your class from control (basically create a new custom control), you can handle events on the UI thread without using Invokes as the control (your barcode reader class) is part of the UI thread.
Sounds like you are using a worker thread for reading barcodes, to keep the UI responsive. And the barcode object just runs on whichever thread created it.
You can centralise event handling (to avoid repeating code) and read barcodes on a worker thread as follows:
Wrap the barcode object inside a custom object/library MyBarcodeReader that exposes MyEvent.
In the constructor for MyBarcodeReader, capture the current SynchronizationContext to a class field syncContext. This will be your UI's SynchronizationContext if your form constructs MyBarcodeReader.
When you activate MyBarcodeReader (e.g. MyBarcodeReader.Execute), create the barcode object on a worker thread.
When you need to raise MyEvent, call syncContext.Send (this will be on the worker thread), passing a delegate whose purpose is to raise MyEvent. syncContext.Send will synchronise to the UI thread (like Control.Invoke). The code below illustrates this.
public class MyBarcodeReader
{
private readonly SynchronizationContext syncContext;
// Handler for barcode object's Readed event.
private void Barcode.Readed(Object sender, Event e)
{
// Block the worker thread to synchronize with the thread associated
// with SynchronizationContext.
syncContext.Send(SyncMyEvent, (Object)e);
}
// Raises MyEvent on the thread associated with SynchronizationContext,
// usually a UI thread.
private void SyncMyEvent(Object o)
{
if (MyEvent != null)
{
MyEvent((Event)o);
}
}
// Constructor.
public MyBarcodeReader()
{
syncContext = SynchronizationContext.Current;
}
}
The approach here will block the worker thread (same as Control.Invoke) but not block the UI thread. If you have one or more forms that subscribe to MyEvent, they don't need to use Control.Invoke; they don't even need to know about the worker thread.
There are some excellent online references on SynchronizationContext, see CodeProject and MSDN magazine.
I've found a solution here: Basically it passes over the form control to the class constructor, and then, inside that class it uses the form.Invoke to fire the event from the form ui thread.
CLASS:
using System;
using System.Windows.Forms;
using System.Threading;
namespace ThreadTest
{
public class WorkerClass
{
private Thread thr;
// UI control for update
public Control UIControl { get; set; }
public delegate void StatusUpdate(DateTime dateTime, string message);
public event StatusUpdate OnStatusUpdate;
// Starts thread
public void Start()
{
thr = new Thread(new ThreadStart(MainWorker));
thr.Start();
}
// Main thread worker
public void MainWorker()
{
int i = 0;
while (true)
{
string message = string.Format("Value of i={0}", i++);
FireStatusUpdate(DateTime.Now, message);
Thread.Sleep(1000);
}
}
// Fire thread safe event
private void FireStatusUpdate(DateTime dateTime, string message)
{
// UIControl is set and OnStatusUpdate has subscriber
if (UIControl != null && OnStatusUpdate != null)
{
if (UIControl.InvokeRequired)
{
UIControl.Invoke(new StatusUpdate(FireStatusUpdate),
new object[] { dateTime, message });
return;
}
OnStatusUpdate(dateTime, message);
}
}
}
}
FORM:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace ThreadTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
WorkerClass worker = new WorkerClass();
// add event handler
worker.OnStatusUpdate += new WorkerClass.StatusUpdate(worker_OnStatusUpdate);
// add UI control to invoke
worker.UIControl = this;
worker.Start();
}
void worker_OnStatusUpdate(DateTime dateTime, string message)
{
label1.Text = dateTime.ToLongTimeString();
label1.Text += " " + message;
}
}
In my case, I've changed the type of event, from StatusUpdate to
EventHandler<MyEventArgs>
, being MyEventArgs this class:
public class MyEventArgs : EventArgs
{
public string MyString { get; set; }
}
Thank you !
I came across a situation which puzzled me at work today which I have simplified in the following code. This code builds and throws no exceptions during debug.
Suppose I have a WinForms app. In my main UI thread I spin off another thread which instantiates an object which in turn holds reference to a control (label1 in my example). I then call a method on my object (SetLabelText) which passes it's execution back onto the UI thread if required.
What stumped me was how, when we are back in the UI thread and executing SetLabelText, is .net CLR able to access the labelText variable when we are executing on a thread (ie the UI thread) which did not create the instance of Thing.
public partial class Form1 : Form
{
delegate void DoSomethingDelegate();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var t = new Thread(DoSomethingWithThing);
t.Start();
}
private void DoSomethingWithThing()
{
var thing = new Thing(this.label1);
thing.SetLabelText();
}
}
public class Thing
{
private Label label;
private string labelText = "new value";
delegate void SetLabelTextDelegate();
public Thing(Label label)
{
this.label = label;
}
public void SetLabelText()
{
if (this.label.InvokeRequired)
{
var setLabelDel = new SetLabelTextDelegate(SetLabelText);
this.label.Invoke(setLabelDel);
}
else
{
this.label.Text = this.labelText;
}
}
}
References to objects are available on any thread.
Threads are not sand-boxed from each other. They share resources unless you explicitly create non-shared resources.
Threads are execution contexts. Think of your application as a kitchen and each thread as a chef. They can work at the same time but if two of them try to use the same knife at the same time, things get messy. This is why c# has the lock keyword and other synchronization mechanisms.
WinForms has restrictions on access to controls because of the way WinForms renders.
I have three class, tow of them was UI class, in the mainForm class, I start a new form by execute
new LoginForm.ShowDialog();
in the LoginForm class, I write code about log in and log out, when the use loged in, I start a new thread to check if something need to be done,and update the databases; and here is the question, I don't know how to update a label that in the MainForm
I search this question and they told me I should to use Delegate.but it really puzzled me a lot cause they don't in a same class so I don't know how to use Delegate cross thread and cross different
Until now, my code is like this
MainForm.cs:
public partial class MainForm : Form
public delegate void testDelegate();
public MainForm()
{
InitializeComponent();
}
public void msg(string s)
{
label.Test = s;
}
}
LoginForm.cs:
JobDoer jD = new JobDoer();
Thread t2 = new Thread(new ThreadStart(jD.run));
t2.Start();
JobDoer:
public void run()
{
//tD();
tD = new MainForm.testDelegate(MainForm.msg);
//this.
Thread.Sleep(1000);
return;
}
what should I do next?
Taken from: Best Way to Invoke Any Cross-Threaded Code?
You also could use an extension method and lambdas to make your code much cleaner.
using System.ComponentModel;
public static class ISynchronizeInvokeExtensions
{
public static void InvokeEx<T>(this T #this, Action<T> action) where T : ISynchronizeInvoke
{
if (#this.InvokeRequired)
{
#this.Invoke(action, new object[] { #this });
}
else
{
action(#this);
}
}
}
So now you can use InvokeEx on any ISynchronizeInvoke and be able to access the properties and fields of implementing class.
this.InvokeEx(f => f.listView1.Items.Clear());
For this kind of thing theres the
System.ComponentModel.BackgroundWorker class.
https://learn.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-implement-a-form-that-uses-a-background-operation.
It handles the thread sync for you, plus it has a usefull prgress update event
I have the class called mainForm that it is main window of my program. I create a TextBox(this TextBox Logs program) object in this class and i want to write program status to it. I do this from mainForm and other object(by passing TextBox object to it) easily, But when i want to do that from another thread, it's complicated.
However, i am writing to TextBox by the thread that it runs the defined code in mainForm(using delegate).
My question is, How to write in the TextBox from thread that runs in another class?
public partial class mainForm : Form
{
TextBox log = new TextBox();
.
.
.
OtherClass o = new OtherClass(log);
}
class OtherClass
{
private TextBox log;
public otherClass(TextBox aLog)
{
log = aLog;
Thread thread = new Thrad(new ThreadStart(this.run));
thread.Start();
}
private void run()
{
log.Text = "Message";// I Can't Do This. Can I Use Delegate Here? How?
}
}
You can use Invoke/BeginInvoke:
log.BeginInvoke(
(Action)(() =>
{
log.Text = "Message";
}));
This allows the secondary thread to safely forward GUI changes to the GUI thread which is the only one that should apply them.
Another way using defined delegate - incidently Xt here can be reused for other methods as long as the signature is the same. Parameters can also be passed - (would then have parameters in the Xt delegate and Invoke of it would pass a coma separated list for each.
private void run()
{
XteChangeText();
}
private delegate void Xt();
private void XteChangeText()
{
if (log.InvokeRequired)
{
Invoke(new Xt(XteChangeText));
}
else
{
log.Text="Message";
}
}