Search code examples
c#eventsdelegatesunsubscribe

delegate with confirm and unsubscribe issue


I am struggling with the delegate/event. Most likely I do not understand the whole picture. for the sake of argument, I have two forms: form 1 and form 2.

Questions:

  1. How to appropriately unsubscribe the event "frm2.ButtonClicked -= new PassMsg(MsgReceivedFromFrm2);"?
  2. If I kill form 2, I still get the confirming message.why does this happenning?

:

 public partial class Form1 : Form
{

    PassMsg NotifyFrm2EventHandler;                             // Delegate definition
    PassMsgWithConfirm NotifyFrm2WithConfirmEventHandler;       // Delegate with return definition
    event PassMsg NotifyFrm2AsEvent;                            // Event definition
    int i;
   
    public Form1()
    {
        InitializeComponent();
        i = 0;
    }

    private void btn_InitializeFrm2_Click(object sender, EventArgs e)
    {
        Form2 frm2 = new Form2();
        NotifyFrm2EventHandler = new PassMsg(frm2.ReceiveMsgFromForm1);                                             // setup link from Form 1 to Form 2
        NotifyFrm2WithConfirmEventHandler = new PassMsgWithConfirm(frm2.ReceiveMsgFromForm1withConfirm);            // setup link from Form 1 to Form 2
        NotifyFrm2AsEvent += new PassMsg(frm2.ReceiveMsgFromForm1);                                                 // setup link from Form 1 to Form 2
        frm2.ButtonClicked += new PassMsg(MsgReceivedFromFrm2);                                                     // setup link from Form 2 to Form 1
        frm2.Show();
    }

    private void btn_Send2Frm2_Click(object sender, EventArgs e)
    {
        i++;

        NotifyFrm2EventHandler?.Invoke("message from form 1 with index " + i.ToString());                                    //  Notification by Call
    }

    private void btn_Send2Frm2WithConfirm_Click(object sender, EventArgs e)
    {
        i++;
        bool confirmed = (bool)(NotifyFrm2WithConfirmEventHandler?.Invoke("message from form 1 with confirm with index" + i.ToString()));             //  Notification by Call
        if (confirmed)
        {
            MessageBox.Show("confirmed.");
        }

    }

    private void btn_sendAsEvent_Click(object sender, EventArgs e)
    {
        i++;
        NotifyFrm2AsEvent?.Invoke("message from form 1 as event with index" + i.ToString());                                //  Notification by Call
    }


    void MsgReceivedFromFrm2(string input)
    {
        label1.Text = input;
    }

}

And form2 code:

    public partial class Form2 : Form
{

    public event PassMsg ButtonClicked;             // Event definition

    public Form2()
    {
        InitializeComponent();
    }

    public void ReceiveMsgFromForm1(string input)
    {
        label1.Text = input;
    }

    public bool ReceiveMsgFromForm1withConfirm(string input)
    {
        label1.Text = input;
        return true;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        ButtonClicked("Message from Form 2");
    }
}

Solution

  • It is always the class (a form in this case) which raises an event (Invoke), that defines the event. Invoking, firing or raising an event is like broadcasting a message. This class is called the publisher. The event is therefore public in most cases.

    The other class attaching to this event with += is the subscriber. It is the receiver of this message.

    You somehow mixed up publisher and subscriber.

    An event is like an auto property but for a delegate. So,

    public event MessageEventHandler Message;
    

    defines an event Message. It is equivalent to

    private MessageEventHandler _messageEventHandler;
    public event MessageEventHandler Message
    {
       add 
       { 
           _messageEventHandler += value;
       } 
       remove 
       { 
          _messageEventHandler -= value;
       }
    }
    

    So, you don't need these explicit event handler field declarations. Also, it is not necessary to create the delegates explicitly with new. += EventHandlerMethod alone is just fine.


    If you want Form2 to handle an event from Form1, it would be better to pass Form1 as a constructor parameter to Form2. The event handler in Form2 then can be private. It is also up to Form2 to subscribe to the event or not. Controls are defining many events, but your form as receiver subscribes to only a few of them if any.

    Sender side:

    public class MessageEventArgs : EventArgs
    {
        public string Message { get; set; }
    }
    
    public delegate void MessageEventHandler(object sender, MessageEventArgs e);
    
    public partial class Form1 : Form
    {
        public event MessageEventHandler Message;
    
        ... constructor and other stuff not shown here.
    
        private void btnInitializeFrm2_Click(object sender, EventArgs e)
        {
            Form2 frm2 = new Form2(this);
            frm2.Show();
        }
    
        private void btnSendMessage_Click(object sender, EventArgs e)
        {
            // You don't care who is receiver. You might have many receivers or none.
            OnMessage($"Message from form 1 with index {i}");
        }
    
        private virtual void OnMessage(string message)
        {
            Message?.Invoke(new MessageEventArgs { Message = message });
        }
    }
    

    Subscriber:

    public partial class Form2 : Form
    {
        private Form1 _form1;
    
        public Form2(Form1 form1)
        {
            InitializeComponent();
            _form1 = form1;
            form1.Message += Form1_Message;
        }
    
        private void Form1_Message(object sender, MessageEventArgs e)
        {
            MessageBox.Show("Form2 got message: " + e.Message);
        }
    
        private void btnUnsubscribe_Click(object sender, EventArgs e)
        {
            _form1.Message -= Form1_Message;
        }
    }
    

    See also