.NET 4.x
Как передавать данные между формами
06.04.2012, 05:44. Показов 68672. Ответов 4
Hi!
Написал по сабжу статью (первоначально — себе в блог), но решил выложить тут. Полезнее будет, может кому и пригодится.
В процессе изучения C# вообще и WinForms в частности, у многих неофитов возникает вполне закономерный вопрос — а как передавать данные (в общем — объекты, но для начала хотя бы просто строки/числа). Кроме того, данные порой нужно передавать не только из основной формы в дочернюю, но и в обратном направлении. Для каждого из этих действий есть несколько способов реализации, и применение каждого из них зависит от контекста задачи, а также — от стиля и опыта программиста. Как правило, программисты выбирают себе несколько способов, которые используют в своих проектах. Я постарался в данной статье привести все известные мне способы, а так же их комбинации. Статья логически разделена на две части — прямая передача данных (из основной формы в дочернюю) и обратная.
Задача 1: Передать текстовую строку из основной формы в дочернюю
Реализация 1: Передать через конструктор дочерней формы
Самый простой способ. Класс дочерней формы конструируется таким образом, чтобы конструктор (или одна из его перегрузок) класса принимал в качестве аргумента или аргументов некие данные. Способ удобен тем, что в дочернюю форму можно передать практически неограниченное количество данных фактически любого типа. Неудобен он тем, что класс дочерней формы в этом случае становится слишком узкоспециализированным. При разработке небольших проектов это не почувствуется, но если вы возьметесь за масштабное модульное бизнес-приложение, сразу поймете всю узкость данного подхода. Но, тем не менее, не рассмотреть его было бы несправедливо.
Листинг 1.1.1. Основная форма:
C# | ||
|
Листинг 1.1.2. Дочерняя форма:
C# | ||
|
Думаю, все понятно и без дальнейших комментариев
Реализация 2: Передать через public-переменную или свойство класса дочерней формы.
Способ чуть посложнее. Потребуется создать в классе дочерней формы дополнительную переменную или свойство (в данном случае — это не важно), и обработать событие Load дочерней формы.
Листинг 1.2.1. Основная форма:
C# | ||
|
Листинг 1.2.2. Дочерняя форма:
C# | ||
|
Реализация 3: Передача данных через свойство/переменную статического класса.
Суть способа в следующем: использовать для временного буфера свойство или переменную статического класса. Данный способ несколько более универсальный. Хотя бы тем, что он не требует специализации класса дочерней формы, т.е. нам не придется добавлять в класс дочерней формы дополнительные свойства или переменные. Только обработать событие Load формы.
Листинг 1.3.1. Статический класс:
C# | ||
|
Листинг 1.3.2. Основная форма:
C# | ||
|
Листинг 1.3.3. Дочерняя форма:
C# | ||
|
Реализация 4: Запись данных напрямую в TextBox дочерней формы через обработку события Load анонимным методом.
Не самый лучший вариант, попахивающий карри и индийскими слонами, но для полноты картины продемонстрирую и его. Суть способа в том, что в основной форме при обработке события Click на кнопке с помощью анонимного метода подписаться на событие Load дочерней формы и задать для этого события обработчик. А в обработчике уже производить присвоение свойству Text текстбокса дочерней формы каких-либо значений. Текстбоксу дочерней формы в этом случае должен быть присвоен модификатор public.
Листинг 1.4.1 Основная форма:
C# | ||
|
Листинг 1.4.2. Дочерняя форма:
C# | ||
|
Задача 2. Передать данные из дочерней формы в основную
Реализация 1. Через статический класс. Тут, в общем то, все достаточно просто и похоже на подобную реализацию выше. Но есть и пара нюансов.
Поскольку по умолчанию основная форма «не знает», когда из дочерней в переменную статического класса будет записано значение, встает проблема — обновить текстбокс основной формы именно тогда, когда в статический класс будут внесены данные. В самом первом приближении это возможно при выполнении следующего условия — дочерняя форма открыта как диалог (т.е. управление передается на дочернюю форму при ее закрытии), а обновление текстбокса основной формы происходит после метода открытия дочерней формы.
Листинг 2.1.1. Статический класс
C# | ||
|
Листинг 2.1.2. Основная форма
C# | ||
|
Листинг 2.1.3. Дочерняя форма
C# | ||
|
Реализация 2. Через событие закрытия окна.
При вызове дочерней формы мы можем с помощью анонимного метода подписаться на события вызываемой формы. Например, если мы подпишемся на событие закрытия окна, то сможем выполнить некие действия, когда дочерняя форма инициирует это событие (т.е. начнет закрываться).
Листинг 2.2.1. Основная форма
C# | ||
|
Листинг 2.2.2. Дочерняя форма
C# | ||
|
Кроме событий формы, можно подписаться на события любых public-компонентов. Но не рекомендую этого делать — это, конечно, легко и просто, но… некрасиво, что ли.
Реализация 3. Через события статического класса.
Опять задействуем посредника в виде статического класса. Однако применим на этот раз иной подход. В основной форме подпишемся на событие ValueChanged статического свойства DataBuffer. Но, поскольку свойство это «из коробки» не имеет подобных событий, его придется создать.
Листинг 2.3.1. Статический класс
C# | ||
|
Листинг 2.3.2. Основная форма
C# | ||
|
Листинг 2.3.3. Дочерняя форма
C# | ||
|
Если открывать дочернюю форму как диалог, данные в основной форме обновятся после закрытия дочерней. В противном случае данные обновятся сразу же. Подобный подход удобен, когда нужно организовать одновременный обмен данными между двумя формами. Причем, в отличие от большинства других вариантов, такой способ обмена доступен и для равнозначных форм (запущенных от одного, не участвующего в обмене родителя — например, MDI-окна). Достаточно просто заставить каждую форму слушать определенное событие статического класса-буфера.
На данный момент вроде как все. Скорее всего что-то забыл, поэтому к критике в комментариях буду прислушиваться особенно внимательно.
Best Regards, Aexx
6
Многие, кто начинал изучение WinForm приложений, сталкивались с проблемой обмена данными между окнами программы. Обмен данными между формами сводится к трем основным принципам: передача параметров в форму, получение данных из формы, изменение значений другой формы. Начнем по порядку.
1) Передача параметров в форму. Любой класс, должен иметь конструктор, и WinForm в этом случае не исключение. А следовательно очевидным является тот факт, что передача данных необходимых для инициализации формы необходимо проводить именно через конструктор формы. Приведем пример.
Создадим WinForm и перейдем к коду. Наблюдаем следующую картину:
public partial class Form1 : Form
{
public Form1() // <-- Конструктор формы по умолчанию
{
InitializeComponent();
} }
Допустим на данной форме размещен элемент textBox в который мы хотим установить значение, при открытии нашей формы. Тогда модифицируем наш код следующим образом:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public Form1(string text) // <-- Новый конструктор формы
{
InitializeComponent();
textBox.Text = text;
}
}
Важно: Все действия, выполняемые с объектами формы должны быть произведены после функции InitializeComponent()!
2-3) По сути задача получения данных из формы и изменение данных в форме сводистя к одной задаче. Пусть у нас есть две формы Form1 и Form2. В первой форме у нас есть три кнопки: open, read, write . Первая откроет вторую форму, в которой есть элемент textBox, Вторая покажет сообщение с текстом, введенным в textBox формы номер два, третья очистит textBox из Form2. Имеем:
public partial class Form1 : Form
{
private Form2 F2 = new Form2();
//<--Объявляем форму два как элемент класса формы один
public Form1()
{
InitializeComponent();
}
private void open_Click(object sender, EventArgs e)
{
F2.ShowDialog();
}
private void read_Click(object sender, EventArgs e)
{
MessageBox.Show(F2.textBoxValue);
}
private void write_Click(object sender, EventArgs e)
{
F2.textBoxValue = String.Empty;
}
}
public partial class Form2 : Form
{
public string textBoxValue
//<--Данная конструкция позволяет получить доступ
//к private элементам формы
{
get { return textBox.Text; }
set { textBox.Text = value; }
}
public Form2()
{
InitializeComponent();
}
}
Вот и все. Оказалось не все так сложно 😉
In any but the smallest of WinForms apps, we’ll have multiple Forms interacting with one another. And while every app we write will be different, there’s really only a couple of ways for two Forms to pass data back and forth.
If you’d like to follow along while you read, the code in this article is available on GitHub.
Let’s take a closer look.
Child Form Pushes Data Back to Parent
When one Form displays another Form to collect user input, or to display a database record for editing, the changes a user makes need to make it back to the first Form or risk being lost.
One way to pass the data is to push it back to the parent Form from the child Form. A Form
is just another class, and in order to call methods or change properties of any class, you need to have a reference to an instance of it. There’s some caveats to doing it this way, which I’ll point out in a minute.
Let’s assume we have a simple app with just two Forms — a parent and a child. Here’s the code behind ParentForm
:
private void btnGetUserInput_Click(object sender, EventArgs e)
{
using (var childForm = new ChildForm(this))
{
childForm.ShowDialog();
}
}
public void SetName(string name)
{
lblName.Text = name;
}
public int Age
{
set { lblAge.Text = value.ToString(); }
}
It displays ChildForm
on a button push, sending it a reference to itself. It also defines a method and a property, made public so the child Form can access them.
Now let’s look at the code behind ChildForm
:
private readonly ParentForm parentForm;
public ChildForm(ParentForm form)
{
InitializeComponent();
parentForm = form;
}
private void btnSaveInput_Click(object sender, EventArgs e)
{
parentForm.SetName(txtName.Text);
parentForm.Age = int.TryParse(txtAge.Text, out var age) ? age : 0;
}
In the constructor, it accepts the reference from ParentForm
and stores it. When the «Save Input» button is pushed, it uses that reference to call the method and property on ParentForm
, passing the data back to it.
So what’s so bad about this code?
Problems With This Approach
I see two issues with this code, and the first is reusability. Imagine that next week, we want to use ChildForm
from another, new Form. ChildForm
will need to be reworked because it currently has a constructor that expects to be passed ParentForm
. We’ll have to add more code and more complexity to ChildForm
, so it’s not easily reusable.
The second issue is that ChildForm
has knowledge it doesn’t need. There is no reason for ChildForm
to know about other forms, user controls, class libraries, etc that could potentially use it. In general, a thing being called should know very little (or nothing) about the thing calling it.
Imagine as our app grows, we have two Forms calling ChildForm
to get a name and age. One calls a new instance of ChildForm
, passing an instance of itself, and has a public EmployeeName
property that ChildForm
can call:
private void btnGetUserInput_Click(object sender, EventArgs e)
{
using (var childForm = new ChildForm(this))
{
childForm.ShowDialog();
}
}
public string EmployeeName
{
set { lblName.Text = value; }
}
The other does the same, except it has a public SetStudentName()
method for ChildForm
to call instead:
private void btnGetUserInput_Click(object sender, EventArgs e)
{
using (var childForm = new ChildForm(this))
{
childForm.ShowDialog();
}
}
public void SetStudentName(string name)
{
lblStudentName.Text = name;
}
The logic in ChildForm
increases in complexity with each new object calling it:
private readonly ParentForm1 parentForm1;
private readonly ParentForm2 parentForm2;
public ChildForm(ParentForm1 form)
{
InitializeComponent();
parentForm1 = form;
}
public ChildForm(ParentForm2 form)
{
InitializeComponent();
parentForm2 = form;
}
private void btnSaveInput_Click(object sender, EventArgs e)
{
if (parentForm1 != null)
parentForm1.EmployeeName = txtName.Text;
else if (parentForm2 != null)
parentForm2.SetStudentName(txtName.Text);
}
Now ChildForm
needs to know about two potential callers, as well as which property or method to call, depending on how it was called. We could try to simplify things, perhaps by introducing an IParentForm
interface, but no matter what, things are getting ugly.
Let’s look at a better way…
Parent Form Pulls Data from Child
If we make the data available from the second Form, then let the individual callers retrieve as much or as little of the data as they need, then ChildForm
doesn’t need to change at all, no matter how many other objects are referencing it.
The easiest way to do this is to create public «getter» methods on ChildForm
:
public partial class DetailForm : Form
{
public DetailForm()
{
InitializeComponent();
}
public new string Name => txtName.Text;
public int Age => int.TryParse(txtAge.Text, out int result) ? result : 0;
}
The two fields on ChildForm
, to collect a name and age, are both made accessible to other forms, classes, etc that might need them. It doesn’t know for sure that a caller will need them, or anything else about potential callers – and that’s a good thing.
Here’s how one Form might call ChildForm
, and it only cares about the name:
private void btnGetUserInput_Click(object sender, EventArgs e)
{
using (var childForm = new ChildForm())
{
childForm.ShowDialog();
lblEmployeeName.Text = childForm.Name;
}
}
Then another Form calls ChildForm
, this time keeping both name and age:
private void btnGetUserInput_Click(object sender, EventArgs e)
{
using (var childForm = new ChildForm())
{
childForm.ShowDialog();
lblStudentName.Text = childForm.Name;
lblAge.Text = childForm.Age.ToString();
}
}
The ChildForm
is left cleaner. It requires no special knowledge of its callers, and has greater reusability and maintainability. The callers can grab as much or as little data as they need, or do nothing at all.
Final Thoughts
There’s two practical choices for passing data between two Forms:
- While we’re still in Form2, push data back to Form1.
- After we return to Form1, pull data from Form2.
The second option leads to easier code maintenance and greater usability.
I hope this helped clarify a few things. If it still seems unclear, or you see a possible error somewhere, leave a comment below and we’ll figure it out!
This article is aimed at providing some simple methods for passing data between forms in Windows applications
- Download demo project — 51.01 KB
Passing Data Between Forms
Introduction
Some of you would have faced a scenario where you wanted to pass data from one form to another in WinForms. Honestly, I too had a similar problem (that’s why I am writing this article!).
There are so many methods (How many? I don’t know) to pass data between forms in Windows application. In this article, let me take four important (easiest) ways of accomplishing this:
- Using constructor
- Using objects
- Using properties
- Using delegates
Let us see all the above methods in detail in the following sections.
For data to be passed between forms using any of the above methods, we need two forms and some controls. Let us start by following the steps given below.
Step 1
Create a new project and select Windows application. This will create a default form as “Form1
”. We can use this form for sending data.
Step 2
Add a textbox
and a button
to the form.
Step 3
Add another Windows Form for receiving the data and to display it. Right click the project and select Add->Add Windows Form. Enter a name or use the default name “Form2.cs” and click ok button.
Step 4
Add a label to the second form to display the text from form1
.
The Constructor Approach
This could be the easiest method of all. A method is invoked whenever you instantiate an object. This method is called a constructor. Code a constructor for form2
class with one string
parameter. In the constructor, assign the text to the label
’s text
property. Instantiate form2
class in form1
’s button click event handler using the constructor with one string
parameter and pass the textbox
’s text to the constructor.
Follow the steps given below:
Step 1
Code a constructor for form2
class as below:
public Form2(string strTextBox) { InitializeComponent(); label1.Text=strTextBox; }
Step 2
Instantiate form2
class in form1
’s button click event handler as below:
private void button1_Click(object sender, System.EventArgs e) { Form2 frm=new Form2(textBox1.Text); frm.Show(); }
The Object Approach
Objects are reference types, and are created on the heap, using the keyword new
. Here we are going to pass data using objects. The approach is simple; in form2
we are going to instantiate form1
class.
Then instantiate form2
in the button click event handler of form1
. After this we are going to pass form1
object to the form2
using form2
’s form1
object. The last step is to invoke the form2
window by calling the form2
’s show
method.
Follow the below steps:
Step 1
Change the access modifier for textbox
in form1
to public
:
public class Form1 : System.Windows.Forms.Form { public System.Windows.Forms.TextBox textBox1;
Step 2
In the button click event-handler, add the following code:
private void btnSend_Click(object sender, System.EventArgs e) { Form2 frm= new Form2(); frm.frm1=this; frm.Show(); }
Step 3
In form2.cs, instantiate form1
class:
public class Form2 : System.Windows.Forms.Form { private System.Windows.Forms.Label label1; public Form1 frm1;
Step 4
In Form2
’s Load
method, type cast the object (frm1
) of form1
to Form1
and access form1
’s textbox
and assign its text to label
’s text.
private void Form2_Load(object sender, System.EventArgs e) { label1.Text=((Form1)frm1).textBox1.Text; }
The Properties Approach
Properties allow clients to access class state as if they were accessing member fields directly, while actually implementing that access through a class method. In this method, we are going to add one property to each form. In form1
we are going to use one property for retrieving value from the textbox
and in form2
, one property to set the label
’s text property. Then, in form1
’s button click event handler, we are going to instantiate form2
and use the form2
’s property to set the label
’s text.
Follow the below steps:
Step 1
Add a property in form1
to retrieve value from textbox
:
public string _textBox1 { get{return textBox1.Text;} }
Step 2
Add a property in form2
to set the label
s’ text
:
public string _textBox { set{label1.Text=value;} }
Step 3
In form1
’s button click event handler, add the following code:
private void button1_Click(object sender, System.EventArgs e) { Form2 frm=new Form2(); frm._textBox=_textBox1; frm.Show(); }
The Delegates Approach
Technically, a delegate
is a reference type used to encapsulate a method with a specific signature and return type. You can encapsulate any matching method in that delegate
. Here we are going to create a delegate
with some signature and assign a function to the delegate
to assign the text
from textbox
to label
.
Follow the below steps:
Step 1
Add a delegate
signature to form1
as below:
public delegate void delPassData(TextBox text);
Step 2
In form1
’s button click event handler, instantiate form2
class and delegate
. Assign a function in form2
to the delegate
and call the delegate
as below:
private void btnSend_Click(object sender, System.EventArgs e) { Form2 frm= new Form2(); delPassData del=new delPassData(frm.funData); del(this.textBox1); frm.Show(); }
Step 3
In form2
, add a function to which the delegate
should point to. This function will assign textbox
’s text
to the label
:
public void funData(TextBox txtForm1) { label1.Text = txtForm1.Text; }
Conclusion
These four approaches are very simple in implementing data passing between forms. There are also other methods available in accomplishing the same. Source code for the methods I stated above is given at the top for download. It is time for you to put on your thinking cap and find other ways of doing this. Happy coding!!!
History
- 16th May, 2006: Initial post
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.
Note
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 sockets.
Requires
All code presented requires Visual Studio 2022 17.4 and higher. There is also code for classic .NET Framework for both C# and VB.NET which were the originals and upgrades in new projects to .NET Core.
Common types to pass between forms
Typically a developer will need to pass a string, date, numeric or an instance of a model/class. To achieve this create a base class responsible for sending information between forms.
The following class and interface are in a class project included in the supplied source code.
public class Dispatcher
{
private readonly Collection<IMessageListener1> _listeners = new();
/// <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>
/// Send int
/// </summary>
/// <param name="value">value to sender</param>
/// <param name="sender">form which sent the value</param>
[DebuggerStepThrough()]
public void Broadcast(int value, Form sender)
{
foreach (IMessageListener1 listener in _listeners)
{
listener.OnListen(value, sender);
}
}
/// <summary>
/// Send object
/// </summary>
/// <param name="value">value to sender</param>
/// <param name="sender">form which sent the value</param>
[DebuggerStepThrough()]
public void Broadcast(object value, Form sender)
{
foreach (IMessageListener1 listener in _listeners)
{
listener.OnListen(value, 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]);
}
}
}
}
Enter fullscreen mode
Exit fullscreen mode
Interface for above class
public interface IMessageListener1
{
void OnListen(string message, Form form);
void OnListen(int value, Form form);
void OnListen(object sender, Form form);
}
Enter fullscreen mode
Exit fullscreen mode
Implementation
For each form which will participate,
Add the following using
using static WinFormLibrary.Classes.Factory
Enter fullscreen mode
Exit fullscreen mode
implement the Interface.
public partial class Form1 : Form, IMessageListener1
Enter fullscreen mode
Exit fullscreen mode
In form load, add the form to the collection in the Dispatcher class.
Dispatcher().AddListener(this);
Enter fullscreen mode
Exit fullscreen mode
Next subscribe to form closing event, add the following code to remove the form from the collection.
Dispatcher().RemoveListener(this);
Enter fullscreen mode
Exit fullscreen mode
Example 1
There is a main form, one child form where if a int value exceeds a max value, change a picture box image from green to red and when the value is less than max value change the picture box image to green.
In the main form, when a NumericUpDown value changes its broadcasted to all listeners.
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
Dispatcher()
.Broadcast((int)numericUpDown1.Value, this);
}
Enter fullscreen mode
Exit fullscreen mode
Each form which implemented IMessageListener1 will have an event.
public void OnListen(int value, Form form)
{
if (form is Form1)
{
pictureBox1.Image = value > 10 ?
Properties.Resources.red :
Properties.Resources.green;
}
}
Enter fullscreen mode
Exit fullscreen mode
In the code sample, start the project, click the button Show child form which opens two instances of the form. On the main form, change the value for the NumericUpDown to over ten then under 10 and all three forms picture boxes image changes.
Example 2
The objective is to pass a class instance between forms. Since the class project knows nothing about our classes there is the following.
void OnListen(object sender, Form form);
Enter fullscreen mode
Exit fullscreen mode
Which allows passing, in this case a person.
To keep things interesting Bogus library is used to create a Person instance.
ic class BogusOperations
{
public static List<Person> People(int count = 1)
{
int identifier = 1;
Faker<Person> fakePerson = new Faker<Person>()
.CustomInstantiator(f => new Person(identifier++))
.RuleFor(p => p.FirstName, f =>
f.Person.FirstName)
.RuleFor(p => p.LastName, f =>
f.Person.LastName)
.RuleFor(p => p.BirthDate, f =>
f.Date.Past(10))
;
return fakePerson.Generate(count);
}
}
Enter fullscreen mode
Exit fullscreen mode
To send a person to all forms listening (remember there are two instances of the same form which know nothing about each other).
private void BogusButton_Click(object sender, EventArgs e)
{
Dispatcher()
.Broadcast(BogusOperations.People()
.FirstOrDefault()!, this);
}
Enter fullscreen mode
Exit fullscreen mode
Child form event
public void OnListen(object sender, Form form)
{
if (sender is Person person)
{
MessageBox.Show(
$"""
{Text}
{person.FirstName} {person.LastName}
""");
}
}
Enter fullscreen mode
Exit fullscreen mode
Using code in your project
- Add the project WinFormLibrary to a Visual Studio solution
- Add a reference to WinFormLibrary to your project
- Copy GlobalUsings.cs from the code sample to your project
Source code
Clone the following GitHub repository, do not download as a .zip file as once extracted forms will have mark of the web set and in turn Visual Studio will not compile until mark of the web is removed.
See also
Microsoft TechNet: original article using .NET Framework