Introduction

Communication between multiple forms can be accomplished several ways, from exposing properties and/or events with public modifiers rather than the default of private modifiers which is okay for the occasional lobbyist while this is bad practice when writing professional solution in Visual Studio. The intent in this article is to provide an easy to use set of classes to allow information to be passed between two or more forms in a Visual Studio C# WinForm project. 

Important

  • If the intent is to create more than simple communication, passing of strings or complex objects between form, instead require a more robust tool e.g. chat server, chat client look at using asynchronous socket path.
  • For novice developers, download the source for this article, build the solution and then while reading the article run the code samples which will provide a better learning experience then just reading this article.
  • DO NOT copy and paste code provided into a project before first having a decent understanding of how the code works in the provided samples as those developer with little or no time with generics and interfaces can endup in frustration implementing this code. 

Passing simple strings between two forms

The following examples will build upon each other.

Example 1

The task is to be able to pass a string between any open form. First an Interface is required which defines the events needed to pass strings between open forms.

The following Interface defines an event where a single parameter of type string. An Interface allows the code which follows to use a different Interface implementation for different project requirements.

public interface IMessageListener
{
    void OnListen(string Message);
}

The following class provides methods to keep a list of listeners which can allow forms in a project to send and receive string data.  When a child form is open and closed several times through the life of an application RemoveListener should be used, otherwise the current application needs to perform more work then needed plus its possible for messages to not be seen in a listener.
using System.Collections.ObjectModel;
using BroadcastListener.Interfaces;
 
namespace BroadcastListener.Classes
{
    public class Broadcaster
    {
        private readonly Collection<IMessageListener> _listeners =
            new Collection<IMessageListener>();
 
        public void Broadcast(string Message)
        {
            foreach (IMessageListener listener in _listeners)
            {
                listener.OnListen(Message);
            }
        }
 
        /// <summary>
        /// Add a Listener to the Collection of Listeners
        /// </summary>
        /// <param name="Listener"></param>
        public void AddListener(IMessageListener Listener)
        {
            _listeners.Add(Listener);
        }
        /// <summary>
        /// Remove a Listener from the collection
        /// </summary>
        /// <param name="Listener"></param>
        public void RemoveListener(IMessageListener Listener)
        {
 
            for (int index = 0; index < _listeners.Count; index++)
            {
                if (_listeners[index].Equals(Listener))
                {
                    _listeners.Remove(_listeners[index]);
                }
            }
        }
    }
}

The following factory class provides access to the Broadcaster above.
public static class Factory
{
    private static Broadcaster _broadcaster;
 
    public static Broadcaster Broadcaster()
    {
        return _broadcaster ?? ( _broadcaster = new Broadcaster() );
    }
}

For any window/form which will participate with sending/receiving string implement IMessageListener. In the following Form1 is the main form and Form2 a child form.

public partial class Form1 : Form , IMessageListener
.
.
public partial class Form2 : Form, IMessageListener

Since the factory class is static use static using statements where 
using static YourNamespace.Classes.Factory;
In the form constructor for forms which will participate with sending or receiving a string add. 

Broadcaster().AddListener(this);

Once adding the implementation above Visual Studio will complain, allow Visual Studio to implement OnListen. In OnListen in Form1 clear out the not implemented code. In form2, 

The following is taken directly from one of the attached code samples. When OnListen is triggered the string passed will be set to an existing TextBox.
public void OnListen(string Message)
{
    FromForm2TextBox.Text = Message;
}

To send a string use
Broadcaster().Broadcast(SomeTextBox.Text);

Any forms which are participating in broadcasting and have code in the OnListener event will have access to the string sent. There is an inherent issue, suppose form1 sends a string intended for form2? There is no way to know were the string came from. This disallows passing information back and forth between forms and know if the string was intended for that form (the current form). Example 2 will build on this example to allow a form to know the caller.

Example 2


This example builds on the latter where there was no indicator on knowing were a message came from. In the last example the Interface had one event with a string parameter. This Interface requires text and a form.

public interface IMessageListener1
{
    void OnListen(string Message, Form Type);
}

Broadcaster class changes using the above Interface. Besides changing the Interface Broadcast method needs the form type defined in IMessageListener1.
public class Broadcaster
{
    private readonly Collection<IMessageListener1> _listeners =
        new Collection<IMessageListener1>();
 
    /// <summary>
    /// Send message
    /// </summary>
    /// <param name="message">Message</param>
    /// <param name="sender"></param>
    /// <remarks></remarks>
    [DebuggerStepThrough()]
    public void Broadcast(string message, Form sender)
    {
        foreach (IMessageListener1 listener in _listeners)
        {
            listener.OnListen(message, sender);
        }
    }
    
