Home > Net >  C# pass delegates to different forms
C# pass delegates to different forms

Time:06-04


i have a WinForms app that consists of several forms.
What I'm trying to achieve is to pass an event handler from a second form, to a third one, but i cannot achieve that. i get a casting error which i can't figure out how to overcome. i would appreciate the help:

code further explanation below:
This is a rough image of what is supposed to happen:
what i try doing

Form1 can create several forms (it also holds the methods that i want to pass) - which i can pass successfully on sub form creation.
the problem starts when i create form3 from within form2: i try to pass the event handler, but i get Error CS0029/CS0030 (casting errors) what am i doing wrong and how to fix it?

EDIT: what needs to happen? -- Form3 needs to control (send back data) to a Gui control placed in Form1

Code:

Form1:

    public delegate void sendMessageToConsoleDelegate(string value);
    public sendMessageToConsoleDelegate sendMessageToConsoleCallback;

    public delegate void SetPlaceHolderDelegate(TextBox tb);
    public SetPlaceHolderDelegate SetPlaceHolderCallback;
    
     private void SetPlaceHolder(TextBox tb)
    {
        if (!tb.InvokeRequired)
        {
            if (!tb.Focused)
            {
                if (string.IsNullOrWhiteSpace(tb.Text))
                    tb.Text = tb.Tag.ToString();
                return;
            }
            if (tb.Text == tb.Tag.ToString())
                tb.Text = "";
            return;
        }
        SetPlaceHolderDelegate call = new SetPlaceHolderDelegate(SetPlaceHolder);
        tb.BeginInvoke(call, tb);
    }

    private void SendMessageToConsole(string msg)
    {
        if (!textBoxConsole.InvokeRequired)
        {
            textBoxConsole.AppendText(msg);
            return;
        }
        sendMessageToConsoleDelegate call = new sendMessageToConsoleDelegate(SendMessageToConsole);
        textBoxConsole.BeginInvoke(call, msg);
    }
    
    
    
    private void AddNewDeviceForm()
    {
        frmAddDevice add_device = new frmAddDevice(devicesDBPath);
        add_device.sendMessageToConsole  = SendMessageToConsole;
        add_device.Show();
    }

    private void StartEdit()
    {
        frmEditDBs editdb = new frmEditDBs(devicesDBPath, commandsDBPath);
        editdb.sendMessageToConsole  = SendMessageToConsole;
        editdb.SetPlaceHolder  = SetPlaceHolder;
        editdb.Show();
    }

Form2 (frmEditDBs)

    public delegate void EventHandler_sendMessageToConsole(string msg);
    public event EventHandler_sendMessageToConsole sendMessageToConsole = delegate { };

    public delegate void EventHandler_SetPlaceHolder(TextBox tb);
    public event EventHandler_SetPlaceHolder SetPlaceHolder = delegate { };
    
    
    private void EditDevice()
    {
        frmAddDevice edit_device = new frmAddDevice(devicesDBpath, current_device);
        edit_device.sendMessageToConsole  = sendMessageToConsole; ****<== This is the issue (same for the placeholder)****
        edit_device.Show();
    }
    

i get error enter image description here

FIRST you need to make the delegate and the inherited EventArgs class outside of your MainForm class:

namespace pass_delegates
{
    public partial class MainForm : Form
    {
    }

    // Make sure these are outside of any other class.
    public delegate void SendMessageToConsoleEventHandler(object sender, SendMessageToConsoleEventArgs e);
    public class SendMessageToConsoleEventArgs : EventArgs
    {
        public string Message { get; }
        public SendMessageToConsoleEventArgs(string message)
        {
            Message = message;
        }
    }
}

Your frmAddDevice (shown here in minimal format) declares the delegate using the event keyword. Your other form frmEditDBs does exactly the same thing.

public partial class frmAddDevice : Form
{
    public event SendMessageToConsoleEventHandler SendMessageToConsole;

    public frmAddDevice(string devicesDBpath)
    {
        InitializeComponent();
    }

    protected virtual void OnSendMessageToConsole(SendMessageToConsoleEventArgs e)
    {
        SendMessageToConsole?.Invoke(this, e);
    }

    // Clicking the button will call this as a test.
    private void btnSendTestMessage_Click(object sender, EventArgs e)
    {
        OnSendMessageToConsole(new SendMessageToConsoleEventArgs("Message received from 'Add Device Form'"));
    }
}

A button in the MainForm code creates a new frmAddDevice like this:

frmAddDevice frmAddDevice = null;
// This handler in the Main Form creates the frmAddDevice form
private void btnFrmAddDevice_Click(object sender, EventArgs e)
{
    if (frmAddDevice == null)
    {
        frmAddDevice = new frmAddDevice(devicesDBpath: "Some path");
        // This was the problem. Not anymore ****
        frmAddDevice.SendMessageToConsole  = outputMessageToConsole;
    }
    frmAddDevice.Show();
}

private void outputMessageToConsole(object sender, SendMessageToConsoleEventArgs e)
{
    textBoxConsole.AppendText(e.Message   Environment.NewLine);
}

If you do these things, you will achieve the functionality of sendMessageToConsole that your code is attempting to do. Try it out by downloading my sample from Graphic showing the outcome

