I don't know how to acces a label element (myLabel
) from a different class outside my Control Class while using background worker.
I really don't know the correct syntax.
Thanks a lot.
This is my code:
public partial class BasicControl : UserControl
{
// ...
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
this.Invoke(new MethodInvoker(delegate { myLabel.Text = "THIS WORKS!"; }));
var moc = new MyOtherClass();
string result = moc.myMethod();
}
}
internal class MyOtherClass
{
public void myMethod()
{
myLabel.Text = "THIS DOES NOT WORK!"; // NOT WORKING
}
}
The DoWorkEventArgs class that you pass in your code is intended to allow interactions between the caller and the worker thread using its Argument property. In the following example it will be set to an instance of CustomDoWorkContext
that implements INotifyPropertyChanged
. Pass it when you start the worker bw_DoWork(sender, args: new DoWorkEventArgs(argument: context))
and have the worker pass args
on to the method string result = moc.myMethod(args)
. This way, the outer, calling class receives PropertyChanged
notifications while the thread is running whenever a bound property changes.
internal class MyOtherClass
{
internal string myMethod(DoWorkEventArgs args)
{
if (args.Argument is CustomDoWorkContext context)
{
context.Message = $"THIS WORKS! @ {DateTime.Now} ";
}
return "Your result";
}
}
Here is the custom class that creates context for the worker thread.
class CustomDoWorkContext : INotifyPropertyChanged
{
public CustomDoWorkContext(CancellationToken cancellationToken)
{
Token = cancellationToken;
}
// This class can have as many bindable properties as you need
string _message = string.Empty;
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
int _count = 0;
public int Count
{
get => _count;
set => SetProperty(ref _count, value);
}
public CancellationToken Token { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>( ref T backingStore, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(backingStore, value)) return false;
backingStore = value;
OnPropertyChanged(propertyName);
return true;
}
}
The key point is to subscribe to the PropertyChanged
event before starting the background worker.
// Get notified when any property changes in CustomDoWorkContext.
var context = new CustomDoWorkContext(cancellationToken: _cts.Token);
context.PropertyChanged += (sender, e) =>
{
switch (e.PropertyName)
{
case nameof(CustomDoWorkContext.Message):
Invoke((MethodInvoker)delegate
// When myMethod sets Message, change the label text.
{ myLabel.Text = $"{context.Message}"; });
break;
case nameof(CustomDoWorkContext.Count):
// When the count increments, change the checkbox text.
Invoke((MethodInvoker)delegate
{ checkBoxDoWork.Text = $"Count = {context.Count}"; });
break;
}
};
Then start the worker by passing the args (something like this).
// Set the `Argument` property when you start the worker.
// (Like this or something similar.)
Task.Run(() => bw_DoWork(sender, args: new DoWorkEventArgs(argument: context)));
Have the worker thread pass the args along to the myMethod
:
private async void bw_DoWork(object sender, DoWorkEventArgs args)
{
var moc = new MyOtherClass();
if (args.Argument is CustomDoWorkContext context)
{
args.Cancel = context.Token.IsCancellationRequested;
while (!args.Cancel) // Loop for testing
{
context.Count++;
string result = moc.myMethod(args); // Pass the args
try { await Task.Delay(1000, context.Token); }
catch (TaskCanceledException) { return; }
}
}
}
TEST
BasicControl:UserControl
with checkbox and label is placed on the MainForm
.
Toggling the checkbox starts and stops the worker after instantiating CustomDoWorkContext
.