#excel #vba
#excel #vba
Вопрос:
У меня возникла странная проблема при попытке Unload
использовать формы, прежде чем показывать другую.
Мой рабочий лист состоит из главного UserForm
, вызываемого UFMainMenu
, который может вызывать другие. Когда основная форма вызывает пользовательскую форму, она скрывает использование чего-то подобного в коде основной пользовательской формы:
Private Sub BtnSupprimerValeur_Click()
Me.Hide
UFSupInstanceParametre.Show
End Sub
Это работает так, как задумано, и открывает UFSupInstanceParametre
Внутри, UFSupInstanceParametre
если пользователь закрывает его, код Unload
UFSupInstanceParametre
и запускает обратно мой мастер UserForm
:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
Unload Me
Call mainMenu
End If
End Sub
MainMenu — это простой подраздел :
Sub mainMenu()
Application.ScreenUpdating = True
Dim objLoop As Object
'The following loop is just for me to see if previous userform was indeed unloaded, and if not it unloads it again
For Each objLoop In VBA.UserForms
If objLoop.Name <> "UFMainMenu" Then
Unload objLoop
End If
Next objLoop
UFMainMenu.Show
End Sub
Проблемы заключаются в :
— Unload Me
Внутренняя UFSupInstanceParametre
часть действительно не выгружается UFSupInstanceParametre
, поскольку она все еще отображается на экране.
-Внутри For
цикла в MainMenu при пошаговом выполнении я вижу, что UFSupInstanceParametre
не было выгружено, и я подтверждаю, что затем код запускает строку Unload objLoop
, которая в любом случае должна ее выгрузить. Этого не происходит, UFSupInstanceParametre
все еще отображается на экране. Чтобы быть на 100% уверенным, что он не выгружен, я попытался запустить во второй раз For
цикл, который снова был обнаружен UFSupInstanceParametre
как все еще загруженный, и снова попытался выгрузить его с помощью строки Unload objLoop
, но безуспешно.
-Код не выдает ошибку, он просто не выгружает пользовательскую форму.
-Код запускает основную пользовательскую форму, как и предполагалось, после попытки выгрузки других в MainMenu, но при этом другая все еще видна позади.
Я должен признать, что я немного запутался в этом, я знаю, что в пользовательских формах есть только одно простое свойство, которого мне не хватает, но я никак не могу его найти.
Есть идеи?
Комментарии:
1. Вам действительно нужно выгружать форму? Вы понимаете последствия (уничтожение объекта, проблемы с экземплярами по умолчанию, которые немедленно воссоздаются …)? Попробуйте просто установить для формы значение hidden (
Me.Hide
илиobjLoop.Hide
). И прочитайте rubberduckvba.wordpress.com/2017/10/25/userform1-show2. Мне нужно, чтобы она была выгружена, да, потому что их необходимо повторно инициализировать с обновленными данными и условием, поэтому простое скрытие и повторное отображение вернет его в предыдущее состояние, и я не могу пойти этим путем. Я мог бы использовать Hide и Show и сбросить пользовательскую форму в исходное состояние, когда она не скрыта, но в моем случае было бы намного эффективнее просто выгрузить ее и начать с нуля
3. Я должен добавить, что каждая пользовательская форма, запускаемая из основной, не предназначена для повторного запуска, но только в некоторых случаях пользователь может захотеть повторно выполнить операцию. Я не очень заинтересован в сохранении их в памяти
4.
Unload Me
это всегда ужасная идея — вы уничтожаете объект, пока его код все еще выполняется. Вы уверены, что всегда видите один и тот же экземпляр — я бы предположил, что вы каждый раз видите вновь созданный экземпляр по умолчанию. В любом случае, почему бы не переместить код, который заполняет форму, в подраздел и вызвать его изActivate
вместоInitialize
.event? Не говоря уже о MVC-системе, в которой данные управляются контроллером, а не представлением5. Я попробую, но что касается выгрузки меня, я добавил их, увидев, что
For
циклу не удалось выгрузить каждый UF, чтобы проверить, сработает ли это. Я собираюсь использоватьActivate
событие, даже если это означает добавление большего количества кода, чем я ожидал, поскольку оно должно заставить UF очистить некоторые данные из своего интерфейса. Тем не менее, я чувствую себя немного уклоняющимся от проблемы, делая это, я действительно не понимаю, почему это просто не выгружает их.
Ответ №1:
Я не могу подробно объяснить каждое поведение вашего кода, но основная проблема заключается в том, что вы прерываете код своей дочерней формы в UserForm_QueryClose
, показывая основную форму. VBA является однопоточной, и в тот момент, когда вы показываете модальную форму, код процедуры прерывается, отображается форма и выполняется код позади (например, Activate-Trigger).
Как только ваша основная форма снова будет скрыта, прерванный QueryClose
-код дочерней формы будет продолжен. Что вам нужно знать, это то, что триггер QueryClose-Trigger вызывается всякий раз, когда форма должна быть уничтожена — это может быть случай, если пользователь нажал кнопку закрытия системы (красная кнопка «X») или если где-то в вашем коде выполняется unload
инструкция. (если вы не задали параметр Cancel = True
).
Теперь, что произойдет, если вы выполните Unload
-оператор в queryClose
-триггере? Я точно не знаю, но, похоже, среда выполнения знает, что вы уже находитесь в середине Unload
-процесса, и игнорирует следующие попытки — это объясняет, почему вы никогда не избавляетесь от экземпляра в своем mainmenu
-коде.
Тот факт, что отображение формы vbmodeless
приводит к совершенно другому поведению, легко объяснить: код, отображающий форму (все еще говорящий о вашей queryClose
процедуре), продолжает выполняться как отображение немодальной формы, не прерывает код.
Итак, как решить эту проблему:
Что вам нужно реализовать, так это своего рода экземпляр контроллера, который показывает / скрывает формы. Не позволяйте формам быть «умными», это не их работа — уничтожать себя или знать, какую форму показывать следующей — это работа контроллера. И задача контроллера также заключалась бы в том, чтобы решить, уничтожать ли какой-либо объект формы или нет.
Быстрым решением может быть разрешение основной формы быть контроллером. Это, конечно, не лучшая практика, но ее было бы легко реализовать.
В основной форме вы помещаете код для отображения дочерней формы следующим образом (запускается с помощью кнопки или любого другого события)
Sub showChildform()
Me.Hide
UFMainMenu.Show
Me.Show
End Sub
Вот и все.
В UFMainMenu
вы можете удалить полный код UserForm_QueryClose
. Единственное, что вам нужно иметь в виду сейчас, это то, как ваша дочерняя форма может быть закрыта. Если у вас есть только кнопка закрытия системы, вам не нужно ничего делать — просто имейте в виду, что нажатие кнопки уничтожит форму. Но если вы добавите кнопку типа «Отмена», «Закрыть», «ОК» или что-то еще, вам нужно «оставить» форму для каждого кода. Варианты — Unload
или Hide
. Unload
как уже упоминалось, это очень плохая практика, используйте инструкцию Me.Hide
в коде запуска кнопки.
Но это оставляет вас в ситуации, когда форма может быть оставлена в двух разных состояниях: скрытой или уничтоженной. Если вы хотите быть последовательным, поместите следующий код в UserForm_QueryClose
-триггер, он скроет форму, но отменит уничтожение при нажатии кнопки закрытия системы. Для этого он проверяет параметр CloseMode
: если он имеет значение vbFormControlMenu
, пользователь нажал закрыть (есть другие случаи, когда вызывается триггер)
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
Cancel = True
Me.Hide
End If
End Sub
Теперь, почему считается плохой практикой, когда форма уничтожает себя? Краткий ответ: Это не задача формы (или любого другого объекта). Это сделало бы ссылку недействительной, и вызывающий код не знал бы. Вызывающий код не может проверить ничего, что было сделано в форме, если форма была оставлена нажатием кнопки «ОК» или «Отмена». Я мог бы объяснить это более подробно, но упомянутая статья Rubberduck объясняет это уже очень хорошо.
Комментарии:
1. Я понимаю. Спасибо за пояснения, теперь это вроде как имеет смысл. Жаль, что VBA только однопоточный, я думаю, поскольку это старый язык, который, возможно, не был полезен в то время, когда они его разрабатывали.