Пользовательская форма, устойчивая к выгрузке вызова

#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-show

2. Мне нужно, чтобы она была выгружена, да, потому что их необходимо повторно инициализировать с обновленными данными и условием, поэтому простое скрытие и повторное отображение вернет его в предыдущее состояние, и я не могу пойти этим путем. Я мог бы использовать 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 только однопоточный, я думаю, поскольку это старый язык, который, возможно, не был полезен в то время, когда они его разрабатывали.