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

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

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

那么最好的方法是什么?

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

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

下载示例项目的链接

// 你的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; } } 

alt text http://sofzh.miximages.com/c%23/frm1.png

替代文字http://sofzh.miximages.com/c%23/frm2.png

在接受回答的评论中,Neeraj Gulia写道:

这导致Form1和Form2forms的紧密耦合,我想相反应该为这种场景使用自定义事件。

评论是完全正确的。 接受的答案还不错; 对于简单的程序,特别是对于那些只是学习编程并试图让基本场景工作的人来说,它是一对表格如何交互的一个非常有用的例子。

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

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

Form1.cs中:

 public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { Form2 frm = new Form2(); frm.Button1Click += (sender, e) => Lbl.Text = ((Form2)sender).Message; frm.Show(); } } 

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

请注意,表达式(sender, e) => Lbl.Text = ((Form2)sender).Message由编译器自动转换为类似于(但绝对不完全相同)的方法:

 private void frm_Message(object sender, EventArgs e) { Lbl.Text = ((Form2)sender).Message; } 

实际上有许多方法/语法可以实现和订阅事件处理程序。 例如,使用如上所述的匿名方法,您实际上不需要转换sender参数; 相反,你可以直接使用frm局部变量:( (sender, e) => 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 ,它暴露了txtMessage控件的Text属性(并且只有 Text属性,实际上只作为只读)。 这允许事件的订阅者获得值并且用它做任何需要的事情。

请注意,事件所做的只是提醒订户该按钮实际上已被点击。 由订户决定如何解释或响应该事件(例如,通过检索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 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) => e.Message; 

所有上述变化中重要的一点是, Form2类在任何时候都不需要知道有关Form1任何信息。 Form1知道Form2是不可避免的; 毕竟,这是将创建一个新的Form2实例并使用它的对象。 但是这种关系可以是不对称的, Form2可以被任何需要它提供的function的对象使用。 通过将function公开为事件(以及可选地具有属性),它使其自身有用而不仅限制其对Form1类的有用性。

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

只需在更改内容时添加事件,应用程序的其余部分就可以对其进行响应。

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

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

创建一个类并将所有属性放在类中。在父类中创建一个属性,并从子(选项)窗体中设置它

您可以在表单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. } 

MVC,MVP,MVVM – 对于一个肯定会说他们想要教程的人有点矫枉过正。 这些理论都有专门针对他们的全部课程。

如上所述,传递一个对象可能是最简单的。 如果将一个类视为一个对象(在这个意义上是可互换的)是新的,那么你可能需要花费另外2-4周来计算属性和构造函数等。

无论如何我都不是C#大师,但是如果你想要在两个表单之间传递值(也就是它们自己的类/对象)之外还要远远超过这些概念,那么这些概念需要非常具体。 在这里根本没有意思,它只是听起来像是从VB6(或任何带有全局变量的语言)转变为更加结构化的东西。

最终,它会点击。

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

假设您必须将子表单中的某些设置更新为父表单。 您也可以使用以下两种方式:

  1. 使用System.Action(这里你只需将主窗体函数作为参数传递给子窗体,就像回调函数一样)
  2. 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 UpdateSettings = null; // IN THE CHILD FORMS CONSTRUCTOR public Options(Action 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"); 

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

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

在C#中读取默认应用程序设置

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

一个VB.NET的例子,但你会明白 –

在您的OptionsForm类中:

 Public Option1 as String = "" 

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

因此,在您的主要表单中,当他们点击“选项”按钮时 – 您创建选项表单:

 OptionsForm.ShowDialog() 

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

 option1 = OptionsForm.Option1 

等等