4

I have an winform and an interface callback that continuously sends updates. I want to be able to update a label1.Text from the callback interface. However since the intrface runs on an seperate thread I do not think i can do it directly so I was trying to use a delegate and invoke.

However I am running into an error:

Invoke or BeginInvoke cannot be called on a control until the window handle has been created - at

 form1.Invoke(form1.myDelegate, new Object[] { so.getString() });

Here is the full code.

     public partial class Form1 : Form
     {

         MyCallBack callback;
         public delegate void UpdateDelegate(string myString);
         public UpdateDelegate myDelegate;

         public Form1()
         {
             InitializeComponent();

             myDelegate = new UpdateDelegate(UpdateDelegateMethod);
             callback = new MyCallBack(this);
             CallBackInterfaceClass.SetCallBack(callback);

            callback.OnUpdate();
        }

         public void UpdateDelegateMethod (String str)
         {
             label1.Text = str;
         }
     }

     class MyTestCallBack : Callback
     {
         public Form1 form1;
         public SomeObject so;

         public MyTestCallBack(Form1 form)
         {
             this.form1 = form;
         }

         public void OnUpdate(SomeObject someobj)
         {
             so = someobj;
             OnUpdate();
         }

         public void OnUpdate()
         {
             form1.Invoke(form1.myDelegate, new Object[] { so.getString() });
         }

     }

Two questions.

  1. Can anyone explain what I am doing wrong?

  2. Is this actually best method to do this?

Here is the answer based on the reply by bokibeg (see below) with a couple of modifications to make it work:

public partial class Form1 : Form {
MyTestCallBack _callback;

public Form1()
{
    InitializeComponent();

    _callback = new MyTestCallBack();
    _callback.MyTestCallBackEvent += callback_MyTestCallBackEvent;
    _callback.OnUpdate();
}

private callback_MyTestCallBackEvent(MyTestCallBackEventArgs e)
{
     if (InvokeRequired)
     {
        Invoke(new Action(() =>
        {
            callback_MyTestCallBackEvent(sender, e);
        }));
        return;
     }
     label1.Text = e.SomeObject.GetDisplayString();
}

class MyTestCallBackEventArgs : EventArgs 
{
     public SomeObject SomeObj { get; set; } 
}

class MyTestCallBack : Callback 
{
    public event EventHandler<MyTestCallBackEventArgs> MyTestCallBackEvent;
    private SomeObject _so;

protected virtual void OnMyTestCallBackEvent(MyTestCallBackEventArgs e)
{
    if (MyTestCallBackEvent != null)
        MyTestCallBackEvent(this, e);
}

public void OnUpdate(SomeObject someobj)
{
    _so = someobj;
    OnMyTestCallBackEvent(new MyTestCallBackEventArgs { SomeObject = _so });
} }
azuric
  • 2,679
  • 7
  • 29
  • 44
  • 4
    If I were you I wouldn't call form's delegates, I'd do it the other way around. The `Callback` derived class should simply raise an event and your form should subscribe to that event. I don't see where `MyTestCallBack` is instantiated but if it's likely within `Form1` which makes it even more so obvious that you should use an event. Edit: ok I see where it's instantiated so yeah, I'd definitely fire events from my callback class rather than what you're trying in above code. – bokibeg Mar 19 '15 at 13:10

1 Answers1

1

Here's what I'd do:

public partial class Form1 : Form
{
    MyTestCallBack _callback;

    public Form1()
    {
        InitializeComponent();

        _callback = new MyTestCallBack();
        _callback.MyTestCallBackEvent += callback_MyTestCallBackEvent;
        _callback.OnUpdate();
    }

    private callback_MyTestCallBackEvent(MyTestCallBackEventArgs e)
    {
        // TODO: Invoke code here with e.g. label1.Text = e.SomeObj.ToString()...
    }
}

class MyTestCallBackEventArgs : EventArgs
{
    public SomeObject SomeObj { get; set; }
}

class MyTestCallBack : Callback
{
    public event EventHandler<MyTestCallBackEventArgs> MyTestCallBackEvent;
    private SomeObject _so;

    public MyTestCallBack()
    {
        //
    }

    protected virtual void OnMyTestCallBackEvent(MyTestCallBackEventArgs e)
    {
        if (MyTestCallBackEvent != null)
            MyTestCallBackEvent(e);
    }

    public void OnUpdate(SomeObject someobj)
    {
        _so = someobj;
        OnMyTestCallBackEvent(new MyTestCallBackEventArgs { SomeObject = _so });
    }
}

It separates the GUI logic from whatever that thread is doing. It fires an event and it's Form's duty to do whatever it pleases with it.

I'm not sure if this compiles, I wrote it in text pad. Tell me if you have questions.

You probably just learned about delegates and got carried away with it, this is similar as it uses an event but the event is properly placed in the "back end" logic - form may or may not use it. Also notice how form's code is a lot better, it doesn't have so much boilerplate code just to implement some background service class.

Note however that MyTestCallBackEvent event may keep firing even after you close the form so make sure you unsubscribe from it when the form closes or disposes (or whenever you feel like form doesn't need it anymore).

Oh and I almost forgot: the original error you were getting is because you called Invoke when one wasn't required and Form definitely wasn't ready for it. Read this question to see how to safely invoke controls.

Community
  • 1
  • 1
bokibeg
  • 2,081
  • 16
  • 23
  • Hi couple of issues: I added "this" to if...MyTestCallBackEvent(this, e) in OnMyTestCallBackEvent get it to compile. But I am getting a "Cross-thread operation not valid: Control 'label1' accessed from a thread other than the thread it was created on." for label1.Text = e.SomeObject.ToString(). – azuric Mar 19 '15 at 15:12
  • I do see your point though, it is much cleaner than the delegate methodology. – azuric Mar 19 '15 at 15:16
  • 1
    if (InvokeRequired) { Invoke(new Action(() => { callback_MyTestCallBackEvent(sender, e); })); return; } solves the issue I will update my answer with this. – azuric Mar 19 '15 at 15:34
  • 1
    @azuric no probs, the `// TODO: Invoke code here` is where you should place the `if (InvokeRequired) { Invoke(...) }` code. Either way make sure InvokeRequired is called **or even better** use the helper function you can find in the link in the last paragraph of my post. – bokibeg Mar 19 '15 at 19:38