Есть ли недостатки в создании кода в Userforms вместо модулей?

Есть ли недостатки в потере кода в пользовательскую форму VBA вместо "нормального" модуля?

Это может быть простой вопрос, но я не нашел для него окончательного ответа при поиске в Интернете и stackoverflow.

Фон: Я разрабатываю Front-End приложение базы данных в Excel-VBA. Для выбора разных фильтров у меня разные пользовательские формы. Я прошу, какой общий дизайн программы лучше: (1) включение структуры управления в отдельный модуль ИЛИ (2) размещение кода для следующей пользовательской формы или действия в пользовательской форме,

Давайте сделаем пример. У меня есть кнопка Active-X, которая запускает мои фильтры и мои формы.

Вариант1: Модули

В CommandButton:

Private Sub CommandButton1_Click()
  call UserInterfaceControlModule
End Sub

В модуле:

Sub UserInterfaceControllModule()
Dim decisionInput1 As Boolean
Dim decisionInput2 As Boolean

UserForm1.Show
decisionInput1 = UserForm1.decision

If decisionInput1 Then
  UserForm2.Show
Else
  UserForm3.Show
End If

End Sub

В варианте 1 структура управления находится в нормальном модуле. И решения, которые пользовательформат отображает дальше, отделены от пользовательской формы. Любая информация, необходимая для принятия решения о том, какую пользовательскую форму для показа дальше следует вытащить из пользовательской формы.

Вариант2: Пользовательская форма

В CommadButton:

Private Sub CommandButton1_Click()
  UserForm1.Show
End Sub

В Userform1:

Private Sub ToUserform2_Click()
  UserForm2.Show
  UserForm1.Hide
End Sub

Private Sub UserForm_Click()
  UserForm2.Show
  UserForm1.Hide
End Sub

В варианте 2 структура управления находится непосредственно в пользовательских формах, и пользовательская форма имеет инструкции, которые следуют за ней.

Я начал разработку с использованием метода 2. Если это была ошибка, и есть некоторые серьезные недостатки этого метода, я хочу знать это скорее раньше, чем позже.

Ответ 1

Отказ от ответственности Я написал статью , связанную с= "/info/459142/are-there-disadvantages-in-putting-code-into-userforms-instead-of-modules#comment4526141_459142" > с. Я владею этим блогом и управляю проектом надстройки VBIDE с открытым исходным кодом для него.

Ни один из ваших вариантов не идеален. Назад к основам.


Чтобы выбрать разные фильтры, у меня есть разные пользовательские формы.

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

Создание формы, ответственной за что-либо, кроме проблемы с представлением, является распространенной ошибкой, и у нее есть имя: это шаблон Smart UI [anti-], и проблема с ним заключается в том, что он не масштабируется. Это отлично подходит для прототипирования (т.е. Быстро, что "работает" - обратите внимание на цитаты с испугом), а не столько для чего-то, что нужно поддерживать в течение многих лет.

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

Вы видите, что UserForm - это модуль класса: он определяет схему объекта. Объекты обычно хотят создать экземпляр, но тогда у кого-то была гениальная идея предоставить все экземпляры MSForms.UserForm predeclared ID, который в терминах COM означает, что вы в основном получаете глобальный объект бесплатно.

Отлично! Нет? Нет.

UserForm1.Show
decisionInput1 = UserForm1.decision

If decisionInput1 Then
  UserForm2.Show
Else
  UserForm3.Show
End If

Что произойдет, если UserForm1 - "X'd-out"? Или если UserForm1 Unload ed? Если форма не обрабатывает событие QueryClose, объект уничтожается - но поскольку этот экземпляр по умолчанию, VBA автоматически/молча создает новый для вас, как раз перед тем, как ваш код читает UserForm1.decision - в результате вы получаете независимо от начального глобального состояния для UserForm1.decision.

Если это не был экземпляр по умолчанию, а QueryClose не обрабатывался, доступ к элементу .decision уничтоженного объекта дал бы вам классическую ошибку времени выполнения 91 для доступа к ссылке на нулевой объект.

UserForm2.Show и UserForm3.Show оба делают одно и то же: огонь и забыть - что бы ни случилось, и чтобы выяснить, что именно состоит, вам нужно выкопать его в соответствующем коде для форм.

Другими словами, формы запускают показ. Они несут ответственность за сбор данных, представление этих данных, сбор пользовательских данных и выполнение любой работы с ним. Именно поэтому он назвал "Smart UI": пользовательский интерфейс все знает.

Там лучший способ. MSForms является предшественником COM-интерфейса .NET WinForms UI, и то, что имеет предок вместе с его преемником .NET, заключается в том, что он особенно хорошо работает со знаменитым шаблоном Model-View-Presenter (MVP).


Модель

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

  • UserForm1.decision отпустите это.

Добавьте новый класс, назовите его, скажем, FilterModel. Должен быть очень простой класс:

Option Explicit

Private Type TModel
    SelectedFilter As String
End Type
Private this As TModel

Public Property Get SelectedFilter() As String
    SelectedFilter = this.SelectedFilter
