在 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# 中的两个窗体之间进行通信的主要内容,如果未能解决你的问题,请参考以下文章