Общаться между двумя окнами в С#
У меня две формы: одна - основная форма, а другая - форма опций. Например, скажем, что пользователь нажимает на мое меню в основной форме: Tools -> Options
, это приведет к отображению формы параметров.
Мой вопрос в том, как я могу отправить данные из формы своих параметров обратно в свою основную форму? Я знаю, что могу использовать свойства, но у меня есть много вариантов, и это похоже на необычную вещь ded.
Итак, что является лучшим способом?
Ответы
Ответ 1
Form1 запускает Form2 для открытия. Form2 имеет перегруженный конструктор, который принимает вызывающую форму в качестве аргумента и предоставляет ссылку на члены Form2. Это решает проблему связи. Например, я выставил свойство Label как общедоступное в Form1, которое изменено в Form2.
При таком подходе вы можете общаться по-разному.
Ссылка для скачивания для примера проекта
//Ваша форма1
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; }
}
}
//Ваша форма2
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)
Ответ 2
В комментариях к принятому ответу Нирадж Гуля пишет:
Это приводит к тесной связи форм 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 += (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
которое предоставляет свойство Text
(и только свойство Text
, и фактически только для чтения) txtMessage
управления 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
В этом случае лучше всего иметь некоторый класс/интерфейс OptionsService
, доступный через IServiceProvider
.
Просто добавьте событие, когда что-то изменится, и остальная часть приложения сможет ответить на него.
Ответ 4
Свойства - это один вариант, общий статический класс - еще один вариант, события - еще один вариант...
Ответ 5
Вы можете попробовать AutoMapper. Сохраняйте свои параметры в отдельном классе, а затем используйте AutoMapper для передачи данных между классом и формой.
Ответ 6
Создайте класс и поместите все свои свойства внутри класса. Создайте свойство в родительском классе и установите его из формы вашего ребенка (параметры)
Ответ 7
Вы можете иметь функцию в форме 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.
}
Ответ 8
MVC, MVP, MVVM - незначительный перебор для кого-то, по общему признанию говоря, что они хотят учебников. Это те теории, которые посвящены целым курсам.
Как уже было опубликовано, прохождение объекта вокруг, наверное, проще всего. Если рассматривать класс как объект (взаимозаменяемый в этом смысле) является новым, тогда вы можете потратить еще 2-4 недели на выяснение свойств и конструкторов и т.д.
Я не являюсь мастером С# любыми способами, но эти концепции должны быть довольно конкретными, если вы хотите пойти намного дальше, нежели передавать значения между двумя формами (также самими классами/объектами). Не пытаясь быть здесь вообще злым, это просто звучит так, будто вы переходите от чего-то вроде VB6 (или любого языка с глобалями) к чему-то гораздо более структурированному.
В конце концов, он щелкнет.
Ответ 9
Существует множество способов установить связь между двумя формами. Некоторые из них вам уже объяснили. Я показываю вам все наоборот.
Предполагая, что вам нужно обновить некоторые настройки из дочерней формы в родительскую форму. Вы также можете использовать эти два способа:
- Использование 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");
Ответ 10
Это, вероятно, немного обойдется вашей проблеме, но в моем диалоговом окне настроек используется настройка параметров приложения. http://msdn.microsoft.com/en-us/library/k4s6c3a0.aspx
Я не могу найти хороший пример, аналогичный тому, как я это делаю (на самом деле имеющему фактический объект класса +), но это охватывает другой способ:
Чтение настроек приложения по умолчанию на С#
Ответ 11
Форма - это класс, как и любой другой класс. Добавьте некоторые общедоступные переменные в ваш класс формы и установите их, когда они нажимают кнопку, чтобы закрыть форму (технически они просто скрывают ее).
Пример VB.NET, но вы получите идею -
В вашем классе OptionsForm:
Public Option1 as String = ""
и т.д.. Установите их, когда они нажмут кнопку "Ok".
Итак, в вашей основной форме, когда они нажимают кнопку "Параметры", вы создаете свою форму:
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
}
и теперь вы можете зарегистрировать эту функцию для целого ряда других элементов управления и контейнеров, и все они получат сигнал
Я надеюсь, что это поможет :)