End Property

Public Property Let SelectedFilter(ByVal value As String)
    this.SelectedFilter = value
End Property

Public Function IsValid() As Boolean
    IsValid = this.SelectedFilter <> vbNullString
End Function

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

Здесь есть только 1 свойство, но у вас может быть еще много: подумайте об одном поле в свойстве form = > one.

Модель также должна знать форма из логики приложения. Например, если форма нуждается в раскрывающемся списке, который отображает несколько возможных вариантов, модель будет объектом, подвергая их.


Вид

Это ваша форма. Он несет ответственность за знание об элементах управления, написании и чтении из модели и... о том, что все. Мы смотрим на диалог здесь: мы его поднимаем, пользователь заполняет его, закрывает его, и программа действует на него - сама форма ничего не делает с данными, которые она собирает. Модель может подтвердить ее, форма может решить отключить ее кнопку Ok, пока модель не сообщит, что ее данные действительны и хороши, но ни при каких обстоятельствах a UserForm читает или записывает с рабочий лист, базу данных, файл, URL-адрес или что-то еще.

Образ кода формы прост: он соединяет пользовательский интерфейс с экземпляром модели и включает/отключает его кнопки по мере необходимости.

Важно помнить:

  • Hide, не Unload: представление - это объект, а объекты не подвергаются самоуничтожению.
  • НИКОГДАобратитесь к экземпляру формы по умолчанию.
  • Всегда обрабатывайте QueryClose, чтобы избежать саморазрушающего объекта ( "X-ing out" формы в противном случае уничтожил бы экземпляр).

В этом случае код-код может выглядеть так:

Option Explicit
Private Type TView
    Model As FilterModel
    IsCancelled As Boolean
End Type
Private this As TView

Public Property Get Model() As FilterModel
    Set Model = this.Model
End Property

Public Property Set Model(ByVal value As FilterModel)
    Set this.Model = value
    Validate
End Property

Public Property Get IsCancelled() As Boolean
    IsCancelled = this.IsCancelled
End Property

Private Sub TextBox1_Change()
    this.Model.SelectedFilter = TextBox1.Text
    Validate
End Sub

Private Sub OkButton_Click()
    Me.Hide
End Sub

Private Sub Validate()
    OkButton.Enabled = this.Model.IsValid
End Sub

Private Sub CancelButton_Click()
    OnCancel
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If CloseMode = VbQueryClose.vbFormControlMenu Then
        Cancel = True
        OnCancel
    End If
End Sub

Private Sub OnCancel()
    this.IsCancelled = True
    Me.Hide
End Sub

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


Ведущий

Это объект "клей", который соединяет точки.

Option Explicit

Public Sub DoSomething()
    Dim m As FilterModel
    Set m = New FilterModel
    With New FilterForm
        Set .Model = m 'set the model
        .Show 'display the dialog
        If Not .IsCancelled Then 'how was it closed?
            'consume the data
            Debug.Print m.SelectedFilter
        End If
    End With
End Sub

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

Вызывающий код может быть вашим клиентом обработчика кнопки ActiveX, New -подготовкой ведущего и вызовом его метода DoSomething.


Это не все, что нужно знать о OOP в VBA (я даже не упоминал интерфейсы, полиморфизм, тестовые заглушки и модульное тестирование), но если вы хотите объективно масштабируемый код, вы захотите спуститься вниз MVP кролика и изучить возможности по-настоящему объектно-ориентированного кода привести к VBA.


TL; DR:

Код ( "бизнес-логика" ) просто не принадлежит коду кода в любой кодовой базе, которая означает масштабирование и сохранение в течение нескольких лет.

В "варианте 1" код трудно выполнить, потому что вы перепрыгиваете между модулями, а проблемы с представлением смешиваются с логикой приложения: это не задание формы, чтобы узнать, какую другую форму отображать данную кнопку A или кнопку B был нажат. Вместо этого он должен позволить ведущему знать, что пользователь хочет делать, и действовать соответствующим образом.

В "варианте 2" код трудно следовать, потому что все скрыто в коде кода пользователя: мы не знаем, что такое логика приложения, если мы не вникнем в этот код, который теперь намеренно смешивает презентационную и бизнес-логику проблемы. Это именно то, что делает анти-шаблон Smart UI.

Иными словами, вариант 1 немного лучше варианта 2, потому что по крайней мере логика не находится в коде, но она по-прежнему является "интеллектуальным интерфейсом", потому что она запускает шоу, а не говорит своему вызывающему, что происходит.

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

Обработать формы, подобные объектам: создать их экземпляр!

В обоих случаях, поскольку код формы тесно связан с логикой приложения и переплетается с проблемами представления, совершенно невозможно написать один unit test, который охватывает даже один аспект того, что происходит. С шаблоном MVP вы можете полностью отделить компоненты, отвлечь их от интерфейсов, изолировать обязанности и написать десятки автоматических модульных тестов, которые охватывают все отдельные функции и документировать то, что спецификация - без написания одного бита документации: код становится его собственной документацией.