C# 将委托传递给不同的形式

C# pass delegates to different forms


我有一个包含多个表单的 WinForms 应用程序。
我想要实现的是将事件处理程序从第二种形式传递到第三种形式,但我无法实现。我遇到了一个我不知道如何克服的铸造错误。 我将不胜感激帮助:

代码+下面的进一步解释:
这是应该发生的事情的粗略图像:


Form1 可以创建多个表单(它还包含我想要传递的方法)- 我可以在创建子表单时成功传递。
当我从 form2 中创建 form3 时,问题就开始了:我尝试传递事件处理程序,但我得到错误 CS0029/CS0030(转换错误) 我做错了什么以及如何解决?

编辑: 需要发生什么? -- Form3需要控制(传回数据)到Form1中放置的一个Gui控件

代码:

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();
    }
    

我收到错误 CS0029

如何将同一个委托传递给其他子表单(例如 frmAddDevice)?

我认为您不理解的主要概念是 delegateclassenumstruct 等“同一级别”。您需要在一些共享范围内声明它以使其以两种形式访问。

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
            };
        }
    }
}

或者,如果您想将其保留在表单中,则需要使用 class 名称引用它,就像使用类型一样。

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
            };
        }
    }
}

您的问题是如何 C# 将委托传递给不同的表单 以便您可以(例如)从其他表单 sendMessageToConsole 到您的 MainForm .在您的代码中,您声明这是问题所在:

// This is the issue (same for the placeholder)****
edit_device.sendMessageToConsole += sendMessageToConsole;

当我查看您的代码时,本质上您是在尝试实现您自己的 Event Pattern 版本。您的问题的一种解决方案是使用标准事件模式。然后 intellisense 将以标准方式识别您的自定义 event delegate

FIRST 你需要让delegate和继承的EventArgsclass之外你的 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;
        }
    }
}

您的 frmAddDevice(此处以最小格式显示)使用 event 关键字声明委托。您的其他表格 frmEditDBs 做完全相同的事情。

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'"));
    }
}

MainForm 代码中的一个按钮创建了一个新的 frmAddDevice,如下所示:

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);
}

如果您执行这些操作,您将实现您的代码试图执行的 sendMessageToConsole 的功能。从 GitHub.

下载我的示例进行试用

如前所述,您的“代表”应该在项目的命名空间中进行一般声明,而不是在特定的 class 中声明,以便它们在整个应用程序中可见。为此,可以在您的项目中为“MyDelegates”创建一个单独的文件,其内容可能类似于:

使用 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);
}

现在,在您要定义要做什么的表单 1 中,根据适当匹配的签名进行定义

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();
        }
    }
}

首先是 form3,因为它只有直接的事件处理程序,但是您可以在 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();
        }
    }
}

现在,您的表格 2 有点不同。由于您想让 form2 可以使用相同的事件处理程序调用 form3,只需将这些事件处理程序作为参数添加到构造函数 class。然后你可以以那种形式保存它们,但同时,self-register 它们就像在第二个按钮单击事件的 var f2b = new Form2 中一样。然后当 form2 需要调用 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();
        }
    }
}

请记住,参数几乎可以是任何东西,您可以像其他任何东西一样将变量存储在 属性 中......只要它与相应的类型相匹配。

然后,从 form3 开始,任一实例都将调用回根实例方法。