3

Выдержка кода из главной формы:

public partial class Main : Form
{
    public NetComm.Host server;
    public NetComm.Client client;
    public Main()
    {
        InitializeComponent();
    }
    private void ToolStripMIConnect_Click(object sender, EventArgs e)
    {
        ClientPref formConnect = new ClientPref();
        formConnect.Owner = this;
        formConnect.ShowDialog();
    }
}

Выдержка кода из создаваемой формы:

public partial class ClientPref : Form
{
    Main formMain;
    public ClientPref()
    {
        InitializeComponent();
        formMain = this.Owner as Main;
    }
    private void btnConnect_Click(object sender, EventArgs e)
    {
        if (formMain != null) //следующий бок не выполняется т.к. formMain равен null
        {
            if (IsAddressValid(this.txtBoxIP.Text))
            {
            formMain.ClientStart(Convert.ToInt32(this.txtBoxPort.Text), this.txtBoxIP.Text, "Соперник");
            }
            else toolTipClient.Show("Введен некорректный IP-адрес.", this.txtBoxIP);
        }

    }

Почему formMain равен null или как в итоге обратиться к методу ClientStart формы Main?

SaheR
  • 117
  • 2
    потому что когда выполняется код formMain = this.Owner as Main; owner еще не установлен, поэтому formMain всегда null – Grundy Jan 08 '16 at 09:43
  • 2
    Или уносите Owner в конструктор формы, или читайте его на Loaded событии. – Monk Jan 08 '16 at 09:48
  • На будущее - лучше не писать код в формах. Всмысле, код запуска клиента - не код формы, зависеть от конкретного экземпляра - не должен. Но что почитать на эту тему - даже не могу подсказать к сожалению. – Monk Jan 08 '16 at 13:52
  • когда вы вызываете formMain.ClientStart из ClientPref, то тем самым нарушаете один из основных принципов ООП. и увеличиваете связанность кода. почитайте мой ответ ниже. – Stack Jan 08 '16 at 16:11

3 Answers3

3

Простой вариант - переписать вот так, сделать поле вычислимым свойством:

Main FormMain { get { return this.Owner as Main } };

Тогда в btnConnect_Click всё должно работать.

Monk
  • 4,478
  • После вашего ответа понял, что можно было даже без объявления formMain в btnConnect_Click написать просто (this.Owner as Main).ClientStart(Convert.ToInt32(this.txtBoxPort.Text), this.txtBoxIP.Text, "Соперник"); – SaheR Jan 08 '16 at 10:06
1

Когда выполняется код

formMain = this.Owner as Main; 

owner еще не установлен, так как он устанавливается после выполнения конструктора, поэтому formMain всегда null

Решить можно передавая параметр в конструктор, для этого надо добавить конструктор с параметром

public ClientPref(Main owner):this()
{
    formMain = this.Owner as Main;
}

и вызывать уже его

private void ToolStripMIConnect_Click(object sender, EventArgs e)
{
    ClientPref formConnect = new ClientPref(this);
    formConnect.ShowDialog();
}
Grundy
  • 81,538
-1

Если ClientPref - это диалог, то его надо сделать независимым и он не должен управлять формой, т.е. в диалоге не должно быть вызова formMain.ClientStart(...), т.к. это нарушает один из основных принципов дизайна в ООП - принцип единственной обязанности (англ. Single responsibility principle - SRP).

Диалог должен получить данные от пользователя, проверить их и предоставить доступ к этим данных. Больше никакой обязанности у диалога быть не должно. Посмотрите, например, на OpenFileDialog, в нем нет ссылки на главную форму.

Чтобы диалог ClientPref сделать независимым, надо значения, выбранные пользователем, сохранять в свойствах диалога.

public partial class ClientPref : Form {

  public int Port { get; private set; }
  public string IP { get; private set; }

