在 C# 中的两个窗体之间进行通信

Posted

技术标签:

【中文标题】在 C# 中的两个窗体之间进行通信【英文标题】:Communicate between two windows forms in C# 【发布时间】:2009-11-03 06:08:26 【问题描述】:

我有两种形式,一种是主形式,另一种是选项形式。例如,假设用户在主窗体上单击我的菜单:Tools -> Options,这将导致显示我的选项窗体。

我的问题是如何将选项表单中的数据发送回主表单?我知道我可以使用属性,但我有很多选择,这似乎是一件乏味奇怪的事情。

那么最好的方法是什么?

【问题讨论】:

@Bob:您是否在设置选项后关闭选项表单并希望使用选项值打开主表单? 【参考方案1】:

Form1 触发 Form2 打开。 Form2 具有重载的构造函数,它将调用表单作为参数并提供对 Form2 成员的引用。这解决了通信问题。例如,我在 Form1 中将 Label 属性公开为 public,在 Form2 中进行了修改。

通过这种方法,您可以以不同的方式进行沟通。

Download Link for Sample Project

//您的 Form1

public partial class Form1 : Form

    public Form1()
    
        InitializeComponent();
    

    private void button1_Click(object sender, EventArgs e)
    
        Form2 frm = new Form2(this);
        frm.Show();
    

    public string LabelText
    
        get  return Lbl.Text; 
        set  Lbl.Text = value; 
    

//您的 Form2

public partial class Form2 : Form

    public Form2()
    
        InitializeComponent();
    

    private Form1 mainForm = null;
    public Form2(Form callingForm)
    
        mainForm = callingForm as Form1; 
        InitializeComponent();
    

    private void Form2_Load(object sender, EventArgs e)
    

    

    private void button1_Click(object sender, EventArgs e)
    
        this.mainForm.LabelText = txtMessage.Text;
    

(来源:ruchitsurati.net)

(来源:ruchitsurati.net)

【讨论】:

很棒的答案!这正是我正在寻找的!谢谢!我也喜欢 XP 上的黑色(它几乎让我想念 XP)。 这是 XP 的 Zune 主题。 :) 这会导致 Form1 和 Form2 紧密耦合,我想应该在这种情况下使用自定义事件。 Woah 多年的 Winforms 编程经验(作为学生)但我从未遇到过这个功能,实现起来非常有趣...... 但是...它仍然与 Form1 耦合...我可以只使用通用 Form (private Form mainForm = null;) (mainForm = callingForm as Form;)...我想要它完全从任何表单接收数据【参考方案2】:

在接受答案的 cmets 中,Neeraj Gulia 写道:

这导致表单 Form1 和 Form2 紧密耦合,我想应该在这种情况下使用自定义事件。

评论完全正确。接受的答案还不错;对于简单的程序,尤其是对于那些刚刚学习编程并试图让基本场景发挥作用的人来说,这是一个非常有用的示例,可以说明一对表单如何交互。

但是,确实可以并且应该避免示例引起的耦合,并且在特定示例中,事件将以通用、解耦的方式完成相同的事情。

这是一个示例,使用接受的答案的代码作为基线:

Form1.cs:

public partial class Form1 : Form

    public Form1()
    
        InitializeComponent();
    

    private void button1_Click(object sender, EventArgs e)
    
        Form2 frm = new Form2();

        frm.Button1Click += (s1, e1) => Lbl.Text = ((Form2)s1).Message;

        frm.Show();
    

上面的代码创建了一个Form2 的新实例,然后在显示它之前,为该表单的Button1Click 事件添加了一个事件处理程序。

请注意,表达式(s1, e1) => Lbl.Text = ((Form2)s1).Message 会被编译器自动转换为看起来类似于(但绝对不完全相同)的方法:

private void frm_Message(object s1, EventArgs e1)

    Lbl.Text = ((Form2)s1).Message;

实际上有很多方法/语法来实现和订阅事件处理程序。例如,使用上述匿名方法,您实际上不需要强制转换sender 参数;相反,您可以直接使用frm 局部变量:(s1, e1) => Lbl.Text = frm.Message

反之,您不需要使用匿名方法。实际上,您可以像上面显示的编译器生成的那样声明一个常规方法,然后将该方法订阅到事件:frm.Button1Click += frm_Message;(您当然使用名称frm_Message 作为该方法,就像在我上面的例子中)。

