Работа с несколькими формами

Необходимо создать несколько форм и как-то ими управлять. Но оказалось, что и тут не всё так просто.
Погуглил, но нашел только как создать и показать форму. Это же и так понятно.
Например. Есть глобальная переменная

        public static FormSettings formSettings = new FormSettings();

        public FormSettings()
        {
            InitializeComponent();
            Disposed += (s, e) =>
            {
                System.Diagnostics.Debug.WriteLine("settings disposed");
            };
            
            System.Diagnostics.Debug.WriteLine("settings Create");
        }

        private void FormSettings_Load(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("settings Load()");
            Left = -1000;
        }

Проблемы:

  • Load() приходит только когда делаем formSettings.Show(). Не вижу логики.
  • Диспоз приходит только если был вызов formSettings.Show(). Логика, ку-ку, вы где?
  • При закрытии формы, её экземпляр уничтожается и обратиться к нему больше нельзя.

Это получается, что каждый раз когда форма нужна, надо создавать её новый экземпляр?
А если события в первой форме должны что-то менять во второй форме, а она еще не показана? Тогда что?

Функции Show() и Hide() разве нельзя применить к формам?
По подобию как здесь:
Hiding and showing forms in C#

а если форму не надо показывать сразу?

Так а проблема в чем? Если что-то надо делать при создании экземпляра класса, то для этого есть конструктор.

Надо создать форму, но сразу не показывать. Чтобы первая форма могла менять что-то во второй (третьей, четвёртой). пока тех не видно.
А показать её только по кнопке, например.

А что она меняет?

Может это

  • доступно и до Load
  • вообще не имеет отношения к формам и может быть отделено от нее в другой класс и т.п.

Есть статический класс Utils, в котором public static List<MyClass> myClasses = new List<MyClass>();, который должен использоваться всеми формами.
Формы должны отображать и менять значения выбранного экземпляра в массиве.
Каждая форма отображает и меняет что-то своё, за что отвечает.

Зачем тогда вообще нужен Load, если он приходит только при вызове Show? Чем это отличается от Shown?
И почему диспоз не приходит, если не вызвать Show? Чё это за логика такая? Форма ведь создаётся до того как была показана.

Раньше вызывается и только в первый раз.

Occurs before a form is displayed for the first time.

В финалайзере происходит, но без вызова событий. Видимо потому что они уже могли быть вызваны в Close и т.п.
https://referencesource.microsoft.com/#System/compmod/system/componentmodel/Component.cs,126

Ну и диспозить же в этом случае форме нечего, ресурсы винапи, которые надо диспозить, оно создает только когда показывает форму.

Тогда, наверное, понятно. Вместо Load используем конструктор, а вместо диспоза - деструктор.
А как сделать, чтобы форма не уничтожалась при закрытии, а просто скрывалась?

Формы это элементы взаимодействия с пользователем. Зачем их использовать не по назначению??
Лучше передавать данные в форму при ее создании.

Сказали же:

Тоже не совсем хорошая практика. Лучше когда нужный параметр передается в свою форму где и происходит его редактирование. А объекты которые изменяются из любой части программы нужно избегать. Либо очень тщательно контролировать доступ.

Так. Чтобы избежать дальнейшего недопонимания, вы мне сразу объясните, что значит “использовать форму не по назначению” и в чём заключается “взаимодействие с пользователем”.
На форме есть ComboBox со списком элементов массива. Выбираем элемент из списка и отображаем/меняем значения. Кроме этого, при выборе элемента ComboBoxа, ComboBoxы на других формах тоже должны переключиться на тот же элемент, что был выбран.
Что из этого не по назначению используется? :thinking:

Если речь идёт об этом

private void button1_Click(object sender, EventArgs e)
    {
      Form frm2 = new Form();
      frm2.Show();
      this.Hide();
      frm2.FormClosing += Frm2_Closing; 
    }
    private void Frm2_Closing(object sender, FormClosingEventArgs e)
    {
      this.Show();
    }

то зачем мне показывать первую (или ещё какую-то) форму при закрытии второй? :thinking:

Я знаю. Тут еще надо подумать, как сделать. Но я пока вариантов не вижу, кроме как использование глобального массива.

Кстати, а почему деструктор формы выполняется только если форма скрыта?

Он вызывается когда захочет сборщик мусора
Finalizers - C# Programming Guide | Microsoft Docs

Вообще логичнее не трогать его, а переопределить этот Dispose

protected override void Dispose(bool disposing)
{
    // очистить что надо

    base.Dispose(disposing);
}

    if (e.CloseReason == CloseReason.UserClosing) 
    {
        e.Cancel = true;
        Hide();
    }

