Добавить пользовательскую форму в другую книгу во время выполнения

У меня открыта надстройка и рабочая книга. Надстройка представляет собой файл .xlam, и в книге я добавил на нее ссылку. Надстройка защищена паролем.

Можно запускать общедоступные методы надстройки из моей книги. Однако один метод в надстройке использует VBA.UserForms.Add для открытия пользовательской формы, созданной во время выполнения подобно этому.

Скажем, рабочая книга, содержащая ссылку на myAddin, имеет следующее:

Private Sub callAddin()
    myAddin.ShowForm ThisWorkbook
End Sub

Обычно код в моей надстройке выглядит так:

Public Sub ShowForm(CallerWorkbook As Workbook)
    Const vbext_ct_MSForm As Long = 3

    'This is to stop screen flashing while creating form
    Application.VBE.MainWindow.Visible = False

    'Add to ThisWorkbook, not supplied workbook or VBE will crash - ignore CallerWorkbook
    Dim myForm As Object
    Set myForm = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm)

    'Create the User Form
    With myForm
        .Properties("Caption") = "Select"
        .Properties("Width") = 300
        .Properties("Height") = 270
    End With

    'Show the form
    Dim finalForm As Object
    Set finalForm = VBA.UserForms.Add(myForm.Name)
    finalForm.Show

    'Remove form
    ThisWorkbook.VBProject.VBComponents.Remove myForm

End Sub

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

Sub ShowForm(CallerWorkbook As Workbook)
    Const vbext_ct_MSForm As Long = 3

    'This is to stop screen flashing while creating form
    Application.VBE.MainWindow.Visible = False

    'Add to CallerWorkbook instead
    Dim myForm As Object
    Set myForm = CallerWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm)

    'Create the User Form
    With myForm
        .Properties("Caption") = "Select"
        .Properties("Width") = 300
        .Properties("Height") = 270
    End With

    'Show the form
    Dim finalForm As Object
    'Now myForm cannot be found and added
    Set finalForm = VBA.UserForms.Add(myForm.Name)
    finalForm.Show

    'Remove form
    CallerWorkbook.VBProject.VBComponents.Remove myForm

End Sub

Однако VBA не может видеть, куда сейчас указывает myForm.Name, поэтому метод Add не работает с "Run time error 424: Object required"

Есть ли способ отобразить форму, созданную во время выполнения, в другой книге?


person Greedo    schedule 26.07.2018    source источник
comment
Интересно, что в классе UserForm нет метода Show. Многие интерфейсы MSForms на самом деле подключаются во время выполнения с помощью VBA. Я не знаю, возможно ли это (пытался и потерпел неудачу только что), но независимо от этого... зачем вам мучиться с созданием 100% динамической пользовательской формы?   -  person Mathieu Guindon    schedule 26.07.2018
comment
FWIW Я получаю ошибку времени выполнения 75 Ошибка доступа к пути/файлу, когда пытаюсь выполнить Set finalForm = myForm.Designer, где finalForm объявлено As UserForm.   -  person Mathieu Guindon    schedule 26.07.2018
comment
Я думаю, что большая проблема заключается в том, что он, вероятно, просто не может скомпилировать новый источник формы во время выполнения. Я не уверен, что вы можете использовать расширения приложений VB, как вы испускаете CLR через .NET Reflection.   -  person Comintern    schedule 26.07.2018
comment
@MathieuGuindon Я пытаюсь создать фрагмент кода, который пользователь мог бы буквально скопировать и вставить в модуль/импортировать как единый модуль, и все это легко работало бы. Лично я использую его из стандартной надстройки панели инструментов, куда я мог бы включить пользовательскую форму шаблона, но я также хочу упростить использование из нового проекта - в этом случае идеально подходит один автономный файл. Значит 100% с нуля.   -  person Greedo    schedule 26.07.2018
comment
...ну, вы можете сделать это. Чего вы не можете сделать, так это запустить форму одновременно.   -  person Mathieu Guindon    schedule 26.07.2018
comment
Вы упоминаете о желании разместить шаблон UserForm в надстройке: должен ли дизайн формы фактически изменяться для каждой книги, или вы просто пытаетесь создать форму, чтобы она была доступна для указанной книги?   -  person ThunderFrame    schedule 27.07.2018
comment
Под чем я подразумеваю, добавляете ли вы элементы управления или код, специфичный для рабочей книги, или просто манипулируете свойствами существующих элементов управления (при условии, что у вас есть шаблон с элементами управления и кодом) способом, характерным для каждой рабочей книги?   -  person ThunderFrame    schedule 27.07.2018


