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

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

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

那么最好的方法是什么?

【问题讨论】:

  • @Bob:您是否在设置选项后关闭选项表单并希望使用选项值打开主表单?

标签: c# winforms properties


【解决方案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】:

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

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

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

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

                  我希望这会有所帮助:)

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多