Как реорганизовать дублированный код в Windows Forms?

#c# #winforms #refactoring

#c# #winforms #рефакторинг

Вопрос:

В настоящее время я работаю над рефакторингом большого количества дублированного кода в нескольких UserControl проектах Windows Forms.

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

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

 private void InitDestinationPathControls(string path)
{
  if (someField)
  {
    tbOne.Enabled = false;
    tbOne.Visible = false;
    btnTwo.Enabled = false;
    btnTwo.Visible = false;
    tbOne.Text = string.Empty;
    return;
  }
  // (...)
}
 

Не слишком привязывайтесь к самому цитируемому коду, это всего лишь пример.

Я бы хотел перенести этот код в общий базовый класс, но он напрямую зависит от определенных полей (хотя они одинаковы и во всех элементах управления). С другой стороны, эти поля генерируются дизайнером, поэтому я не могу извлечь их в базовый класс.

Единственное, что приходит мне в голову, — это передать эти поля в качестве параметров методу базового класса, но тогда, если какой-то метод использует их много, я получу чудовищную часть интерфейса, и это на самом деле не слишком улучшит читаемость.

Как я могу справиться с такими распространенными частями пользовательских элементов управления в Windows Forms?

Комментарии:

1. Кажется, что-то и больше кода отсутствуют. Метод в корневом классе звучит правдоподобно, иначе вы пробовали вспомогательный класс, имеющий методы расширения?

2. Эти включенные / видимые / текстовые настройки могут быть свойствами, связанными с данными, в любом базовом бизнес-объекте. На самом деле, ничто не мешает вам использовать MVVM даже в приложении WinForms. Вот демонстрационное приложение, которое использует одну и ту же ViewModel в WinForms и в графическом интерфейсе WPF. А вот еще один (несколько более простой) пример ViewModel формы с некоторыми ...Enabled свойствами.

Ответ №1:

По-видимому, у вас есть комбинация нескольких элементов управления, которая отображается в нескольких формах. Другими словами: у вас есть, например, несколько кнопок, выпадающих списков и т. Д., Которые вы хотите отображать в разных формах, и вы хотите, Чтобы они вели себя одинаково.

Если не только поведение этих элементов управления одинаково во всех формах, но и макет, тогда подумайте о создании класса, производного от UserControl .

UserControl скрывает от внешнего мира, какие элементы управления отображаются, как они визуализируются и как они ведут себя.

Я предполагаю, что вы уже извлекли модель из визуализации данных.

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

Если, с другой стороны, макет отличается в каждой форме, Но набор кнопок, выпадающих списков и т. Д. И их поведение одинаковы Во всех формах, которые показывают эту коллекцию элементов управления, и у них много общего поведения, подумайте о создании собственного ControlCollection .

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

 class FileDisplayControls : IDisposable
{
    public Button ButtonSelectFile {get;} = new Button();
    public Label labelFileName {get; } = new Label();
    public Label labelFileSize {get; } = new Label();
    public TextBox textFileContents {get; } = new FileContents();

    private void ButtonSelectFile_Clicked(object sender, ...)
    {
        // TODO: open file dialog, display result in labels and text box
    }
}
 

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

Пользователь класса (= code, а не operator) сразу же получает набор элементов управления, которые имеют некоторое стандартное поведение, например реагируют на нажатие кнопки. Все, что ему нужно сделать, это установить расположение элементов в своей собственной форме. При желании измените другие свойства макета (цвет, фон) и поместите их в его собственную форму.

Если вы хотите предотвратить изменение другими пользователями других визуальных аспектов элементов управления, кроме положения, не публикуйте сами элементы управления, а только положение элемента управления:

 public System.Drawing.Point LocationSelectFileButton
{
    get => this.buttonSelectFile.Location;
    set => this.buttonSelectFile.Location = value;
}

public System.Drawing.Point LocationFileContentTextBox
{
    get => this.textBoxFileContent.Location;
    set => this.textBoxFileContent.Location = value;
}
 

и т.д.

При необходимости вы можете добавить события для пользователей:

 public event EventHandler SelectedFileChanged;

public string FileName => this.labelFileName.Text;
public string FileContents => this.textBoxFileContent.Text;
 

и т.д.

Заключение

Решение, которое вы выберете, зависит от сходства элементов управления в различных формах:

  • если поведение и макет одинаковы: UserControl
  • Если отличается только позиция и несколько свойств: специальный класс с разными свойствами. Таким образом, вы можете создать более похожий стиль: все кнопки «Выбрать файл» выглядят одинаково.
  • Если отличается только одно или два поведения: добавление Action<...> свойств или событий
  • Если вы хотите получить полный контроль над макетом: предоставьте доступ к элементам управления.
  • Поведение, которое является общим для всех ваших форм, которые показывают эти элементы управления (в моем примере: как выбрать файл и что делать при выборе файла), находится внутри класса.

Ответ №2:

повторяющийся код может быть извлечен в метод (возможно, в базовом классе или как статический метод в вспомогательном классе)

 public void DisableControls(params Control[] controls)
{
    foreach(var c in Controls)
    {
        c.Enabled = false;
        c.Visible = false;
        if (c is TextBox t)
        {
            t.Text = string.Empty;
        }
    }
}

private void InitDestinationPathControls(string path)
{
     if (someField)
     {
        DisableControls(tbOne, btnTwo);
        return;
     }

     // (...)
}