  void Validate() {
    if (IsAddressValid(this.txtBoxIP.Text)) {
      this.Post = Convert.ToInt32(this.txtBoxPort.Text);
      this.IP = this.txtBoxIP.Text;
    }   
  }
}

В диалоге надо перехватывать нажатие Esc -- работающий пример диалога тут.

Для вызова диалога и получения данных из него, в главной форме пишем:

var fc = new ClientPref();
if(fc.ShowDialog() == DialogResult.OK)
   ClientStart(fc.Port, fc.IP);

т.е. форма решает когда вызвать диалог и что делать с данными введенными пользователем.


Если по какой-то причине надо получить ссылки на все открытые формы, то это можно сделать так:

using System.Windows.Forms;

var forms = Application.OpenForms;

Открытую форму определенного типа можно получить так

using System.Linq;

var main = Application.OpenForms.OfType<Main>().First();

Если есть несколько форм одинакового типа, то их можно отпичать по значению свойства Name, но таких ситуаций лучше избегать.


Почему нежелательно передавать ссылки? Представьте, что в приложении десяток форм. В каждую форму передается ссылка на главную форму, а также возможно, что передается ссылка на какую-то дополнительную форму. Если в дальнейшем надо будет изменить логику взаимодействия форм, то возникнут сложности с рефакторингом.
Поэтому надо стараться уменьшать связанность кода, и если есть возможность передать ссылку через Application.OpenForms, то лучше так и делать.

Stack
  • 9,452
  • а как ваш ответ отвечает на вопрос почему поле Owner = null? – Grundy Jan 08 '16 at 13:34
  • @Grundy "а как ваш ответ отвечает на вопрос почему поле Owner = null?" -- лучше дать один из нескольких правильных ответов, чем отвечать на множество неправильных, многие из которых даже задавать не надо, потому что в Visual Studio есть F11. – Stack Jan 08 '16 at 13:43
  • @Grundy а почему вы свой вопрос не задали @Monk? и почему минус поставили, хотите сказать, что Application.OpenForms не работает? – Stack Jan 08 '16 at 14:38
  • Потому что ваш ответ никак не привязан к заданному вопросу - вы просто получаете какую-то форму, в то время как в ответе @Monk, вполне себе решение задачи из вопроса. простой пример: если в программе несколько форм, вы получаете первую, а надо получать ссылку на ту, из которой открывается форма – Grundy Jan 08 '16 at 14:44
  • @Grundy "получаете какую-то форму" -- вы о чем? откройте вторую форму и посмотрите на результат вызова Application.OpenForms.OfType
    ().First(); вы получите совершенно определенную форму.
    – Stack Jan 08 '16 at 14:48
  • вы получите совершенно определенную форму если у вас открыто две формы тип Main - какую из них вы тут получите? – Grundy Jan 08 '16 at 14:49
  • @Grundy "у вас открыто две формы тип Main - какую из них вы тут получите?" -- для чего вам две Main? :) но если семантику названий типов вы не используете, то у контролов/форм есть свойство .Name и .Tag, а вообще надо всеми силами снижать связанность кода. и учить этому новичков. а вы против этого? или вы еще сами не сталкивались с проблемами, которые возникают из-за связанности? – Stack Jan 08 '16 at 15:04
  • Вы ушли от ответа :-) – Grundy Jan 08 '16 at 15:27
  • @Grundy "Вы ушли от ответа" -- а что вам непонятно? в программе бывает одна главная форма. вы спрашиваете: а если их две? я вам ответил что это неправильно, но даже в такой ситуации формы можно отличить, например, по значению .Name, а еще посмотрите на Form.ActiveForm. так что правильных вариантов немного, а вы хотите обсуждать неправильные и чтобы я отвечал? :) – Stack Jan 08 '16 at 15:36
  • вам ответил что это неправильно, но даже в такой ситуации формы можно отличить, например, по значению .Name, и как вы узнаете по Name в какой из форм был открыт диалог? – Grundy Jan 08 '16 at 15:37
  • а вы хотите обсуждать неправильные и чтобы я отвечал? - вы ответили на какой-то свой вопрос, вот я и пытаюсь хоть как-то привязать его к тому что спросил автор, а вы только добавляете варианты ответов на свой вопрос :) – Grundy Jan 08 '16 at 15:39
  • @Grundy "как вы узнаете по Name в какой из форм был открыт диалог?" -- для чего это диалогу знать? диалог вообще не должен знать кто его открыл. для примера посмотрите на OpenFileDialog. "вы ответили на какой-то свой вопрос," -- есть вопрос связанности кода, и он касается многих. вас еще не коснулся? про связанность кода я добавил в свой ответ. – Stack Jan 08 '16 at 15:45
  • Потому что в данном случае он вызывает функцию из своего owner – Grundy Jan 08 '16 at 15:46
  • @Grundy "он вызывает функцию из своего owner" -- и тем самым нарушает один из основных принципов ООП. почитайте мой ответ. – Stack Jan 08 '16 at 16:09
  • обратите внимание, что в обработчике btnConnect_Click диалог не закрывается, таким образом, ваш вариант с проверкой результата работать не будет – Grundy Jan 08 '16 at 16:38
  • @Grundy "в обработчике btnConnect_Click диалог не закрывается" -- очевидно, что само по себе ничего не происходит. надо добавить код. и если нажатие btnConnect по смыслу соответствует OK, то надо закрыть диалог со значением DialogResult.OK. а если юзер нажал Esc, то надо закрыть диалог с соответствующим значением DialogResult. посмотрите как это сделано например в OpenFileDialog. – Stack Jan 08 '16 at 16:53
  • @Grundy пример диалога в ответе тут – Stack Jan 08 '16 at 17:48
  • @Stack, дочернюю форму не закрываю сразу т.к. в ней происходит ожидание до подключения клиента к серверу. В момент ожидания можно прервать попытку подключения, закрыв форму (при этом будет выведен диалог о том, что попытка соединения будет прекращена и вариантами о дальнейшем ожидании и прекращения). Как только соединение с сервером было установлено, форма закрывается самостоятельно. – SaheR Jan 08 '16 at 18:43