    /// <summary>
    /// Add a Listener to the Collection of Listeners
    /// </summary>
    /// <param name="listener"></param>
    public void AddListener(IMessageListener1 listener)
    {
        _listeners.Add(listener);
    }
    /// <summary>
    /// Remove a Listener from the collection
    /// </summary>
    /// <param name="listener"></param>
    public void RemoveListener(IMessageListener1 listener)
    {
 
        for (int index = 0; index < _listeners.Count; index++)
        {
            if ( _listeners[index].Equals(listener) )
            {
                _listeners.Remove(_listeners[index]);
            }
        }
    }
}

Implementing the new Interface for each form
public partial class SomeForm : Form, IMessageListener1

In OnListen determine if the message is worth looking at. In the following example the form is a child form called from the main form. If the message/string came from Form1 then assign the string to a TextBox, otherwise the string came from the current form and not intended for this form.
public void OnListen(string Message, Form sender)
{
    if (sender is Form1)
    {
        StringFromForm1TextBox.Text = Message;
    }
}

Likewise, in the main form only react to strings from the child form.
public void OnListen(string message, Form sender)
{
 
    if (sender is Form2)
    {
        FromForm2TextBox.Text = message;
    }
 
}

To send a string from a form where the second parameter is the current form.
Broadcaster().Broadcast(SimpleMessageToChildTextBox.Text, this);

Passing class instances between two forms.

In the following example a person object will be used for passing to another form.
public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public override string ToString()
    {
        return $"{FirstName} {LastName}";
    }
}

The Interface from the last example first parameter was a string, in this example a person is the first parameter, second parameter remain the same.
public interface IMessageListener
{
    void OnListen(Person person, Form form);
}

No changes to the Factory class
public static class Factory
{
    private static Broadcaster _broadcaster;
 
    public static Broadcaster Broadcaster()
    {
        return _broadcaster ?? ( _broadcaster = new Broadcaster() );
    }
}

The Broadcaster class uses the same Interface name but with the first parameter a Person, not a string, this is easy enough to change.



In this example form1 (main form) displays a child form modally, each time the add button is clicked or pressed values of two TextBox controls are broadcasted to all listeners,
Broadcaster().Broadcast(new Person()
{
    FirstName = FirstNameTextBox.Text,
    LastName = LastNameTextBox.Text,
    Id = -1
}, this);

In the child form OnListen event if the form is the main form assign two TextBox.Text properties to values passed.
public void OnListen(Person person, Form form)
{
    if (form is Form1)
    {
        FirstNameTextBox.Text = person.FirstName;
        LastNameTextBox.Text = person.LastName;
    }
}

Child form created, broadcast a person then shows the child form.
private void AddPeopleButton_Click(object sender, EventArgs e)
{
    var form = new AddPeopleForm();
 
    Broadcaster().Broadcast(new Person()
    {
        FirstName = "Jane",
        LastName = "Adams",
        Id = -1
    }, this);
 
    form.ShowDialog();
}

Custom controls

Several custom controls are used in this code sample worth mentioning, ObserableButton provides events via implementing
IObservable<string>
With its own event for passing, in this case string information via Notify event.
public void Notify(string Message)
{
 
    foreach (IObserver<string> observer in _observers)
    {
        observer.OnNext(Message);
    }
 
}
Which works by simply passing a string to Notify of an instance of this button. For this code sample a custom Textbox, ObserverTextBox implements the same interface as the button above.
public class ObserverTextBox : TextBox, IObserver<string>
{
    private IDisposable _unsubscriber;
 
    void IObserver<string>.OnCompleted()
    {
    }
 
    void IObserver<string>.OnError(Exception error)
    {
 
    }
    void IObserver<string>.OnNext(string value)
    {
        Text = value;
        Refresh();
    }
 
    public virtual void Subscribe(IObservable<string> provider)
    {
        if (provider != null)
        {
            _unsubscriber = provider.Subscribe(this);
        }
    }
    public virtual void Unsubscribe()
    {
        _unsubscriber.Dispose();
    }
}

Rather than passing a string around in this code sample a Reset method is used to clear TextBoxes which have subscribed to the button above.
FirstNameTextBox.Subscribe(AddButton);
LastNameTextBox.Subscribe(AddButton);

When its appropriate use Reset to clear those TextBoxes.
AddButton.Reset();

See also

Event handling in an MVVM WPF application
Windows forms custom delegates and events
Simple Multi-User TCP/IP Client/Server using TAP

Summary

Code has been presented to provide an easy way to pass information between two or more forms in a single project starting off with the very basics and concluding with an example using a class object. Other options include delegate/events and sockets (see the following included). There is not one correct path for every task requiring sharing information between forms, have a understanding of what has been presented along with sockets, delegates and events. When transitioning to WPF working with information between windows has classes and Interfaces e.g. DependencyProperty class and ObservableCollection<T> class.

Source code

GitHub repository (include a single visual basic code sample along with three C# code samples)