不管你如何做,当然你需要Form2 来实际实现Button1Click 事件。这很简单……

Form2.cs:

public partial class Form2 : Form

    public event EventHandler Button1Click;

    public string Message  get  return txtMessage.Text;  

    public Form2()
    
        InitializeComponent();
    

    private void button1_Click(object sender, EventArgs e)
    
        EventHandler handler = Button1Click;

        if (handler != null)
        
            handler(this, EventArgs.Empty);
        
    

除了事件之外,我还声明了一个属性 Message,它公开了 Text 属性(并且 Text 属性,实际上只是只读的) 的txtMessage 控件。这允许事件的订阅者获取该值并对其进行任何需要的操作。

请注意,该事件所做的只是提醒订阅者该按钮实际上已被点击。由订阅者决定如何解释或响应该事件(例如,通过检索 Message 属性的值并将其分配给某物)。

或者,您实际上可以通过声明一个新的 EventArgs 子类并将其用于事件来代替事件本身来传递文本:

public class MessageEventArgs : EventArgs

    public string Message  get; private set; 

    public MessageEventArgs(string message)
    
        Message = message;
    


public partial class Form2 : Form

    public event EventHandler<MessageEventArgs> Button1Click;

    public Form2()
    
        InitializeComponent();
    

    private void button1_Click(object sender, EventArgs e)
    
        EventHandler handler = Button1Click;

        if (handler != null)
        
            handler(this, new MessageEventArgs(txtMessage.Text));
        
    

然后订阅者可以直接从事件对象中检索消息值:

frm.Button1Click += (sender, e) => Lbl.Text = e.Message;

上述所有变体中的重要一点是,Form2 类在任何时候都不需要知道有关 Form1 的任何信息。让Form1 了解Form2 是不可避免的;毕竟,该对象将创建一个新的Form2 实例并使用它。但这种关系可能是不对称的,Form2 可供 任何 需要它提供的功能的对象使用。通过将功能公开为事件(并且可以选择使用属性),它使自己变得有用,而不会将其用处仅限于 Form1 类。

【讨论】:

【参考方案3】:

在这种情况下,最好的办法是拥有一些可通过IServiceProvider 访问的OptionsService 类/接口。

当某些事情发生变化时,只需添加一个事件,应用程序的其余部分就可以响应它。

【讨论】:

【参考方案4】:

有很多方法可以在两个表单之间执行通信。 其中一些已经向您解释过。我正在向你展示相反的方式。

假设您必须将一些设置从子表单更新到父表单。您也可以使用这两种方式:

    使用 System.Action(这里您只需将主窗体函数作为参数传递给子窗体,就像回调函数一样) OpenForms 方法(您直接调用其中一个打开的表单)

使用 System.Action

您可以将其视为传递给子表单的回调函数。

// -------- IN THE MAIN FORM --------

// CALLING THE CHILD FORM IN YOUR CODE LOOKS LIKE THIS
Options frmOptions = new Options(UpdateSettings);
frmOptions.Show();

// YOUR FUNCTION IN THE MAIN FORM TO BE EXECUTED
public void UpdateSettings(string data)

   // DO YOUR STUFF HERE


// -------- IN THE CHILD FORM --------

Action<string> UpdateSettings = null;

// IN THE CHILD FORMS CONSTRUCTOR
public Options(Action<string> UpdateSettings)

    InitializeComponent();
    this.UpdateSettings = UpdateSettings;


private void btnUpdate_Click(object sender, EventArgs e)

    // CALLING THE CALLBACK FUNCTION
    if (UpdateSettings != null)
        UpdateSettings("some data");

OpenForms 方法

这个方法很简单(2 行)。但仅适用于打开的表单。 您需要做的就是在您想要传递一些数据的地方添加这两行。

Main frmMain = (Main)Application.OpenForms["Main"];
frmMain.UpdateSettings("Some data");

【讨论】:

【参考方案5】:

属性是一个选项,共享静态类 - 另一个选项,事件 - 另一个选项...

【讨论】:

构造函数的参数,方法调用,... :-)【参考方案6】:

你可以试试AutoMapper。将您的选项保存在单独的类中,然后使用 AutoMapper 在类和表单之间传递数据。

【讨论】:

【参考方案7】:

