-1

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
       }
    }
  • If you had actually researched the `BackgroundWorker` properly then you'd know that you MUST NOT access controls in the `DoWork` event handler. If you want to access a control during the background work then you call `ReportProgress` and access the control in the `ProgressChanged` event handler. If you want to access a control after the background work is done then you do so in the `RunWorkerCompleted` event handler. Those event handlers are both executed on the UI thread, unlike `DoWork`. You should not be calling `Invoke` as avoiding that is the whole point of the `BackgroundWorker`. – John Jul 15 '22 at 04:00
  • You may benefit from [this thread](https://www.vbforums.com/showthread.php?542316) of mine. – John Jul 15 '22 at 04:01
  • I definitely need to acces the control during the background work. But I do not want to show a progress bar but show current values in label element instead. – ultimateCoder Jul 15 '22 at 04:05
  • John Thank you. But I still do not know to access ProgressChanged event handler from another class. – ultimateCoder Jul 15 '22 at 04:13
  • Regardless of the `BackgroundWorker`, you should not be accessing a control in any class other than the form that that `Label` is part of. If you need to put text on that `Label` that is generated from another class then you should have a function in that class that returns that text. The form can then call that method to get the text and display it in its own `Label`. In that case, it would be the form - more specifically the user control in your case - handling the `ProgressChanged` event of the `BackgroundWorker` and your issue goes away. – John Jul 15 '22 at 05:14
  • The other class shouldn't need to know anything about the `BackgroundWorker` or the `Label`. If its job is to generate the text then that's ALL it should do. Think Single Responsibility Principle. – John Jul 15 '22 at 05:16
  • *"I do not want to show a progress bar"*. Then don't. No one said you have to. Do your research first, then you would know that, while `ProgressChanged` is often used to update a progress bar, there's no reason that it has to be and you can do anything else you want regardless, given that you can pass an arbitrary object from the `ReportPorgress` method to the `ProgressChanged` event handler. The link I provided demonstrates that. – John Jul 15 '22 at 05:18
  • The thing is that the "other class" is called from the `BackgroundWorker`. And I really do not know how to call `bw_ProgressChanged` method from the other class. – ultimateCoder Jul 15 '22 at 05:36
  • Nothing is called "from the `BackgroundWorker`". The other class is referenced in the `DoWork` event handler of the `BackgroundWorker`, which is part of your user control. That user control is handling the `DoWork` event of the `BackgroundWorker` so it can handle the `ProgressChanged` even as well. It can then reference that other class in that event handler instead of the other. – John Jul 15 '22 at 06:13
  • I can understand the theory but as I stated before I do not know the syntax in which I could handle `ProgressChanged` event. – ultimateCoder Jul 15 '22 at 09:57
  • If you have a reference to the label in the other class then you already know how to do it. Just change `this` to the name of the label: `myLabel.Invoke(new MethodInvoker(delegate { myLabel.Text = "THIS WORKS!"; }));` – Idle_Mind Jul 15 '22 at 19:21
  • ..and that's a big "IF" there. Did you actually pass a reference to the label into the other class? It's not clear what part isn't working. – Idle_Mind Jul 15 '22 at 19:22
  • @Idle_Mind It does not work. It says: "The name 'myLabel' does not exist in the current context". – ultimateCoder Jul 16 '22 at 04:08
  • It would make more sense to write ot like this: `BasicControl.Invoke(new MethodInvoker(delegate { myLabel.Text = "THIS WORKS!"; }));` Now it is only wrong the `myLabel.Text` part with the same error as previous. The point is that I am accessing it from a different class (`MyOtherClass`). – ultimateCoder Jul 16 '22 at 04:08
  • Then when you create your class instance, you can pass a reference to the label in via the CONSTRUCTOR. Look up an example, there are tons!! – Idle_Mind Jul 16 '22 at 15:42

1 Answers1

0

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.

initial state

Toggling the checkbox starts and stops the worker after instantiating CustomDoWorkContext.

continuous events

IVSoftware
  • 5,732
  • 2
  • 12
  • 23
  • [Clone](https://github.com/IVSoftware/label-element-from-background-worker.git) this example. – IVSoftware Jul 15 '22 at 15:29
  • Thanks for the answer. In your solution `myMethod()` in `MyOtherClass` returns the value back to backgroundworker after it has finished. But the whole point is that `MyOtherClass` is doing the process thing and is supposed to send results continuously before it finishes. – ultimateCoder Jul 15 '22 at 18:25
  • I am talking about `MyOtherClass`. Once I use `return DateTime.Now;` the process of `myMethod()` finishes and that is exactly what I do not want. – ultimateCoder Jul 15 '22 at 18:56
  • No problem, if the `result` of `myMethod` _isn't_ what goes in myLabel.Text then all you have to do is pass the args to the `myMethod`. I made an edit to show that. The point of the answer is that the purpose of sending `DoWorkEvent` args in the first place is to be able to communicate between the caller and the thread. – IVSoftware Jul 16 '22 at 13:18
  • This background worker thing seems rather diffucult. Anyway I am trying to implement it but I got stuck on this line: `var context = new CustomDoWorkContext(cancellationToken: _cts.Token);` It was throwing error saying: CS1739: The best overload for 'CustomDoWorkContext' does not have a parameter named 'cancellationToken'. So I changed the name to `token` but still there is an error with `_cts`. It says CS0103: The name '_cts' does not exist in the current context. This syntax is all Greek to me so I really have no idea about what to do with that. – ultimateCoder Jul 17 '22 at 14:24
  • You are quite correct. My final version of the `CustomDoWorkContext` did not make it into my edited post. I do apologize. I have fixed the typo! I do have [100% functional code on GitHub](https://github.com/IVSoftware/label-element-from-background-worker.git) to either _browse_ or _clone_. Let me know if you need any assistance with either, I'm happy to help. – IVSoftware Jul 17 '22 at 14:49
  • Syntax-wise, you're not alone! For me, even after all these years, there's always something new and yeah, it looks like "Greek" at first. Here's what I'd like to suggest: 1) Get it running in your Visual Studio 2) Make sure it actually _does_ answer your question. 3) Learn the unfamiliar syntax by setting breakpoints to see _why_ it works. 4) Ask more questions if they come up 5) Use your new understanding to modify it and adapt it to you own uses. Not just _this_ example but _any_ :) – IVSoftware Jul 17 '22 at 15:09
  • 1
    Ok, I finally made it work! There were a few issues with arguments that I had to work out. I also found out that your solution is not based on the actual `backgroundworker` but rather a separate `thread` running asynchronously. It took me a while to notice that. I also need to process the result of the bw that I had in my `bw_RunWorkerCompleted` method so I changed it to `var result = await Task.Run(() => bw_DoWork(sender, args: new DoWorkEventArgs(argument: context)));` and made proper adjustments. Anyway it works now. Thanks a lot, I really appreciate your help in a big way. – ultimateCoder Jul 18 '22 at 03:31
  • Nice job! And I'm glad you said that because (unless I missed it) I didn't really see `BackgroundWorker` _in your actual code_ that you posted. I'm guessing that might be part of the reason it got a downvote (consider editing your post)? Without that, I accomplished the same thing using `Task`. Here's a great SO post that might add to your understanding: [What is the difference between task and thread?](https://stackoverflow.com/q/4130194/5438626) – IVSoftware Jul 18 '22 at 13:12