Kaiser69
Kaiser69

Reputation: 430

GUI Invoke with EventHandlers

I have a class like that "ClientSocket.cs"

  class ClientSocket {
     public delegate void ConnectHandler(object sender, EventArgs e);
     public event ConnectHandler ConnectEvent = delegate { };

     protected void OnConnectEvent(EventArgs e) {
        ConnectHandler ev = ConnectEvent;
        ev(this, e);
    }

  }

And another class "myForm.cs"

public partial class myForm : Form {
    private ClientSocket client;

    private void button1_Click(object sender, EventArgs e) {
        client = new ClientSocket();
        client.ConnectEvent += myForm_OnConnectEvent;

        client.connect();
    }


    // Handler for ConnectEvent
    private void myForm_OnConnectEvent(object sender, EventArgs e) {
        //this.BeginInvoke((MethodInvoker)delegate { writeLog("Connected"); }); 

        writeLog("Connected");
    }

    // Function that write a log string to a TextBox
    public writeLog(string log) {
        guiTextBox.AppendText(log);
    }
  }

Here the question. I try to call writeLog with BeginInvoke or calling it directly. Sometimes I get an InvalidOperationException when writing to guiTextBox. I don't understand why I receive that message. The event is fired by ClientSocket object, but the event handler is relative to the main UI-thread (myForm).

Can I avoid to use BeginInvoke/Invoke for each EventHandler of my class?


EDIT: I understand what's the difference, now I'm try to understand the best practice for calling the event.

Should I put the BeginInvoke/Invoke method when RAISING the event in the BASE class (ClientSocket in that case)

    protected void OnConnectEvent(EventArgs e) {
        ConnectHandler ev = ConnectEvent;

        this.BeginInvoke((MethodInvoker)delegate { ev(this, e);});
    }

or should I put that WHEN I'm using that object and add a listeners to that handler

    // Handler for ConnectEvent used in GUI (myForm)
    private void myForm_OnConnectEvent(object sender, EventArgs e) {
        this.BeginInvoke((MethodInvoker)delegate { writeLog("Connected"); }); 
    }

Cheers

Upvotes: 2

Views: 1111

Answers (2)

Scott Chamberlain
Scott Chamberlain

Reputation: 127593

the this.BeginInvoke inside ClientSocket does not exist. To be able to do the BeginInvoke it must be called on a object that has that method (your form in your case).

If you wanted the invoking to happen inside your ClientSocket class you would need to pass in a Control that has the BeginInvoke function.

However if I where writing this I would not do this approach. It adds a unnecessary requirement to ClientSocket that you must have a Control passed in (this is called Tightly Coupling and you should try to avoid it in your programming). Personally I would let the event pass along in whatever thread it wants to be raised in and let the consumer worry about doing any special invoking (if they even need to at all).

Here is how I would write myForm_OnConnectEvent, this pattern checks to see if we need to invoke and if we do it calls the function again with the same arguments but this time on the UI thread.

// Handler for ConnectEvent used in GUI (myForm)
private void myForm_OnConnectEvent(object sender, EventArgs e) 
{
    if(this.InvokeRequired)
    {
        this.BeginInvoke(new ConnectHandler(myForm_OnConnectEvent), new object[] {sender, e});
        return;
    }

    writeLog("Connected");
}

As a side note, I don't know what writeLog is doing (it should have a capital W by the way) but if it is not interacting with the UI you don't need to do any invoking at all. If it interacts with a TextBox or something else on the UI, that is where I would do my invoking.

private void myForm_OnConnectEvent(object sender, EventArgs e) 
{
    writeLog("Connected");
}

private void writeLog(string logMessage) 
{
    if(logTextBox.InvokeRequired)
    {
        logTextBox.BeginInvoke(new Action<string>(writeLog), logMessage);
        return;
    }

    var logLine = String.Format("{0:g}: {1}{2}", DateTime.Now, logMessage, Enviorment.NewLine);
    logTextBox.AppendText(logLine);
}

Upvotes: 3

Dennis
Dennis

Reputation: 37780

The event handler is declared in myForm, but the thread, which executes handler, is defined by the logic of ClientSocket class. If this will be background thread, event handler will be raised from background thread, so, you'll need BeginInvoke to avoid cross-thread access to controls.

In other words: belonging of any method of any type isn't related to the thread, which will ever execute this method. These things (types and threads) are parallel universes.

By the way, you can replace this:

public delegate void ConnectHandler(object sender, EventArgs e);
public event ConnectHandler ConnectEvent = delegate { };

with this:

public event EventHandler ConnectEvent;

There's no need to make yet another delegate type.

Upvotes: 3

Related Questions