创建一个类并将所有属性放在类中。在父类中创建一个属性并从您的子(选项)表单中设置它

【讨论】:

【参考方案8】:

你可以像这样在表格 B 中拥有一个函数:

public SettingsResults GetNewSettings()

    if(this.ShowDialog() == DialogResult.Ok)
    
         return new SettingsResult  ... ;
    
    else
    
         return null;
    

你可以这样称呼它:

...
using(var fb = new FormB())

    var s = fb.GetNewSettings();
    ...
    // Notify other parts of the application that settings have changed.

【讨论】:

【参考方案9】:

MVC、MVP、MVVM——对于那些承认自己想要教程的人来说,这有点矫枉过正。这些理论都有专门的课程。

正如已经发布的那样,传递一个对象可能是最简单的。如果将类视为对象(在这种意义上是可互换的)是新事物,那么您可能需要再花 2-4 周的时间来弄清楚属性和构造函数等。

无论如何,我都不是 C# 大师,但如果您想在两个表单之间传递值(也包括类/对象本身)更进一步,这些概念需要非常具体。根本不想在这里刻薄,这听起来就像您正在从 VB6(或任何具有全局变量的语言)之类的东西转向更有条理的东西。

最终,它会点击。

【讨论】:

【参考方案10】:

这可能稍微回避了您的问题,但我的设置对话框使用应用程序设置构造。 http://msdn.microsoft.com/en-us/library/k4s6c3a0.aspx

我找不到与我的做法相似的好例子(实际上是有一个实际的类+对象),但这涵盖了另一种做法:

Reading default application settings in C#

【讨论】:

这很好,但我对存储设置不感兴趣。我只是希望将这些设置中继到主窗体。【参考方案11】:

表单是一个类,就像任何其他类一样。将一些公共变量添加到您的表单类中,并在他们单击按钮关闭表单时设置它们(从技术上讲,他们只是隐藏它)。

一个 VB.NET 示例,但你会明白的 -

在您的 OptionsForm 类中:

Public Option1 as String = ""

等等。当他们点击“确定”按钮时设置它们。

所以在你的主表单中,当他们点击“选项”按钮时 - 你创建了你的选项表单:

OptionsForm.ShowDialog()

当它退出时,您会从表单上的公共变量中获取您的选项设置:

option1 = OptionsForm.Option1

等等

【讨论】:

【参考方案12】:

处理容器之间通信的最好方法是实现一个观察者类

观察者模式是一种软件设计模式,其中一个称为主体的对象维护其依赖项列表,称为观察者,并自动通知他们任何状态变化,通常通过调用他们的方法之一。 (***)

我这样做的方法是创建一个 Observer 类,并在其中编写如下内容:

1    public delegate void dlFuncToBeImplemented(string signal);
2    public static event dlFuncToBeImplemented OnFuncToBeImplemented;
3    public static void FuncToBeImplemented(string signal)
4    
5         OnFuncToBeImplemented(signal);
6    

基本上是这样:第一行说会有一个其他人将实现的功能

第二行是创建一个在委托函数调用时发生的事件

第三行是调用事件的函数的创建

所以在你的 UserControl 中,你应该添加这样的函数:

private void ObserverRegister()//will contain all observer function registration

    Observer.OnFuncToBeImplemented += Observer_OnFuncToBeImplemented;
    /*and more observer function registration............*/



void Observer_OnFuncToBeImplemented(string signal)//the function that will occur when  FuncToBeImplemented(signal) will call 

    MessageBox.Show("Signal "+signal+" received!", "Atention!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

在您的表单中,您应该执行以下操作:

public static int signal = 0;

public void button1_Click(object sender, EventArgs e)

      Observer.FuncToBeImplemented(signal);//will call the event in the user control

现在,您可以将此函数注册到一大堆其他控件和容器中,它们都会收到信号

我希望这会有所帮助:)

【讨论】:

以上是关于在 C# 中的两个窗体之间进行通信的主要内容,如果未能解决你的问题,请参考以下文章

在两个 C# exe 之间进行通信 [关闭]

C# WPF MVVM模式Prism框架下事件发布与订阅

C# WPF MVVM模式Caliburn.Micro框架下事件发布与订阅

c# 进程间的通信实现之一简单字符串收发

C# 委托和事件 实现窗体间的通信

如何使用 WCF 在 Windows 服务和 Windows 窗体之间进行通信?