CodePudding user response:

I think the main concept you don't understand is that delegate is "same level" as class, enum, struct etc. You need to declare it in some shared scope to make it accessible in both forms.

namespace ConsoleApp6
{
    public delegate void TestDelegate();

    public class ClassA
    {
        public TestDelegate delegateA;
    }

    public class ClassB
    {
        public TestDelegate delegateB;
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            TestDelegate del = () => { };

            var classA = new ClassA()
            {
                delegateA = del,
            };

            var classB = new ClassB()
            {
                delegateB = classA.delegateA
            };
        }
    }
}

Or, if you want to keep it inside of the form, you need reference it by a class name the same way you would do with a type.

namespace ConsoleApp6
{

    public class ClassA
    {
        public delegate void TestDelegate();

        public TestDelegate delegateA;
    }

    public class ClassB
    {
        public ClassA.TestDelegate delegateB;
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            ClassA.TestDelegate del = () => { };

            var classA = new ClassA()
            {
                delegateA = del,
            };

            var classB = new ClassB()
            {
                delegateB = classA.delegateA
            };
        }
    }
}

CodePudding user response:

As was described previously, your "delegates" should be declared generically at the namespace of your project, not within a specific class so they are visible throughout your app. To do so, maybe make a separate file in your project for "MyDelegates" and may look something like:

using System.Windows.Forms;

namespace WinHelp1
{
    // Create your own delegates outside of your classes that need to be publicly 
    // visible within your app or even protected if so needed.
    public delegate void EventHandler_SendMessageToConsole(string msg);
    public delegate void EventHandler_SetPlaceHolder(TextBox tb);
}

Now, in your form 1 that you want to define WHAT to do, do so based on the signatures matching appropriately

using System.Windows.Forms;

namespace WinHelp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public void DoThisForConsole(string msg)
        {
            // whatever to do with string
        }

        public void DoThisForTextBox(TextBox tb)
        {
            // whatever to do with textbox
        }

        private void Btn2_Click(object sender, System.EventArgs e)
        {
            var f2 = new Form2();
            f2.SendMessageToConsole  = DoThisForConsole;
            f2.SetPlaceHolder  = DoThisForTextBox;
            f2.ShowDialog();

            // OR, if using the PARAMETERIZED for pass-through to call
            // when form2 calls form 3
            var f2b = new Form2( DoThisForConsole, DoThisForTextBox );
            f2b.ShowDialog();
        }

        private void Btn3_Click(object sender, System.EventArgs e)
        {
            var f3 = new Form3();
            f3.SendMessageToConsole  = DoThisForConsole;
            f3.SetPlaceHolder  = DoThisForTextBox;
            f3.ShowDialog();
        }
    }
}

First, form3 since that will just have the direct event handlers, and you can invoke however within form 3

using System.Windows.Forms;

namespace WinHelp1
{
    public partial class Form3 : Form
    {
        // now, for each form you want to USE them on...
        public event EventHandler_SendMessageToConsole SendMessageToConsole;
        public event EventHandler_SetPlaceHolder SetPlaceHolder;

        public Form3()
        {
            InitializeComponent();
        }
    }
}

Now, in your form 2, is a bit different. Since you want to make available for form2 to call form3 with the same event handler, just add those event handlers as parameters to the constructor class. Then you can preserve them in that form, but at the same time, self-register them as in the var f2b = new Form2 of the second button click event. Then use those preserved values when form2 needs to call form3

using System.Windows.Forms;

namespace WinHelp1
{

    public partial class Form2 : Form
    {
        // now, for each form you want to USE them on...
        public event EventHandler_SendMessageToConsole SendMessageToConsole;
        public event EventHandler_SetPlaceHolder SetPlaceHolder;

        // now, for each form you want to USE them on...
        public EventHandler_SendMessageToConsole passThroughForMessage;
        public EventHandler_SetPlaceHolder passThroughForTextBox;

        public Form2()
        {
            InitializeComponent();
        }

        public Form2(EventHandler_SendMessageToConsole forSendMsg, EventHandler_SetPlaceHolder forPlaceHolder ) : this()
        {
            // preserve into properties in-case you need to call form 3
            passThroughForMessage = forSendMsg;
            passThroughForTextBox = forPlaceHolder;

            // and the constructor can auto-set for itself so IT can notify as well
            if( forSendMsg != null )
                SendMessageToConsole  = forSendMsg;

            if( forPlaceHolder != null )
                SetPlaceHolder  = forPlaceHolder;
        }

        private void Btn3_Click(object sender, System.EventArgs e)
        {
            var f3 = new Form3();

            // and the constructor can auto-set for itself so IT can notify as well
            if (passThroughForMessage != null)
                f3.SendMessageToConsole  = passThroughForMessage;

            if (passThroughForTextBox != null)
                f3.SetPlaceHolder  = passThroughForTextBox;

            f3.ShowDialog();
        }
    }
}

Remember, parameters can be practically anything, and you can have a variable stored in a property just like anything else... as long as it matches the respective type.

Then, from form3, either instance will invoke back to whatever the root instance method may be.

  • Related