Ответы (1)


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

Ваш оператор Set myForm = CallerWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm) возвращает VbComponent, а не UserForm, поэтому вы не можете использовать VBA.UserForms.Add(myForm.Name)

Есть 2 способа обойти это:

1. Создайте PublicNotCreatable шаблон пользовательской формы в своей надстройке.

Пользовательская форма подобна классу, поэтому она может иметь установленное свойство Instancing, как класс. Однако VBE не предоставляет свойство Instancing в окне свойств для пользовательских форм, поэтому для установки экземпляра необходимо экспортировать форму, а затем отредактировать атрибут Attribute VB_Exposed в файле FRM в текстовом редакторе перед импортом формы. снова. Вот шаги:

  • Создайте пользовательскую форму с именем TemplateForm в проекте надстройки.
  • Удалите TemplateForm и выберите Экспорт формы перед ее удалением.
  • Откройте файл TemplateForm.frm в текстовом редакторе.
  • Отредактируйте строку Attribute VB_Exposed = False так, чтобы она читалась как Attribute VB_Exposed = True.
  • Сохраните изменения в TemplateForm.frm
  • Импортируйте TemplateForm.frm в свою надстройку
  • Добавьте в надстройку общедоступную функцию, которая возвращает новый экземпляр TemplateForm. Я заставил эту функцию принимать ссылку на книгу, чтобы надстройка могла настраивать любые свойства книги в форме:

    Public Function GetTemplateForm(CallerWorkbook As Workbook) As TemplateForm
      Dim frm As TemplateForm
      Set frm = New TemplateForm
      'Set early-bound properties with intellisense
      frm.Caption = "Select"
      frm.Width = 300
      frm.Height = 270
    
      'Configure CallerWorkbook specific form properties here
      '...
      Set GetTemplateForm = frm
    End Function
    
  • Затем в рабочей книге пользователя вы можете отобразить экземпляр TemplateForm без необходимости динамического добавления формы или работы с мерцанием экрана или сложным для отладки кодом:

    Sub ShowAddinForm()
        With MyAddin.GetTemplateForm(ThisWorkbook)
            'Do more workbook specific propery setting here...
            '...
            .Show
        End With
    End Sub
    

** Примечание. Надстройка VBA Rubberduck скоро будет иметь возможность добавлять PublicNotCreatable UserForm.

2. Пусть надстройка создает компонент UserForm, но управляет им рабочая книга пользователя.

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

  • Добавьте этот код в надстройку:

    Public Function GetTempFormName(CallerWorkbook As Workbook) As String
        Const vbext_ct_MSForm As Long = 3
    
        'This is to stop screen flashing while creating form
        Application.VBE.MainWindow.Visible = False
    
        'Add to CallerWorkbook instead
        With CallerWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm)
            .Properties("Caption") = "Select"
            .Properties("Width") = 300
            .Properties("Height") = 270
            GetTempFormName = .Name
        End With
    End Function
    
    Public Sub RemoveTempForm(CallerWorkbook As Workbook, FormName As String)
        With CallerWorkbook.VBProject.VBComponents
            Dim comp As Object
            Set comp = .Item(FormName)
            .Remove .Item(FormName)
        End With
    End Sub
    
  • Затем в рабочей книге пользователя добавьте этот код:

    Sub GetAddinToCreateForm()
        Dim FormName As String
        FormName = MyAddin.GetTempFormName(ThisWorkbook)
        With VBA.UserForms.Add(FormName)
            .Show
        End With
        MyAddin.RemoveTempForm ThisWorkbook, FormName
    End Sub
    
person ThunderFrame    schedule 26.07.2018