https://stackoverflow.com/a/2021708

это я уже методом тыка нашел. Только не понятно, как с этим связана тема по ссылке.

выдаёт ошибку CS0111 Тип "FormSettings" уже определяет член "Dispose" с такими же типами параметров.

А, да, он уже в .Designer.cs

Так вопрос же как прятать вместо закрытия.

Там всё автоматически создаётся. То есть, свой код туда писать?

там, вроде, не сказано про e.Cancel = true.

Может быть только часть в #region Код, автоматически созданный конструктором форм Windows перегенерируется, тогда можно редактировать остальное (например, переместить в основной файл).

Не понял. Я только это добавил


        protected override void Dispose(bool disposing)
        {
            OnDispose();<<<<<
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

Чтобы не путаться где что, можно убрать этот метод из .Designer.cs и вставить в основной файл. (в .Designer.cs же обычно мало кто смотрит).

Да проще некуда. В чем вообще проблем то возникает?

public partial class TestForm : Form
    {
        public delegate object InteractionEventHandler(object sender, object data);

        public event InteractionEventHandler InteractionEvent;

        public TestForm()
        {
            InitializeComponent();
        }

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            var res =  InteractionEvent?.Invoke(this, new object[] { comboBox1.SelectedItem, DateTime.Now.ToString() });
            if(res is object[] odat)
            {
                textBox1.Text = odat.Select(s=>s.ToString()).Aggregate((a, b) => a += Environment.NewLine +b.ToString() );
            }
        }
    }
object locker = new object();
        List<TestForm> formsList = new List<TestForm>();
        private void button1_Click(object sender, EventArgs e)
        {
            TestForm nForm = new TestForm();
            nForm.FormClosing += (ox, ex) =>
            {
                lock (locker)
                {
                    formsList.Remove(nForm);
                }
            };

            nForm.InteractionEvent += (ox, data) =>
            {
                if (data is object[] odat)
                {
                    if (int.TryParse(odat[0].ToString(), out int num))
                    {
// тут определяете любую логику обработки запросов с форм. И передаете любые данные.
                        return Enumerable.Repeat(odat[1].ToString(), num).ToArray();
                    }
                }
                return null;
            };

            lock (locker)
            {
                formsList.Add(nForm);
                nForm.Show();
            }
        }

Удивительно но да, нужно.

Странная логика. Ну и пусть меняет в классе параметров. Зачем постоянно тыкать в пользователя формами?
Вообще если форм много то это очень плохо сказывается на юзабилити.

Формы существуют только для отображения параметров а не как носители настроек. То есть класс формы не должен содержать общих настроек. Тогда и не нужно будет создавать целую форму только для того чтобы проставить там путь к какому нибудь файлу конфига.

Не рекомендуется же файл дизайнера менять. Вся проблема задачи заключается в неправильной логике работы и это порождает какой то невообразимый набор костылей.

  1. Класс параметров должен быть самодостаточным и отдельным без какого либо GUI.
  2. Формы должны отображать параметры и предоставлять изменение и сохранение данных без создания дополнительных форм если они не нужны пользователю в этот момент.
  3. Количество форм лучше минимизировать и сгруппировать по какому нибудь признаку.

описываете все поля класса настроек с проверкой изменения

public class FSettings
    {
        int _startUpCounter = 0;

        public int StartUpCounter
        {
            get { return _startUpCounter; }
            set { var old = _startUpCounter; _startUpCounter = value; if (old != _startUpCounter) OnPropertyChange?.Invoke("StartUpCounter", _startUpCounter); }
        }



        public event OnChangeEvent OnPropertyChange;

        public delegate void OnChangeEvent(string PropertyName, object PropertyValue);

    }

А при создании формы подписываете на изменения:

        public TestForm(FSettings set)
        {
            InitializeComponent();
            locSet = set;
            set.OnPropertyChange += Set_OnPropertyChange;
        }

        private void Set_OnPropertyChange(string PropertyName, object PropertyValue)
        {
// при каждом изменении полей будет прилетать сообщение со значением. И можете обновлять свои поля как хотите. И каждый тип формы может обновлять именно свои значения.
            textBox1.Text = "OnChange \"" + PropertyName + "\" = " + PropertyValue;
        }

Если заморочится то можно каждому контролу задать специфичное имя или таг с названием свойства класса и в цикле производить поиск и установку. Тогда будет еще меньше кода и что самое главное не будет костылей.

Ту часть, которая может перегенерироваться. Остальное по идее можно (и люди делают), иначе бы невозможно было ничего в диспоз формы добавить.

А для чего автору диспоз понадобился неизвестно, может он в любом случае был бы нужен.